"Fossies" - the Fresh Open Source Software Archive

Member "tin-2.4.3/src/page.c" (23 Nov 2018, 66860 Bytes) of package /linux/misc/tin-2.4.3.tar.xz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "page.c" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 2.4.2_vs_2.4.3.

    1 /*
    2  *  Project   : tin - a Usenet reader
    3  *  Module    : page.c
    4  *  Author    : I. Lea & R. Skrenta
    5  *  Created   : 1991-04-01
    6  *  Updated   : 2018-11-22
    7  *  Notes     :
    8  *
    9  * Copyright (c) 1991-2019 Iain Lea <iain@bricbrac.de>, Rich Skrenta <skrenta@pbm.com>
   10  * All rights reserved.
   11  *
   12  * Redistribution and use in source and binary forms, with or without
   13  * modification, are permitted provided that the following conditions
   14  * are met:
   15  * 1. Redistributions of source code must retain the above copyright
   16  *    notice, this list of conditions and the following disclaimer.
   17  * 2. Redistributions in binary form must reproduce the above copyright
   18  *    notice, this list of conditions and the following disclaimer in the
   19  *    documentation and/or other materials provided with the distribution.
   20  * 3. The name of the author may not be used to endorse or promote
   21  *    products derived from this software without specific prior written
   22  *    permission.
   23  *
   24  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
   25  * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
   26  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
   27  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
   28  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
   29  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
   30  * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
   31  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
   32  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
   33  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
   34  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   35  */
   36 
   37 
   38 #ifndef TIN_H
   39 #   include "tin.h"
   40 #endif /* !TIN_H */
   41 #ifndef TCURSES_H
   42 #   include "tcurses.h"
   43 #endif /* !TCURSES_H */
   44 
   45 
   46 /*
   47  * PAGE_HEADER is the size in lines of the article page header
   48  * ARTLINES is the number of lines available to display actual article text.
   49  */
   50 #define PAGE_HEADER 4
   51 #define ARTLINES    (NOTESLINES - (PAGE_HEADER - INDEX_TOP))
   52 
   53 int curr_line;          /* current line in art (indexed from 0) */
   54 static FILE *note_fp;           /* active stream (raw or cooked) */
   55 static int artlines;            /* active # of lines in pager */
   56 static t_lineinfo *artline; /* active 'lineinfo' data */
   57 
   58 static t_url *url_list;
   59 
   60 t_openartinfo pgart =   /* Global context of article open in the pager */
   61     {
   62         { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, FALSE, NULL},
   63         FALSE, 0,
   64         NULL, NULL, NULL, NULL,
   65     };
   66 
   67 int last_resp;          /* previous & current article # in arts[] for '-' command */
   68 int this_resp;
   69 
   70 size_t tabwidth = 8;
   71 
   72 static struct t_header *note_h = &pgart.hdr;    /* Easy access to article headers */
   73 
   74 static FILE *info_file;
   75 static const char *info_title;
   76 static int curr_info_line;
   77 static int hide_uue;            /* set when uuencoded sections are 'hidden' */
   78 static int num_info_lines;
   79 static int reveal_ctrl_l_lines; /* number of lines (from top) with de-activated ^L */
   80 static int rotate;              /* 0=normal, 13=rot13 decode */
   81 static int scroll_region_top;   /* first screen line for displayed message */
   82 static int search_line;         /* Line to commence next search from */
   83 static t_lineinfo *infoline = (t_lineinfo *) 0;
   84 
   85 static t_bool show_all_headers; /* all headers <-> headers in news_headers_to[_not]_display */
   86 static t_bool show_raw_article; /* CTRL-H raw <-> cooked article */
   87 static t_bool reveal_ctrl_l;    /* set when ^L hiding is off */
   88 
   89 /*
   90  * Local prototypes
   91  */
   92 static int build_url_list(void);
   93 static int load_article(int new_respnum, struct t_group *group);
   94 static int prompt_response(int ch, int curr_respnum);
   95 static int scroll_page(int dir);
   96 static t_bool deactivate_next_ctrl_l(void);
   97 static t_bool activate_last_ctrl_l(void);
   98 static t_bool process_url(int n);
   99 static t_bool url_page(void);
  100 static t_function page_left(void);
  101 static t_function page_right(void);
  102 static t_function page_mouse_action(t_function (*left_action) (void), t_function (*right_action) (void));
  103 static t_function url_left(void);
  104 static t_function url_right(void);
  105 static void build_url_line(int i);
  106 static void draw_page_header(const char *group);
  107 static void draw_url_arrow(void);
  108 static void free_url_list(void);
  109 static void preprocess_info_message(FILE *info_fh);
  110 static void print_message_page(FILE *file, t_lineinfo *messageline, size_t messagelines, size_t base_line, size_t begin, size_t end, int help_level);
  111 static void process_search(int *lcurr_line, size_t message_lines, size_t screen_lines, int help_level);
  112 static void show_url_page(void);
  113 static void invoke_metamail(FILE *fp);
  114 
  115 static t_menu urlmenu = { 0, 0, 0, show_url_page, draw_url_arrow, build_url_line };
  116 
  117 #ifdef XFACE_ABLE
  118 #   define XFACE_SHOW() if (tinrc.use_slrnface) \
  119                                 slrnface_show_xface()
  120 #   define XFACE_CLEAR()    if (tinrc.use_slrnface) \
  121                                 slrnface_clear_xface()
  122 #   define XFACE_SUPPRESS() if (tinrc.use_slrnface) \
  123                                 slrnface_suppress_xface()
  124 #else
  125 #   define XFACE_SHOW() /*nothing*/
  126 #   define XFACE_CLEAR()    /*nothing*/
  127 #   define XFACE_SUPPRESS() /*nothing*/
  128 #endif /* XFACE_ABLE */
  129 
  130 /*
  131  * Scroll visible article part of display down (+ve) or up (-ve)
  132  * according to 'dir' (KEYMAP_UP or KEYMAP_DOWN) and tinrc.scroll_lines
  133  * >= 1  line count
  134  * 0     full page scroll
  135  * -1    full page but retain last line of prev page when scrolling
  136  *       down. Obviously only applies when scrolling down.
  137  * -2    half page scroll
  138  * Return the offset we scrolled by so that redrawing can be done
  139  */
  140 static int
  141 scroll_page(
  142     int dir)
  143 {
  144     int i;
  145 
  146     if (tinrc.scroll_lines >= 1)
  147         i = tinrc.scroll_lines;
  148     else {
  149         i = (signal_context == cPage) ? ARTLINES : NOTESLINES;
  150         switch (tinrc.scroll_lines) {
  151             case 0:
  152                 break;
  153 
  154             case -1:
  155                 i--;
  156                 break;
  157 
  158             case -2:
  159                 i >>= 1;
  160                 break;
  161         }
  162     }
  163 
  164     if (dir == KEYMAP_UP)
  165         i = -i;
  166 
  167 #ifdef USE_CURSES
  168     scrollok(stdscr, TRUE);
  169 #endif /* USE_CURSES */
  170     SetScrollRegion(scroll_region_top, NOTESLINES + 1);
  171     ScrollScreen(i);
  172     SetScrollRegion(0, cLINES);
  173 #ifdef USE_CURSES
  174     scrollok(stdscr, FALSE);
  175 #endif /* USE_CURSES */
  176 
  177     return i;
  178 }
  179 
  180 
  181 /*
  182  * Map keypad codes to standard keyboard characters
  183  */
  184 static t_function
  185 page_left(
  186     void)
  187 {
  188     return GLOBAL_QUIT;
  189 }
  190 
  191 
  192 static t_function
  193 page_right(
  194     void)
  195 {
  196     return PAGE_NEXT_UNREAD;
  197 }
  198 
  199 
  200 static t_function
  201 page_mouse_action(
  202     t_function (*left_action) (void),
  203     t_function (*right_action) (void))
  204 {
  205     t_function func = NOT_ASSIGNED;
  206 
  207     switch (xmouse) {
  208         case MOUSE_BUTTON_1:
  209             if (xrow < PAGE_HEADER || xrow >= cLINES - 1)
  210                 func = GLOBAL_PAGE_DOWN;
  211             else
  212                 func = right_action();
  213             break;
  214 
  215         case MOUSE_BUTTON_2:
  216             if (xrow < PAGE_HEADER || xrow >= cLINES - 1)
  217                 func = GLOBAL_PAGE_UP;
  218             else
  219                 func = left_action();
  220             break;
  221 
  222         case MOUSE_BUTTON_3:
  223             func = SPECIAL_MOUSE_TOGGLE;
  224             break;
  225 
  226         default:
  227             break;
  228     }
  229     return func;
  230 }
  231 
  232 
  233 /*
  234  * Make hidden part of article after ^L visible.
  235  * Returns:
  236  *    FALSE no ^L found, no changes
  237  *    TRUE  ^L found and displayed page must be updated
  238  *          (draw_page must be called)
  239  */
  240 static t_bool
  241 deactivate_next_ctrl_l(
  242     void)
  243 {
  244     int i;
  245     int end = curr_line + ARTLINES;
  246 
  247     if (reveal_ctrl_l)
  248         return FALSE;
  249     if (end > artlines)
  250         end = artlines;
  251     for (i = reveal_ctrl_l_lines + 1; i < end; i++)
  252         if (artline[i].flags & C_CTRLL) {
  253             reveal_ctrl_l_lines = i;
  254             return TRUE;
  255         }
  256     reveal_ctrl_l_lines = end - 1;
  257     return FALSE;
  258 }
  259 
  260 
  261 /*
  262  * Re-hide revealed part of article after last ^L
  263  * that is currently displayed.
  264  * Returns:
  265  *    FALSE no ^L found, no changes
  266  *    TRUE  ^L found and displayed page must be updated
  267  *          (draw_page must be called)
  268  */
  269 static t_bool
  270 activate_last_ctrl_l(
  271     void)
  272 {
  273     int i;
  274 
  275     if (reveal_ctrl_l)
  276         return FALSE;
  277     for (i = reveal_ctrl_l_lines; i >= curr_line; i--)
  278         if (artline[i].flags & C_CTRLL) {
  279             reveal_ctrl_l_lines = i - 1;
  280             return TRUE;
  281         }
  282     reveal_ctrl_l_lines = curr_line - 1;
  283     return FALSE;
  284 }
  285 
  286 
  287 /*
  288  * The main routine for viewing articles
  289  * Returns:
  290  *    >=0   normal exit - return a new base[] note
  291  *    <0    indicates some unusual condition. See GRP_* in tin.h
  292  *          GRP_QUIT        User is doing a 'Q'
  293  *          GRP_RETSELECT   Back to selection level due to 'T' command
  294  *          GRP_ARTUNAVAIL  We didn't make it into the art
  295  *                          don't bother fixing the screen up
  296  *          GRP_ARTABORT    User 'q'uit load of article
  297  *          GRP_GOTOTHREAD  To thread menu due to 'l' command
  298  *          GRP_NEXT        Catchup with 'c'
  299  *          GRP_NEXTUNREAD     "      "  'C'
  300  */
  301 int
  302 show_page(
  303     struct t_group *group,
  304     int start_respnum,      /* index into arts[] */
  305     int *threadnum)         /* to allow movement in thread mode */
  306 {
  307     char buf[LEN];
  308     char key[MAXKEYLEN];
  309     int i, j, n = 0;
  310     int art_type = GROUP_TYPE_NEWS;
  311     int hide_uue_tmp;
  312     t_artnum old_artnum = T_ARTNUM_CONST(0);
  313     t_bool mouse_click_on = TRUE;
  314     t_bool repeat_search;
  315     t_function func;
  316 
  317     if (group->attribute->mailing_list != NULL)
  318         art_type = GROUP_TYPE_MAIL;
  319 
  320     /*
  321      * Peek to see if the pager started due to a body search
  322      * Stop load_article() changing context again
  323      */
  324     if (srch_lineno != -1)
  325         this_resp = start_respnum;
  326 
  327     if ((i = load_article(start_respnum, group)) < 0)
  328         return i;
  329 
  330     if (srch_lineno != -1)
  331         process_search(&curr_line, artlines, ARTLINES, PAGE_LEVEL);
  332 
  333     forever {
  334         if ((func = handle_keypad(page_left, page_right, page_mouse_action, page_keys)) == GLOBAL_SEARCH_REPEAT) {
  335             func = last_search;
  336             repeat_search = TRUE;
  337         } else
  338             repeat_search = FALSE;
  339 
  340         switch (func) {
  341             case GLOBAL_ABORT:  /* Abort */
  342                 break;
  343 
  344             case DIGIT_1:
  345             case DIGIT_2:
  346             case DIGIT_3:
  347             case DIGIT_4:
  348             case DIGIT_5:
  349             case DIGIT_6:
  350             case DIGIT_7:
  351             case DIGIT_8:
  352             case DIGIT_9:
  353                 if (!HAS_FOLLOWUPS(which_thread(this_resp)))
  354                     info_message(_(txt_no_responses));
  355                 else {
  356                     if ((n = prompt_response(func_to_key(func, page_keys), this_resp)) != -1) {
  357                         XFACE_CLEAR();
  358                         if ((i = load_article(n, group)) < 0)
  359                             return i;
  360                     }
  361                 }
  362                 break;
  363 
  364 #ifndef NO_SHELL_ESCAPE
  365             case GLOBAL_SHELL_ESCAPE:
  366                 XFACE_CLEAR();
  367                 shell_escape();
  368                 draw_page(group->name, 0);
  369                 break;
  370 #endif /* !NO_SHELL_ESCAPE */
  371 
  372             case SPECIAL_MOUSE_TOGGLE:
  373                 if (mouse_click_on)
  374                     set_xclick_off();
  375                 else
  376                     set_xclick_on();
  377                 mouse_click_on = bool_not(mouse_click_on);
  378                 break;
  379 
  380             case GLOBAL_PAGE_UP:
  381                 if (activate_last_ctrl_l())
  382                     draw_page(group->name, 0);
  383                 else {
  384                     if (curr_line == 0)
  385                         info_message(_(txt_begin_of_art));
  386                     else {
  387                         curr_line -= ((tinrc.scroll_lines == -2) ? ARTLINES / 2 : ARTLINES);
  388                         draw_page(group->name, 0);
  389                     }
  390                 }
  391                 break;
  392 
  393             case GLOBAL_PAGE_DOWN:      /* page down or next response */
  394             case PAGE_NEXT_UNREAD:
  395                 if (!((func == PAGE_NEXT_UNREAD) && (tinrc.goto_next_unread & GOTO_NEXT_UNREAD_TAB)) && deactivate_next_ctrl_l())
  396                     draw_page(group->name, 0);
  397                 else {
  398                     if (curr_line + ARTLINES >= artlines) { /* End is already on screen */
  399                         switch (func) {
  400                             case PAGE_NEXT_UNREAD:  /* <TAB> */
  401                                 goto page_goto_next_unread;
  402 
  403                             case GLOBAL_PAGE_DOWN:
  404                                 if (tinrc.goto_next_unread & GOTO_NEXT_UNREAD_PGDN)
  405                                     goto page_goto_next_unread;
  406                                 break;
  407 
  408                             default:        /* to keep gcc quiet */
  409                                 break;
  410                         }
  411                         info_message(_(txt_end_of_art));
  412                     } else {
  413                         if ((func == PAGE_NEXT_UNREAD) && (tinrc.goto_next_unread & GOTO_NEXT_UNREAD_TAB))
  414                             goto page_goto_next_unread;
  415 
  416                         curr_line += ((tinrc.scroll_lines == -2) ? ARTLINES / 2 : ARTLINES);
  417 
  418                         if (tinrc.scroll_lines == -1)       /* formerly show_last_line_prev_page */
  419                             curr_line--;
  420                         draw_page(group->name, 0);
  421                     }
  422                 }
  423                 break;
  424 
  425 page_goto_next_unread:
  426                 XFACE_CLEAR();
  427                 if ((n = next_unread(next_response(this_resp))) == -1)
  428                     return (which_thread(this_resp));
  429                 if ((i = load_article(n, group)) < 0)
  430                     return i;
  431                 break;
  432 
  433             case GLOBAL_FIRST_PAGE:     /* beginning of article */
  434                 if (reveal_ctrl_l_lines > -1 || curr_line != 0) {
  435                     reveal_ctrl_l_lines = -1;
  436                     curr_line = 0;
  437                     draw_page(group->name, 0);
  438                 }
  439                 break;
  440 
  441             case GLOBAL_LAST_PAGE:      /* end of article */
  442                 if (reveal_ctrl_l_lines < artlines - 1 || curr_line + ARTLINES != artlines) {
  443                     reveal_ctrl_l_lines = artlines - 1;
  444                     /* Display a full last page for neatness */
  445                     curr_line = artlines - ARTLINES;
  446                     draw_page(group->name, 0);
  447                 }
  448                 break;
  449 
  450             case GLOBAL_LINE_UP:
  451                 if (activate_last_ctrl_l())
  452                     draw_page(group->name, 0);
  453                 else {
  454                     if (curr_line == 0) {
  455                         info_message(_(txt_begin_of_art));
  456                         break;
  457                     }
  458 
  459                     i = scroll_page(KEYMAP_UP);
  460                     curr_line += i;
  461                     draw_page(group->name, i);
  462                 }
  463                 break;
  464 
  465             case GLOBAL_LINE_DOWN:
  466                 if (deactivate_next_ctrl_l())
  467                     draw_page(group->name, 0);
  468                 else {
  469                     if (curr_line + ARTLINES >= artlines) {
  470                         info_message(_(txt_end_of_art));
  471                         break;
  472                     }
  473 
  474                     i = scroll_page(KEYMAP_DOWN);
  475                     curr_line += i;
  476                     draw_page(group->name, i);
  477                 }
  478                 break;
  479 
  480             case GLOBAL_LAST_VIEWED:    /* show last viewed article */
  481                 if (last_resp < 0 || (which_thread(last_resp) == -1)) {
  482                     info_message(_(txt_no_last_message));
  483                     break;
  484                 }
  485                 if ((i = load_article(last_resp, group)) < 0) {
  486                     XFACE_CLEAR();
  487                     return i;
  488                 }
  489                 break;
  490 
  491             case GLOBAL_LOOKUP_MESSAGEID:           /* Goto article by Message-ID */
  492                 if ((n = prompt_msgid()) != ART_UNAVAILABLE) {
  493                     if ((i = load_article(n, group)) < 0) {
  494                         XFACE_CLEAR();
  495                         return i;
  496                     }
  497                 }
  498                 break;
  499 
  500             case PAGE_GOTO_PARENT:      /* Goto parent of this article */
  501             {
  502                 struct t_msgid *parent = arts[this_resp].refptr->parent;
  503 
  504                 if (parent == NULL) {
  505                     info_message(_(txt_art_parent_none));
  506                     break;
  507                 }
  508 
  509                 if (parent->article == ART_UNAVAILABLE) {
  510                     info_message(_(txt_art_parent_unavail));
  511                     break;
  512                 }
  513 
  514                 if (arts[parent->article].killed && tinrc.kill_level == KILL_NOTHREAD) {
  515                     info_message(_(txt_art_parent_killed));
  516                     break;
  517                 }
  518 
  519                 if ((i = load_article(parent->article, group)) < 0) {
  520                     XFACE_CLEAR();
  521                     return i;
  522                 }
  523 
  524                 break;
  525             }
  526 
  527             case GLOBAL_PIPE:       /* pipe article/thread/tagged arts to command */
  528                 XFACE_SUPPRESS();
  529                 feed_articles(FEED_PIPE, PAGE_LEVEL, NOT_ASSIGNED, group, this_resp);
  530                 XFACE_SHOW();
  531                 break;
  532 
  533             case PAGE_MAIL: /* mail article/thread/tagged articles to somebody */
  534                 XFACE_SUPPRESS();
  535                 feed_articles(FEED_MAIL, PAGE_LEVEL, NOT_ASSIGNED, group, this_resp);
  536                 XFACE_SHOW();
  537                 break;
  538 
  539 #ifndef DISABLE_PRINTING
  540             case GLOBAL_PRINT:  /* output art/thread/tagged arts to printer */
  541                 XFACE_SUPPRESS();
  542                 feed_articles(FEED_PRINT, PAGE_LEVEL, NOT_ASSIGNED, group, this_resp);
  543                 XFACE_SHOW();
  544                 break;
  545 #endif /* !DISABLE_PRINTING */
  546 
  547             case PAGE_REPOST:   /* repost current article */
  548                 if (can_post) {
  549                     XFACE_SUPPRESS();
  550                     feed_articles(FEED_REPOST, PAGE_LEVEL, NOT_ASSIGNED, group, this_resp);
  551                     XFACE_SHOW();
  552                 } else
  553                     info_message(_(txt_cannot_post));
  554                 break;
  555 
  556             case PAGE_SAVE: /* save article/thread/tagged articles */
  557                 XFACE_SUPPRESS();
  558                 feed_articles(FEED_SAVE, PAGE_LEVEL, NOT_ASSIGNED, group, this_resp);
  559                 XFACE_SHOW();
  560                 break;
  561 
  562             case PAGE_AUTOSAVE: /* Auto-save articles without prompting */
  563                 if (grpmenu.curr >= 0) {
  564                     XFACE_SUPPRESS();
  565                     feed_articles(FEED_AUTOSAVE, PAGE_LEVEL, NOT_ASSIGNED, group, (int) base[grpmenu.curr]);
  566                     XFACE_SHOW();
  567                 }
  568                 break;
  569 
  570             case GLOBAL_SEARCH_REPEAT:
  571                 info_message(_(txt_no_prev_search));
  572                 break;
  573 
  574             case GLOBAL_SEARCH_SUBJECT_FORWARD: /* search in article */
  575             case GLOBAL_SEARCH_SUBJECT_BACKWARD:
  576                 if (search_article((func == GLOBAL_SEARCH_SUBJECT_FORWARD), repeat_search, search_line, artlines, artline, reveal_ctrl_l_lines, note_fp) == -1)
  577                     break;
  578 
  579                 if (func == GLOBAL_SEARCH_SUBJECT_BACKWARD && !reveal_ctrl_l) {
  580                     reveal_ctrl_l_lines = curr_line + ARTLINES - 1;
  581                     draw_page(group->name, 0);
  582                 }
  583                 process_search(&curr_line, artlines, ARTLINES, PAGE_LEVEL);
  584                 break;
  585 
  586             case GLOBAL_SEARCH_BODY:    /* article body search */
  587                 if ((n = search_body(group, this_resp, repeat_search)) != -1) {
  588                     this_resp = n;          /* Stop load_article() changing context again */
  589                     if ((i = load_article(n, group)) < 0) {
  590                         XFACE_CLEAR();
  591                         return i;
  592                     }
  593                     process_search(&curr_line, artlines, ARTLINES, PAGE_LEVEL);
  594                 }
  595                 break;
  596 
  597             case PAGE_TOP_THREAD:   /* first article in current thread */
  598                 if (arts[this_resp].prev >= 0) {
  599                     if ((n = which_thread(this_resp)) >= 0 && base[n] != this_resp) {
  600                         assert(n < grpmenu.max);
  601                         if ((i = load_article(base[n], group)) < 0) {
  602                             XFACE_CLEAR();
  603                             return i;
  604                         }
  605                     }
  606                 }
  607                 break;
  608 
  609             case PAGE_BOTTOM_THREAD:    /* last article in current thread */
  610                 for (i = this_resp; i >= 0; i = arts[i].thread)
  611                     n = i;
  612 
  613                 if (n != this_resp) {
  614                     if ((i = load_article(n, group)) < 0) {
  615                         XFACE_CLEAR();
  616                         return i;
  617                     }
  618                 }
  619                 break;
  620 
  621             case PAGE_NEXT_THREAD:  /* start of next thread */
  622                 XFACE_CLEAR();
  623                 if ((n = next_thread(this_resp)) == -1)
  624                     return (which_thread(this_resp));
  625                 if ((i = load_article(n, group)) < 0)
  626                     return i;
  627                 break;
  628 
  629 #ifdef HAVE_PGP_GPG
  630             case PAGE_PGP_CHECK_ARTICLE:
  631                 XFACE_SUPPRESS();
  632                 if (pgp_check_article(&pgart))
  633                     draw_page(group->name, 0);
  634                 XFACE_SHOW();
  635                 break;
  636 #endif /* HAVE_PGP_GPG */
  637 
  638             case PAGE_TOGGLE_HEADERS:   /* toggle display of all headers */
  639                 XFACE_CLEAR();
  640                 show_all_headers = bool_not(show_all_headers);
  641                 resize_article(TRUE, &pgart);   /* Also recooks it.. */
  642                 curr_line = 0;
  643                 draw_page(group->name, 0);
  644                 break;
  645 
  646             case PAGE_TOGGLE_RAW:   /* toggle display of whole 'raw' article */
  647                 XFACE_CLEAR();
  648                 toggle_raw(group);
  649                 break;
  650 
  651             case PAGE_TOGGLE_TEX2ISO:       /* toggle german TeX to ISO latin1 style conversion */
  652                 if (((group->attribute->tex2iso_conv) = !(group->attribute->tex2iso_conv)))
  653                     pgart.tex2iso = is_art_tex_encoded(pgart.raw);
  654                 else
  655                     pgart.tex2iso = FALSE;
  656 
  657                 resize_article(TRUE, &pgart);   /* Also recooks it.. */
  658                 draw_page(group->name, 0);
  659                 info_message(_(txt_toggled_tex2iso), txt_onoff[group->attribute->tex2iso_conv != FALSE ? 1 : 0]);
  660                 break;
  661 
  662             case PAGE_TOGGLE_TABS:      /* toggle tab stops 8 vs 4 */
  663                 tabwidth = (tabwidth == 8) ? 4 : 8;
  664                 resize_article(TRUE, &pgart);   /* Also recooks it.. */
  665                 draw_page(group->name, 0);
  666                 info_message(_(txt_toggled_tabwidth), tabwidth);
  667                 break;
  668 
  669             case PAGE_TOGGLE_UUE:           /* toggle display of uuencoded sections */
  670                 hide_uue = (hide_uue + 1) % (UUE_ALL + 1);
  671                 resize_article(TRUE, &pgart);   /* Also recooks it.. */
  672                 /*
  673                  * If we hid uue and are off the end of the article, reposition to
  674                  * show last page for neatness
  675                  */
  676                 if (hide_uue && curr_line + ARTLINES > artlines)
  677                     curr_line = artlines - ARTLINES;
  678                 draw_page(group->name, 0);
  679                 /* TODO: info_message()? */
  680                 break;
  681 
  682             case PAGE_REVEAL:           /* toggle hiding after ^L */
  683                 reveal_ctrl_l = bool_not(reveal_ctrl_l);
  684                 if (!reveal_ctrl_l) {   /* switched back to active ^L's */
  685                     reveal_ctrl_l_lines = -1;
  686                     curr_line = 0;
  687                 } else
  688                     reveal_ctrl_l_lines = artlines - 1;
  689                 draw_page(group->name, 0);
  690                 /* TODO: info_message()? */
  691                 break;
  692 
  693             case GLOBAL_QUICK_FILTER_SELECT:    /* quickly auto-select article */
  694             case GLOBAL_QUICK_FILTER_KILL:      /* quickly kill article */
  695                 if (quick_filter(func, group, &arts[this_resp])) {
  696                     old_artnum = arts[this_resp].artnum;
  697                     unfilter_articles(group);
  698                     filter_articles(group);
  699                     make_threads(group, FALSE);
  700                     if ((n = find_artnum(old_artnum)) == -1 || which_thread(n) == -1) /* We have lost the thread */
  701                         return GRP_KILLED;
  702                     this_resp = n;
  703                     draw_page(group->name, 0);
  704                     info_message((func == GLOBAL_QUICK_FILTER_KILL) ? _(txt_info_add_kill) : _(txt_info_add_select));
  705                 }
  706                 break;
  707 
  708             case GLOBAL_MENU_FILTER_SELECT:     /* auto-select article menu */
  709             case GLOBAL_MENU_FILTER_KILL:           /* kill article menu */
  710                 XFACE_CLEAR();
  711                 if (filter_menu(func, group, &arts[this_resp])) {
  712                     old_artnum = arts[this_resp].artnum;
  713                     unfilter_articles(group);
  714                     filter_articles(group);
  715                     make_threads(group, FALSE);
  716                     if ((n = find_artnum(old_artnum)) == -1 || which_thread(n) == -1) /* We have lost the thread */
  717                         return GRP_KILLED;
  718                     this_resp = n;
  719                 }
  720                 draw_page(group->name, 0);
  721                 break;
  722 
  723             case GLOBAL_EDIT_FILTER:
  724                 XFACE_CLEAR();
  725                 if (invoke_editor(filter_file, filter_file_offset, NULL)) {
  726                     old_artnum = arts[this_resp].artnum;
  727                     unfilter_articles(group);
  728                     (void) read_filter_file(filter_file);
  729                     filter_articles(group);
  730                     make_threads(group, FALSE);
  731                     if ((n = find_artnum(old_artnum)) == -1 || which_thread(n) == -1) /* We have lost the thread */
  732                         return GRP_KILLED;
  733                     this_resp = n;
  734                 }
  735                 draw_page(group->name, 0);
  736                 break;
  737 
  738             case GLOBAL_REDRAW_SCREEN:      /* redraw current page of article */
  739                 my_retouch();
  740                 draw_page(group->name, 0);
  741                 break;
  742 
  743             case PAGE_TOGGLE_ROT13: /* toggle rot-13 mode */
  744                 rotate = rotate ? 0 : 13;
  745                 draw_page(group->name, 0);
  746                 info_message(_(txt_toggled_rot13));
  747                 break;
  748 
  749             case GLOBAL_SEARCH_AUTHOR_FORWARD:  /* author search forward */
  750             case GLOBAL_SEARCH_AUTHOR_BACKWARD: /* author search backward */
  751                 if ((n = search(func, this_resp, repeat_search)) < 0)
  752                     break;
  753                 if ((i = load_article(n, group)) < 0) {
  754                     XFACE_CLEAR();
  755                     return i;
  756                 }
  757                 break;
  758 
  759             case CATCHUP:           /* catchup - mark read, goto next */
  760             case CATCHUP_NEXT_UNREAD:   /* goto next unread */
  761                 if (group->attribute->thread_articles == THREAD_NONE)
  762                     snprintf(buf, sizeof(buf), _(txt_mark_art_read), (func == CATCHUP_NEXT_UNREAD) ? _(txt_enter_next_unread_art) : "");
  763                 else
  764                     snprintf(buf, sizeof(buf), _(txt_mark_thread_read), (func == CATCHUP_NEXT_UNREAD) ? _(txt_enter_next_thread) : "");
  765                 if ((!TINRC_CONFIRM_ACTION) || prompt_yn(buf, TRUE) == 1) {
  766                     thd_mark_read(group, base[which_thread(this_resp)]);
  767                     XFACE_CLEAR();
  768                     return (func == CATCHUP_NEXT_UNREAD) ? GRP_NEXTUNREAD : GRP_NEXT;
  769                 }
  770                 break;
  771 
  772             case MARK_THREAD_UNREAD:
  773                 thd_mark_unread(group, base[which_thread(this_resp)]);
  774                 if (group->attribute->thread_articles != THREAD_NONE)
  775                     info_message(_(txt_marked_as_unread), _(txt_thread_upper));
  776                 else
  777                     info_message(_(txt_marked_as_unread), _(txt_article_upper));
  778                 break;
  779 
  780             case PAGE_CANCEL:           /* cancel an article */
  781                 if (can_post || art_type != GROUP_TYPE_NEWS) {
  782                     XFACE_SUPPRESS();
  783                     if (cancel_article(group, &arts[this_resp], this_resp))
  784                         draw_page(group->name, 0);
  785                     XFACE_SHOW();
  786                 } else
  787                     info_message(_(txt_cannot_post));
  788                 break;
  789 
  790             case PAGE_EDIT_ARTICLE:     /* edit an article (mailgroup only) */
  791                 XFACE_SUPPRESS();
  792                 if (art_edit(group, &arts[this_resp]))
  793                     draw_page(group->name, 0);
  794                 XFACE_SHOW();
  795                 break;
  796 
  797             case PAGE_FOLLOWUP_QUOTE:       /* post a followup to this article */
  798             case PAGE_FOLLOWUP_QUOTE_HEADERS:
  799             case PAGE_FOLLOWUP:
  800                 if (!can_post && art_type == GROUP_TYPE_NEWS) {
  801                     info_message(_(txt_cannot_post));
  802                     break;
  803                 }
  804                 XFACE_CLEAR();
  805                 (void) post_response(group->name, this_resp,
  806                   (func == PAGE_FOLLOWUP_QUOTE || func == PAGE_FOLLOWUP_QUOTE_HEADERS) ? TRUE : FALSE,
  807                   func == PAGE_FOLLOWUP_QUOTE_HEADERS ? TRUE : FALSE, show_raw_article);
  808                 draw_page(group->name, 0);
  809                 break;
  810 
  811             case GLOBAL_HELP:   /* help */
  812                 XFACE_CLEAR();
  813                 show_help_page(PAGE_LEVEL, _(txt_art_pager_com));
  814                 draw_page(group->name, 0);
  815                 break;
  816 
  817             case GLOBAL_TOGGLE_HELP_DISPLAY:    /* toggle mini help menu */
  818                 toggle_mini_help(PAGE_LEVEL);
  819                 draw_page(group->name, 0);
  820                 break;
  821 
  822             case GLOBAL_QUIT:   /* return to index page */
  823 return_to_index:
  824                 XFACE_CLEAR();
  825                 i = which_thread(this_resp);
  826                 if (threadnum)
  827                     *threadnum = which_response(this_resp);
  828 
  829                 return i;
  830 
  831             case GLOBAL_TOGGLE_INVERSE_VIDEO:   /* toggle inverse video */
  832                 toggle_inverse_video();
  833                 draw_page(group->name, 0);
  834                 show_inverse_video_status();
  835                 break;
  836 
  837 #ifdef HAVE_COLOR
  838             case GLOBAL_TOGGLE_COLOR:       /* toggle color */
  839                 if (toggle_color()) {
  840                     draw_page(group->name, 0);
  841                     show_color_status();
  842                 }
  843                 break;
  844 #endif /* HAVE_COLOR */
  845 
  846             case PAGE_LIST_THREAD:  /* -> thread page that this article is in */
  847                 XFACE_CLEAR();
  848                 fixup_thread(this_resp, FALSE);
  849                 return GRP_GOTOTHREAD;
  850 
  851             case GLOBAL_OPTION_MENU:    /* option menu */
  852                 XFACE_CLEAR();
  853                 old_artnum = arts[this_resp].artnum;
  854                 config_page(group->name, signal_context);
  855                 if ((this_resp = find_artnum(old_artnum)) == -1 || which_thread(this_resp) == -1) { /* We have lost the thread */
  856                     pos_first_unread_thread();
  857                     return GRP_EXIT;
  858                 }
  859                 fixup_thread(this_resp, FALSE);
  860                 draw_page(group->name, 0);
  861                 break;
  862 
  863             case PAGE_NEXT_ARTICLE: /* skip to next article */
  864                 XFACE_CLEAR();
  865                 if ((n = next_response(this_resp)) == -1)
  866                     return (which_thread(this_resp));
  867 
  868                 if ((i = load_article(n, group)) < 0)
  869                     return i;
  870                 break;
  871 
  872             case PAGE_MARK_THREAD_READ: /* mark rest of thread as read */
  873                 thd_mark_read(group, this_resp);
  874                 if ((n = next_unread(next_response(this_resp))) == -1)
  875                     goto return_to_index;
  876                 if ((i = load_article(n, group)) < 0) {
  877                     XFACE_CLEAR();
  878                     return i;
  879                 }
  880                 break;
  881 
  882             case PAGE_NEXT_UNREAD_ARTICLE:  /* next unread article */
  883                 goto page_goto_next_unread;
  884 
  885             case PAGE_PREVIOUS_ARTICLE: /* previous article */
  886                 XFACE_CLEAR();
  887                 if ((n = prev_response(this_resp)) == -1)
  888                     return this_resp;
  889 
  890                 if ((i = load_article(n, group)) < 0)
  891                     return i;
  892                 break;
  893 
  894             case PAGE_PREVIOUS_UNREAD_ARTICLE:  /* previous unread article */
  895                 if ((n = prev_unread(prev_response(this_resp))) == -1)
  896                     info_message(_(txt_no_prev_unread_art));
  897                 else {
  898                     if ((i = load_article(n, group)) < 0) {
  899                         XFACE_CLEAR();
  900                         return i;
  901                     }
  902                 }
  903                 break;
  904 
  905             case GLOBAL_QUIT_TIN:   /* quit */
  906                 XFACE_CLEAR();
  907                 return GRP_QUIT;
  908 
  909             case PAGE_REPLY_QUOTE:  /* reply to author through mail */
  910             case PAGE_REPLY_QUOTE_HEADERS:
  911             case PAGE_REPLY:
  912                 XFACE_CLEAR();
  913                 mail_to_author(group->name, this_resp, (func == PAGE_REPLY_QUOTE || func == PAGE_REPLY_QUOTE_HEADERS) ? TRUE : FALSE, func == PAGE_REPLY_QUOTE_HEADERS ? TRUE : FALSE, show_raw_article);
  914                 draw_page(group->name, 0);
  915                 break;
  916 
  917             case PAGE_TAG:  /* tag/untag article for saving */
  918                 tag_article(this_resp);
  919                 break;
  920 
  921             case PAGE_GROUP_SELECT: /* return to group selection page */
  922 #if 0
  923                 /* Hasn't been used since tin 1.1 PL4 */
  924                 if (filter_state == FILTERING) {
  925                     filter_articles(group);
  926                     make_threads(group, FALSE);
  927                 }
  928 #endif /* 0 */
  929                 XFACE_CLEAR();
  930                 return GRP_RETSELECT;
  931 
  932             case GLOBAL_VERSION:
  933                 info_message(cvers);
  934                 break;
  935 
  936             case GLOBAL_POST:   /* post a basenote */
  937                 XFACE_SUPPRESS();
  938                 if (post_article(group->name))
  939                     draw_page(group->name, 0);
  940                 XFACE_SHOW();
  941                 break;
  942 
  943             case GLOBAL_POSTPONED:  /* post postponed article */
  944                 if (can_post || art_type != GROUP_TYPE_NEWS) {
  945                     XFACE_SUPPRESS();
  946                     if (pickup_postponed_articles(FALSE, FALSE))
  947                         draw_page(group->name, 0);
  948                     XFACE_SHOW();
  949                 } else
  950                     info_message(_(txt_cannot_post));
  951                 break;
  952 
  953             case GLOBAL_DISPLAY_POST_HISTORY:   /* display messages posted by user */
  954                 XFACE_SUPPRESS();
  955                 if (user_posted_messages())
  956                     draw_page(group->name, 0);
  957                 XFACE_SHOW();
  958                 break;
  959 
  960             case MARK_ARTICLE_UNREAD:   /* mark article as unread(to return) */
  961                 art_mark(group, &arts[this_resp], ART_WILL_RETURN);
  962                 info_message(_(txt_marked_as_unread), _(txt_article_upper));
  963                 break;
  964 
  965             case PAGE_SKIP_INCLUDED_TEXT:   /* skip included text */
  966                 for (i = j = curr_line; i < artlines; i++) {
  967                     if (artline[i].flags & (C_QUOTE1 | C_QUOTE2 | C_QUOTE3)) {
  968                         j = i;
  969                         break;
  970                     }
  971                 }
  972 
  973                 for (; j < artlines; j++) {
  974                     if (!(artline[j].flags & (C_QUOTE1 | C_QUOTE2 | C_QUOTE3)))
  975                         break;
  976                 }
  977 
  978                 if (j != curr_line) {
  979                     curr_line = j;
  980                     draw_page(group->name, 0);
  981                 }
  982                 break;
  983 
  984             case GLOBAL_TOGGLE_INFO_LAST_LINE: /* this is _not_ correct, we do not toggle status here */
  985                 info_message("%s", arts[this_resp].subject);
  986                 break;
  987 
  988             case PAGE_TOGGLE_HIGHLIGHTING:
  989                 word_highlight = bool_not(word_highlight);
  990                 draw_page(group->name, 0);
  991                 info_message(_(txt_toggled_high), txt_onoff[word_highlight != FALSE ? 1 : 0]);
  992                 break;
  993 
  994             case PAGE_VIEW_ATTACHMENTS:
  995                 XFACE_SUPPRESS();
  996                 hide_uue_tmp = hide_uue;
  997                 hide_uue = UUE_NO;
  998                 resize_article(TRUE, &pgart);
  999                 attachment_page(&pgart);
 1000                 hide_uue = hide_uue_tmp;
 1001                 resize_article(TRUE, &pgart);
 1002                 draw_page(group->name, 0);
 1003                 XFACE_SHOW();
 1004                 break;
 1005 
 1006             case PAGE_VIEW_URL:
 1007                 if (!show_raw_article) { /* cooked mode? */
 1008                     t_bool success;
 1009 
 1010                     XFACE_SUPPRESS();
 1011                     resize_article(FALSE, &pgart); /* unbreak long lines */
 1012                     success = url_page();
 1013                     resize_article(TRUE, &pgart); /* rebreak long lines */
 1014                     draw_page(group->name, 0);
 1015                     if (!success)
 1016                         info_message(_(txt_url_done));
 1017                     XFACE_SHOW();
 1018                 }
 1019                 break;
 1020 
 1021             default:
 1022                 info_message(_(txt_bad_command), printascii(key, func_to_key(GLOBAL_HELP, page_keys)));
 1023         }
 1024     }
 1025     /* NOTREACHED */
 1026     return GRP_ARTUNAVAIL;
 1027 }
 1028 
 1029 
 1030 static void
 1031 print_message_page(
 1032     FILE *file,
 1033     t_lineinfo *messageline,
 1034     size_t messagelines,
 1035     size_t base_line,
 1036     size_t begin,
 1037     size_t end,
 1038     int help_level)
 1039 {
 1040     char *line;
 1041     char *p;
 1042     int bytes;
 1043     size_t i = begin;
 1044     t_lineinfo *curr;
 1045 
 1046     for (; i < end; i++) {
 1047         if (base_line + i >= messagelines)      /* ran out of message */
 1048             break;
 1049 
 1050         curr = &messageline[base_line + i];
 1051 
 1052         if (fseek(file, curr->offset, SEEK_SET) != 0)
 1053             break;
 1054 
 1055         if ((line = tin_fgets(file, FALSE)) == NULL)
 1056             break;  /* ran out of message */
 1057 
 1058         if ((help_level == INFO_PAGER) && (strwidth(line) >= cCOLS - 1))
 1059 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
 1060             {
 1061                 char *tmp, *f, *t;
 1062 
 1063                 f = tmp = strunc(line, cCOLS - 1);
 1064                 t = line;
 1065                 while (*f)
 1066                     *t++ = *f++;
 1067                 *t = '\0';
 1068                 free(tmp);
 1069             }
 1070 #else
 1071             line[cCOLS - 1] = '\0';
 1072 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
 1073 
 1074         /*
 1075          * use the offsets gained while doing line wrapping to
 1076          * determine the correct position to truncate the line
 1077          */
 1078         if ((help_level != INFO_PAGER) && (base_line + i < messagelines - 1)) { /* not last line of message */
 1079             bytes = (curr + 1)->offset - curr->offset;
 1080             line[bytes] = '\0';
 1081         }
 1082 
 1083         /*
 1084          * rotN encoding on body and sig data only
 1085          */
 1086         if ((rotate != 0) && ((curr->flags & (C_BODY | C_SIG)) || show_raw_article)) {
 1087             for (p = line; *p; p++) {
 1088                 if (*p >= 'A' && *p <= 'Z')
 1089                     *p = (*p - 'A' + rotate) % 26 + 'A';
 1090                 else if (*p >= 'a' && *p <= 'z')
 1091                     *p = (*p - 'a' + rotate) % 26 + 'a';
 1092             }
 1093         }
 1094 
 1095         strip_line(line);
 1096 
 1097 #ifndef USE_CURSES
 1098         snprintf(screen[i + scroll_region_top].col, cCOLS, "%s" cCRLF, line);
 1099 #endif /* !USE_CURSES */
 1100 
 1101         MoveCursor(i + scroll_region_top, 0);
 1102         draw_pager_line(line, curr->flags, show_raw_article);
 1103 
 1104         /*
 1105          * Highlight URL's and mail addresses
 1106          */
 1107         if (tinrc.url_highlight) {
 1108             if (curr->flags & C_URL)
 1109 #ifdef HAVE_COLOR
 1110                 highlight_regexes(i + scroll_region_top, &url_regex, use_color ? tinrc.col_urls : -1);
 1111 #else
 1112                 highlight_regexes(i + scroll_region_top, &url_regex, -1);
 1113 #endif /* HAVE_COLOR */
 1114 
 1115             if (curr->flags & C_MAIL)
 1116 #ifdef HAVE_COLOR
 1117                 highlight_regexes(i + scroll_region_top, &mail_regex, use_color ? tinrc.col_urls : -1);
 1118 #else
 1119                 highlight_regexes(i + scroll_region_top, &mail_regex, -1);
 1120 #endif /* HAVE_COLOR */
 1121 
 1122             if (curr->flags & C_NEWS)
 1123 #ifdef HAVE_COLOR
 1124                 highlight_regexes(i + scroll_region_top, &news_regex, use_color ? tinrc.col_urls : -1);
 1125 #else
 1126                 highlight_regexes(i + scroll_region_top, &news_regex, -1);
 1127 #endif /* HAVE_COLOR */
 1128         }
 1129 
 1130         /*
 1131          * Highlight /slashes/, *stars*, _underscores_ and -strokes-
 1132          */
 1133         if (word_highlight && (curr->flags & C_BODY) && !(curr->flags & C_CTRLL)) {
 1134 #ifdef HAVE_COLOR
 1135             highlight_regexes(i + scroll_region_top, &slashes_regex, use_color ? tinrc.col_markslash : tinrc.mono_markslash);
 1136             highlight_regexes(i + scroll_region_top, &stars_regex, use_color ? tinrc.col_markstar : tinrc.mono_markstar);
 1137             highlight_regexes(i + scroll_region_top, &underscores_regex, use_color ? tinrc.col_markdash : tinrc.mono_markdash);
 1138             highlight_regexes(i + scroll_region_top, &strokes_regex, use_color ? tinrc.col_markstroke : tinrc.mono_markstroke);
 1139 #else
 1140             highlight_regexes(i + scroll_region_top, &slashes_regex, tinrc.mono_markslash);
 1141             highlight_regexes(i + scroll_region_top, &stars_regex, tinrc.mono_markstar);
 1142             highlight_regexes(i + scroll_region_top, &underscores_regex, tinrc.mono_markdash);
 1143             highlight_regexes(i + scroll_region_top, &strokes_regex, tinrc.mono_markstroke);
 1144 #endif /* HAVE_COLOR */
 1145         }
 1146 
 1147         /* Blank the screen after a ^L (only occurs when showing cooked) */
 1148         if (!reveal_ctrl_l && (curr->flags & C_CTRLL) && (int) (base_line + i) > reveal_ctrl_l_lines) {
 1149             CleartoEOS();
 1150             break;
 1151         }
 1152     }
 1153 
 1154 #ifdef HAVE_COLOR
 1155     fcol(tinrc.col_text);
 1156 #endif /* HAVE_COLOR */
 1157 
 1158     show_mini_help(help_level);
 1159 }
 1160 
 1161 
 1162 /*
 1163  * Redraw the current page, curr_line will be the first line displayed
 1164  * Everything that calls draw_page() just sets curr_line, this function must
 1165  * ensure it is set to something sane
 1166  * If part is !=0, then only draw the first (-ve) or last (+ve) few lines
 1167  */
 1168 void
 1169 draw_page(
 1170     const char *group,
 1171     int part)
 1172 {
 1173     int start, end; /* 1st, last line to draw */
 1174 
 1175     signal_context = cPage;
 1176 
 1177     /*
 1178      * Can't do partial draw if term can't scroll properly
 1179      */
 1180     if (part && !have_linescroll)
 1181         part = 0;
 1182 
 1183     /*
 1184      * Ensure curr_line is in bounds
 1185      */
 1186     if (curr_line < 0)
 1187         curr_line = 0;          /* Oops - off the top */
 1188     else {
 1189         if (curr_line > artlines)
 1190             curr_line = artlines;   /* Oops - off the end */
 1191     }
 1192 
 1193     search_line = curr_line;    /* Reset search to start from top of display */
 1194 
 1195     scroll_region_top = PAGE_HEADER;
 1196 
 1197     /* Down-scroll, only redraw bottom 'part' lines of screen */
 1198     if ((start = (part > 0) ? ARTLINES - part : 0) < 0)
 1199         start = 0;
 1200 
 1201     /* Up-scroll, only redraw the top 'part' lines of screen */
 1202     if ((end = (part < 0) ? -part : ARTLINES) > ARTLINES)
 1203         end = ARTLINES;
 1204 
 1205     /*
 1206      * ncurses doesn't clear the scroll area when you scroll by more than the
 1207      * window size - force full redraw
 1208      */
 1209     if ((end - start >= ARTLINES) || (part == 0)) {
 1210         ClearScreen();
 1211         draw_page_header(group);
 1212     } else
 1213         MoveCursor(0, 0);
 1214 
 1215     print_message_page(note_fp, artline, artlines, curr_line, start, end, PAGE_LEVEL);
 1216 
 1217     /*
 1218      * Print an appropriate footer
 1219      */
 1220     if (curr_line + ARTLINES >= artlines) {
 1221         char buf[LEN], *buf2;
 1222         int len;
 1223 
 1224         STRCPY(buf, (arts[this_resp].thread != -1) ? _(txt_next_resp) : _(txt_last_resp));
 1225         buf2 = strunc(buf, cCOLS - 1);
 1226         len = strwidth(buf2);
 1227         clear_message();
 1228         MoveCursor(cLINES, cCOLS - len - (1 + BLANK_PAGE_COLS));
 1229 #ifdef HAVE_COLOR
 1230         fcol(tinrc.col_normal);
 1231 #endif /* HAVE_COLOR */
 1232         StartInverse();
 1233         my_fputs(buf2, stdout);
 1234         EndInverse();
 1235         my_flush();
 1236         free(buf2);
 1237     } else
 1238         draw_percent_mark(curr_line + ARTLINES, artlines);
 1239 
 1240 #ifdef XFACE_ABLE
 1241     if (tinrc.use_slrnface && !show_raw_article)
 1242         slrnface_display_xface(note_h->xface);
 1243 #endif /* XFACE_ABLE */
 1244 
 1245     stow_cursor();
 1246 }
 1247 
 1248 
 1249 /*
 1250  * Start external metamail program
 1251  */
 1252 static void
 1253 invoke_metamail(
 1254     FILE *fp)
 1255 {
 1256     char *ptr;
 1257     long offset;
 1258 #ifndef DONT_HAVE_PIPING
 1259     FILE *mime_fp;
 1260     char buf[LEN];
 1261 #endif /* !DONT_HAVE_PIPING */
 1262 
 1263     ptr = tinrc.metamail_prog;
 1264     if ((*ptr == '\0') || (!strcmp(ptr, INTERNAL_CMD)) || (getenv("NOMETAMAIL") != NULL))
 1265         return;
 1266 
 1267     if ((offset = ftell(fp)) == -1) {
 1268         perror_message(_(txt_command_failed), ptr);
 1269         return;
 1270     }
 1271     rewind(fp);
 1272 
 1273     EndWin();
 1274     Raw(FALSE);
 1275 
 1276     /* TODO: add DONT_HAVE_PIPING fallback code */
 1277 #ifndef DONT_HAVE_PIPING
 1278     if ((mime_fp = popen(ptr, "w"))) {
 1279         while (fgets(buf, (int) sizeof(buf), fp) != NULL)
 1280             fputs(buf, mime_fp);
 1281 
 1282         fflush(mime_fp);
 1283         pclose(mime_fp);
 1284     } else
 1285 #endif /* !DONT_HAVE_PIPING */
 1286         perror_message(_(txt_command_failed), ptr);
 1287 
 1288 #ifdef USE_CURSES
 1289     Raw(TRUE);
 1290     InitWin();
 1291 #endif /* USE_CURSES */
 1292     prompt_continue();
 1293 #ifndef USE_CURSES
 1294     Raw(TRUE);
 1295     InitWin();
 1296 #endif /* !USE_CURSES */
 1297 
 1298     /* This is needed if we are viewing the raw art */
 1299     fseek(fp, offset, SEEK_SET);    /* goto old position */
 1300 }
 1301 
 1302 
 1303 /*
 1304  * PAGE_HEADER defines the size in lines of this header
 1305  */
 1306 static void
 1307 draw_page_header(
 1308     const char *group)
 1309 {
 1310     char *buf, *tmp;
 1311     int i;
 1312     int whichresp, x_resp;
 1313     int len, right_len, center_pos, cur_pos;
 1314     size_t line_len;
 1315 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
 1316     wchar_t *fmt_resp, *fmt_thread, *wtmp, *wtmp2, *wbuf;
 1317 #else
 1318     char *tmp2;
 1319 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
 1320 
 1321     whichresp = which_response(this_resp);
 1322     x_resp = num_of_responses(which_thread(this_resp));
 1323 
 1324     line_len = LEN + 1;
 1325     buf = my_malloc(line_len);
 1326 
 1327     if (!my_strftime(buf, line_len, curr_group->attribute->date_format, localtime(&arts[this_resp].date))) {
 1328         strncpy(buf, BlankIfNull(note_h->date), line_len);
 1329         buf[line_len - 1] = '\0';
 1330     }
 1331 
 1332 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
 1333     /* convert to wide-char format strings */
 1334     fmt_thread = char2wchar_t(_(txt_thread_x_of_n));
 1335     fmt_resp = char2wchar_t(_(txt_art_x_of_n));
 1336 
 1337     /*
 1338      * determine the needed space for the text at the right hand margin
 1339      * the formatting info (%4s) needs 3 positions but we need 4 positions
 1340      * on the screen for each counter.
 1341      */
 1342     if (fmt_thread && fmt_resp)
 1343         right_len = MAX((wcswidth(fmt_thread, wcslen(fmt_thread)) - 6 + 8), (wcswidth(fmt_resp, wcslen(fmt_resp)) - 6 + 8));
 1344     else if (fmt_thread)
 1345         right_len = wcswidth(fmt_thread, wcslen(fmt_thread)) - 6 + 8;
 1346     else if (fmt_resp)
 1347         right_len = wcswidth(fmt_resp, wcslen(fmt_resp)) - 6 + 8;
 1348     else
 1349         right_len = 0;
 1350     FreeIfNeeded(fmt_thread);
 1351     FreeIfNeeded(fmt_resp);
 1352 
 1353     /*
 1354      * limit right_len to cCOLS / 3
 1355      */
 1356     if (right_len > cCOLS / 3 + 1)
 1357         right_len = cCOLS / 3 + 1;
 1358 
 1359     /*
 1360      * first line
 1361      */
 1362     cur_pos = 0;
 1363 
 1364 #   ifdef HAVE_COLOR
 1365     fcol(tinrc.col_head);
 1366 #   endif /* HAVE_COLOR */
 1367 
 1368     /* date */
 1369     if ((wtmp = char2wchar_t(buf)) != NULL) {
 1370         my_fputws(wtmp, stdout);
 1371         cur_pos += wcswidth(wtmp, wcslen(wtmp));
 1372         free(wtmp);
 1373     }
 1374 
 1375     /*
 1376      * determine max len for centered group name
 1377      * allow one space before and after group name
 1378      */
 1379     len = cCOLS - 2 * MAX(cur_pos, right_len) - 3;
 1380 
 1381     /* group name */
 1382     if ((wtmp = char2wchar_t(group)) != NULL) {
 1383         /* wconvert_to_printable(wtmp, FALSE); */
 1384         if (tinrc.abbreviate_groupname)
 1385             wtmp2 = abbr_wcsgroupname(wtmp, len);
 1386         else
 1387             wtmp2 = wstrunc(wtmp, len);
 1388 
 1389         if ((i = wcswidth(wtmp2, wcslen(wtmp2))) < len)
 1390             len = i;
 1391 
 1392         center_pos = (cCOLS - len) / 2;
 1393 
 1394         /* pad out to left */
 1395         for (; cur_pos < center_pos; cur_pos++)
 1396             my_fputc(' ', stdout);
 1397 
 1398         my_fputws(wtmp2, stdout);
 1399         cur_pos += wcswidth(wtmp2, wcslen(wtmp2));
 1400         free(wtmp2);
 1401         free(wtmp);
 1402     }
 1403 
 1404     /* pad out to right */
 1405     for (; cur_pos < cCOLS - right_len - 1; cur_pos++)
 1406         my_fputc(' ', stdout);
 1407 
 1408     /* thread info */
 1409     /* can't eval tin_ltoa() more than once in a statement due to statics */
 1410     strcpy(buf, tin_ltoa(which_thread(this_resp) + 1, 4));
 1411     tmp = strunc(_(txt_thread_x_of_n), cCOLS / 3 - 1);
 1412     my_printf(tmp, buf, tin_ltoa(grpmenu.max, 4));
 1413     free(tmp);
 1414 
 1415     my_fputs(cCRLF, stdout);
 1416 
 1417 #   if 0
 1418     /* display a ruler for layout checking purposes */
 1419     my_fputs("....|....3....|....2....|....1....|....0....|....1....|....2....|....3....|....\n", stdout);
 1420 #   endif /* 0 */
 1421 
 1422     /*
 1423      * second line
 1424      */
 1425     cur_pos = 0;
 1426 
 1427 #   ifdef HAVE_COLOR
 1428     fcol(tinrc.col_head);
 1429 #   endif /* HAVE_COLOR */
 1430 
 1431     /* line count */
 1432     if (arts[this_resp].line_count < 0)
 1433         strcpy(buf, "?");
 1434     else
 1435         snprintf(buf, line_len, "%-4d", arts[this_resp].line_count);
 1436 
 1437     {
 1438         wchar_t *fmt;
 1439 
 1440         if ((wtmp = char2wchar_t(_(txt_lines))) != NULL) {
 1441             int tex_space = pgart.tex2iso ? 5 : 0;
 1442 
 1443             fmt = wstrunc(wtmp, cCOLS / 3 - 1 - tex_space);
 1444             wtmp = my_realloc(wtmp, sizeof(wchar_t) * line_len);
 1445             swprintf(wtmp, line_len, fmt, buf);
 1446             my_fputws(wtmp, stdout);
 1447             cur_pos += wcswidth(wtmp, wcslen(wtmp));
 1448             free(fmt);
 1449             free(wtmp);
 1450         }
 1451     }
 1452 
 1453 #   ifdef HAVE_COLOR
 1454     fcol(tinrc.col_subject);
 1455 #   endif /* HAVE_COLOR */
 1456 
 1457     /* tex2iso */
 1458     if (pgart.tex2iso) {
 1459         if ((wtmp = char2wchar_t(_(txt_tex))) != NULL) {
 1460             wtmp2 = wstrunc(wtmp, 5);
 1461             my_fputws(wtmp2, stdout);
 1462             cur_pos += wcswidth(wtmp2, wcslen(wtmp2));
 1463             free(wtmp);
 1464             free(wtmp2);
 1465         }
 1466     }
 1467 
 1468     /* subject */
 1469     /*
 1470      * TODO: why do we fall back to arts[this_resp].subject if !note_h->subj?
 1471      *       if !note_h->subj then the article just has no subject, no matter
 1472      *       what the overview says.
 1473      */
 1474     strncpy(buf, (note_h->subj ? note_h->subj : arts[this_resp].subject), line_len);
 1475     buf[line_len - 1] = '\0';
 1476     if ((wtmp = char2wchar_t(buf)) != NULL) {
 1477         wbuf = wexpand_tab(wtmp, tabwidth);
 1478         wtmp2 = wstrunc(wbuf, cCOLS - 2 * right_len - 3);
 1479         center_pos = (cCOLS - wcswidth(wtmp2, wcslen(wtmp2))) / 2;
 1480 
 1481         /* pad out to left */
 1482         for (; cur_pos < center_pos; cur_pos++)
 1483             my_fputc(' ', stdout);
 1484 
 1485         StartInverse();
 1486         my_fputws(wtmp2, stdout);
 1487         EndInverse();
 1488         cur_pos += wcswidth(wtmp2, wcslen(wtmp2));
 1489         free(wtmp2);
 1490         free(wtmp);
 1491         free(wbuf);
 1492     }
 1493 
 1494 #   ifdef HAVE_COLOR
 1495     fcol(tinrc.col_response);
 1496 #   endif /* HAVE_COLOR */
 1497 
 1498     /* pad out to right */
 1499     for (; cur_pos < cCOLS - right_len - 1; cur_pos++)
 1500         my_fputc(' ', stdout);
 1501 
 1502     if (whichresp) {
 1503         tmp = strunc(_(txt_art_x_of_n), cCOLS / 3 - 1);
 1504         my_printf(tmp, whichresp + 1, x_resp + 1);
 1505         free(tmp);
 1506     } else {
 1507         /* TODO: ngettext */
 1508         if (!x_resp) {
 1509             tmp = strunc(_(txt_no_responses), cCOLS / 3 - 1);
 1510             my_printf("%s", tmp);
 1511         } else if (x_resp == 1) {
 1512             tmp = strunc(_(txt_1_resp), cCOLS / 3 - 1);
 1513             my_printf("%s", tmp);
 1514         } else {
 1515             tmp = strunc(_(txt_x_resp), cCOLS / 3 - 1);
 1516             my_printf(tmp, x_resp);
 1517         }
 1518         free(tmp);
 1519     }
 1520     my_fputs(cCRLF, stdout);
 1521 
 1522     /*
 1523      * third line
 1524      */
 1525     cur_pos = 0;
 1526 
 1527 #   ifdef HAVE_COLOR
 1528     fcol(tinrc.col_from);
 1529 #   endif /* HAVE_COLOR */
 1530     /* from */
 1531     /*
 1532      * TODO: don't use arts[this_resp].name/arts[this_resp].from
 1533      *       split up note_h->from and use that instead as it might
 1534      *       be different _if_ the overviews are broken
 1535      */
 1536     {
 1537         char *p = idna_decode(arts[this_resp].from);
 1538 
 1539         if (arts[this_resp].name)
 1540             snprintf(buf, line_len, "%s <%s>", arts[this_resp].name, p);
 1541         else {
 1542             strncpy(buf, p, line_len);
 1543             buf[line_len - 1] = '\0';
 1544         }
 1545         free(p);
 1546     }
 1547 
 1548     if ((wtmp = char2wchar_t(buf)) != NULL) {
 1549         wtmp2 = wstrunc(wtmp, cCOLS - 1);
 1550         my_fputws(wtmp2, stdout);
 1551         cur_pos += wcswidth(wtmp2, wcslen(wtmp2));
 1552         free(wtmp2);
 1553         free(wtmp);
 1554     }
 1555 
 1556     /*
 1557      * Organization
 1558      *
 1559      * TODO: IDNA decoding, see also comment in
 1560      *       cook.c:cook_article()
 1561      */
 1562     if (note_h->org) {
 1563         snprintf(buf, line_len, _(txt_at_s), note_h->org);
 1564 
 1565         if ((wtmp = char2wchar_t(buf)) != NULL) {
 1566             wbuf = wexpand_tab(wtmp, tabwidth);
 1567             wtmp2 = wstrunc(wbuf, cCOLS - cur_pos - 1);
 1568 
 1569             i = cCOLS - wcswidth(wtmp2, wcslen(wtmp2)) - 1;
 1570             for (; cur_pos < i; cur_pos++)
 1571                 my_fputc(' ', stdout);
 1572 
 1573             my_fputws(wtmp2, stdout);
 1574             free(wtmp2);
 1575             free(wtmp);
 1576             free(wbuf);
 1577         }
 1578     }
 1579 
 1580     my_fputs(cCRLF, stdout);
 1581     my_fputs(cCRLF, stdout);
 1582 
 1583 #else /* !MULTIBYTE_ABLE || NO_LOCALE */
 1584     /*
 1585      * determine the needed space for the text at the right hand margin
 1586      * the formatting info (%4s) needs 3 positions but we need 4 positions
 1587      * on the screen for each counter
 1588      */
 1589     right_len = MAX((strlen(_(txt_thread_x_of_n)) - 6 + 8), (strlen(_(txt_art_x_of_n)) - 6 + 8));
 1590 
 1591     /*
 1592      * first line
 1593      */
 1594     cur_pos = 0;
 1595 
 1596 #   ifdef HAVE_COLOR
 1597     fcol(tinrc.col_head);
 1598 #   endif /* HAVE_COLOR */
 1599 
 1600     /* date */
 1601     my_fputs(buf, stdout);
 1602     cur_pos += strlen(buf);
 1603 
 1604     /*
 1605      * determine max len for centered group name
 1606      * allow one space before and after group name
 1607      */
 1608     len = cCOLS - 2 * MAX(cur_pos, right_len) - 3;
 1609 
 1610     /* group name */
 1611     if (tinrc.abbreviate_groupname)
 1612         tmp = abbr_groupname(group, len);
 1613     else
 1614         tmp = strunc(group, len);
 1615 
 1616     if ((i = strlen(tmp)) < len)
 1617         len = i;
 1618 
 1619     center_pos = (cCOLS - len) / 2;
 1620 
 1621     /* pad out to left */
 1622     for (; cur_pos < center_pos; cur_pos++)
 1623         my_fputc(' ', stdout);
 1624 
 1625     my_fputs(tmp, stdout);
 1626     cur_pos += strlen(tmp);
 1627     free(tmp);
 1628 
 1629     /* pad out to right */
 1630     for (; cur_pos < cCOLS - right_len - 1; cur_pos++)
 1631         my_fputc(' ', stdout);
 1632 
 1633     /* thread info */
 1634     /* can't eval tin_ltoa() more than once in a statement due to statics */
 1635     strcpy(buf, tin_ltoa(which_thread(this_resp) + 1, 4));
 1636     my_printf(_(txt_thread_x_of_n), buf, tin_ltoa(grpmenu.max, 4));
 1637 
 1638     my_fputs(cCRLF, stdout);
 1639 
 1640 #   if 0
 1641     /* display a ruler for layout checking purposes */
 1642     my_fputs("....|....3....|....2....|....1....|....0....|....1....|....2....|....3....|....\n", stdout);
 1643 #   endif /* 0 */
 1644 
 1645     /*
 1646      * second line
 1647      */
 1648     cur_pos = 0;
 1649 
 1650 #   ifdef HAVE_COLOR
 1651     fcol(tinrc.col_head);
 1652 #   endif /* HAVE_COLOR */
 1653 
 1654     /* line count */
 1655     /* an accurate line count will appear in the footer anymay */
 1656     if (arts[this_resp].line_count < 0)
 1657         strcpy(buf, "?");
 1658     else
 1659         snprintf(buf, line_len, "%-4d", arts[this_resp].line_count);
 1660 
 1661     tmp = my_malloc(line_len);
 1662     snprintf(tmp, line_len, _(txt_lines), buf);
 1663     my_fputs(tmp, stdout);
 1664     cur_pos += strlen(tmp);
 1665     free(tmp);
 1666 
 1667 #   ifdef HAVE_COLOR
 1668     fcol(tinrc.col_subject);
 1669 #   endif /* HAVE_COLOR */
 1670 
 1671     /* tex2iso */
 1672     if (pgart.tex2iso) {
 1673         my_fputs(_(txt_tex), stdout);
 1674         cur_pos += strlen(_(txt_tex));
 1675     }
 1676 
 1677     /* subject */
 1678     /*
 1679      * TODO: why do we fall back to arts[this_resp].subject if !note_h->subj?
 1680      *       if !note_h->subj then the article just has no subject, no matter
 1681      *       what the overview says.
 1682      */
 1683     strncpy(buf, (note_h->subj ? note_h->subj : arts[this_resp].subject), line_len);
 1684     buf[line_len - 1] = '\0';
 1685 
 1686     tmp2 = expand_tab(buf, tabwidth);
 1687     tmp = strunc(tmp2, cCOLS - 2 * right_len - 3);
 1688 
 1689     center_pos = (cCOLS - strlen(tmp)) / 2;
 1690 
 1691     /* pad out to left */
 1692     for (; cur_pos < center_pos; cur_pos++)
 1693         my_fputc(' ', stdout);
 1694 
 1695     StartInverse();
 1696     my_fputs(tmp, stdout);
 1697     EndInverse();
 1698     cur_pos += strlen(tmp);
 1699     free(tmp);
 1700     free(tmp2);
 1701 
 1702 #   ifdef HAVE_COLOR
 1703     fcol(tinrc.col_response);
 1704 #   endif /* HAVE_COLOR */
 1705 
 1706     /* pad out to right */
 1707     for (; cur_pos < cCOLS - right_len - 1; cur_pos++)
 1708         my_fputc(' ', stdout);
 1709 
 1710     if (whichresp)
 1711         my_printf(_(txt_art_x_of_n), whichresp + 1, x_resp + 1);
 1712     else {
 1713         /* TODO: ngettext */
 1714         if (!x_resp)
 1715             my_printf("%s", _(txt_no_responses));
 1716         else if (x_resp == 1)
 1717             my_printf("%s", _(txt_1_resp));
 1718         else
 1719             my_printf(_(txt_x_resp), x_resp);
 1720     }
 1721     my_fputs(cCRLF, stdout);
 1722 
 1723     /*
 1724      * third line
 1725      */
 1726     cur_pos = 0;
 1727 
 1728 #ifdef HAVE_COLOR
 1729     fcol(tinrc.col_from);
 1730 #endif /* HAVE_COLOR */
 1731     /* from */
 1732     /*
 1733      * TODO: don't use arts[this_resp].name/arts[this_resp].from
 1734      *       split up note_h->from and use that instead as it might
 1735      *       be different _if_ the overviews are broken
 1736      */
 1737     if (arts[this_resp].name)
 1738         snprintf(buf, line_len, "%s <%s>", arts[this_resp].name, arts[this_resp].from);
 1739     else {
 1740         strncpy(buf, arts[this_resp].from, line_len);
 1741         buf[line_len - 1] = '\0';
 1742     }
 1743 
 1744     tmp = strunc(buf, cCOLS - 1);
 1745     my_fputs(tmp, stdout);
 1746     cur_pos += strlen(tmp);
 1747     free(tmp);
 1748 
 1749     if (note_h->org && cCOLS - cur_pos - 1 >= (int) strlen(_(txt_at_s)) - 2 + 3) {
 1750         /* we have enough space to print at least " at ..." */
 1751         snprintf(buf, line_len, _(txt_at_s), note_h->org);
 1752 
 1753         tmp2 = expand_tab(buf, tabwidth);
 1754         tmp = strunc(tmp2, cCOLS - cur_pos - 1);
 1755         len = cCOLS - (int) strlen(tmp) - 1;
 1756         for (; cur_pos < len; cur_pos++)
 1757             my_fputc(' ', stdout);
 1758         my_fputs(tmp, stdout);
 1759         free(tmp);
 1760         free(tmp2);
 1761     }
 1762 
 1763     my_fputs(cCRLF, stdout);
 1764     my_fputs(cCRLF, stdout);
 1765 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
 1766     free(buf);
 1767 
 1768 #ifdef HAVE_COLOR
 1769     fcol(tinrc.col_normal);
 1770 #endif /* HAVE_COLOR */
 1771 }
 1772 
 1773 
 1774 /*
 1775  * Change the pager article context to arts[new_respnum]
 1776  * Return GRP_ARTUNAVAIL if article could not be opened
 1777  * or GRP_ARTABORT if load of article was interrupted
 1778  * or 0 on success
 1779  */
 1780 static int
 1781 load_article(
 1782     int new_respnum,
 1783     struct t_group *group)
 1784 {
 1785     static t_bool art_closed = FALSE;
 1786 
 1787 #ifdef DEBUG
 1788     if (debug & DEBUG_MISC)
 1789         fprintf(stderr, "load_art %s(new=%d, curr=%d)\n", (new_respnum == this_resp && !art_closed) ? "ALREADY OPEN!" : "", new_respnum, this_resp);
 1790 #endif /* DEBUG */
 1791 
 1792     if (new_respnum != this_resp || art_closed) {
 1793         int ret;
 1794 
 1795         art_close(&pgart);          /* close previously opened art in pager */
 1796         ret = art_open(TRUE, &arts[new_respnum], group, &pgart, TRUE, _(txt_reading_article));
 1797 
 1798         switch (ret) {
 1799             case ART_UNAVAILABLE:
 1800                 art_mark(group, &arts[new_respnum], ART_READ);
 1801                 /* prevent retagging as unread in unfilter_articles() */
 1802                 if (arts[new_respnum].killed == ART_KILLED_UNREAD)
 1803                     arts[new_respnum].killed = ART_KILLED;
 1804                 art_closed = TRUE;
 1805                 wait_message(1, _(txt_art_unavailable));
 1806                 return GRP_ARTUNAVAIL;
 1807 
 1808             case ART_ABORT:
 1809                 art_close(&pgart);
 1810                 art_closed = TRUE;
 1811                 return GRP_ARTABORT;    /* special retcode to stop redrawing screen */
 1812 
 1813             default:                    /* Normal case */
 1814 #if 0           /* Very useful debugging tool */
 1815                 if (prompt_yn("Fake art unavailable? ", FALSE) == 1) {
 1816                     art_close(&pgart);
 1817                     art_mark(group, &arts[new_respnum], ART_READ);
 1818                     art_closed = TRUE;
 1819                     return GRP_ARTUNAVAIL;
 1820                 }
 1821 #endif /* 0 */
 1822                 if (art_closed)
 1823                     art_closed = FALSE;
 1824                 if (new_respnum != this_resp) {
 1825                     /*
 1826                      * Remember current & previous articles for '-' command
 1827                      */
 1828                     last_resp = this_resp;
 1829                     this_resp = new_respnum;        /* Set new art globally */
 1830                 }
 1831                 break;
 1832         }
 1833     } else if (show_all_headers) {
 1834         /*
 1835          * article is already opened with show_all_headers ON
 1836          * -> re-cook it
 1837          */
 1838         show_all_headers = FALSE;
 1839         resize_article(TRUE, &pgart);
 1840     }
 1841 
 1842     art_mark(group, &arts[this_resp], ART_READ);
 1843 
 1844     /*
 1845      * Change status if art was unread before killing to
 1846      * prevent retagging as unread in unfilter_articles()
 1847      */
 1848     if (arts[this_resp].killed == ART_KILLED_UNREAD)
 1849         arts[this_resp].killed = ART_KILLED;
 1850 
 1851     if (pgart.cooked == NULL) { /* harmony corruption */
 1852         wait_message(1, _(txt_art_unavailable));
 1853         return GRP_ARTUNAVAIL;
 1854     }
 1855 
 1856     /*
 1857      * Setup to start viewing cooked version
 1858      */
 1859     show_raw_article = FALSE;
 1860     show_all_headers = FALSE;
 1861     curr_line = 0;
 1862     note_fp = pgart.cooked;
 1863     artline = pgart.cookl;
 1864     artlines = pgart.cooked_lines;
 1865     search_line = 0;
 1866     /*
 1867      * Reset offsets only if not invoked during 'body search' (srch_lineno != -1)
 1868      * otherwise the found string will not be highlighted
 1869      */
 1870     if (srch_lineno == -1)
 1871         reset_srch_offsets();
 1872     rotate = 0;         /* normal mode, not rot13 */
 1873     reveal_ctrl_l = FALSE;
 1874     reveal_ctrl_l_lines = -1;   /* all ^L's active */
 1875     hide_uue = tinrc.hide_uue;
 1876 
 1877     draw_page(group->name, 0);
 1878 
 1879     /*
 1880      * Automatically invoke attachment viewing if requested
 1881      */
 1882     if (!note_h->mime || IS_PLAINTEXT(note_h->ext))     /* Text only article */
 1883         return 0;
 1884 
 1885     if (*tinrc.metamail_prog == '\0' || getenv("NOMETAMAIL") != NULL)   /* Viewer turned off */
 1886         return 0;
 1887 
 1888     if (group->attribute->ask_for_metamail) {
 1889         if (prompt_yn(_(txt_use_mime), TRUE) != 1)
 1890             return 0;
 1891     }
 1892 
 1893     XFACE_SUPPRESS();
 1894     if (strcmp(tinrc.metamail_prog, INTERNAL_CMD) == 0) /* Use internal viewer */
 1895         decode_save_mime(&pgart, FALSE);
 1896     else
 1897         invoke_metamail(pgart.raw);
 1898     XFACE_SHOW();
 1899     return 0;
 1900 }
 1901 
 1902 
 1903 static int
 1904 prompt_response(
 1905     int ch,
 1906     int curr_respnum)
 1907 {
 1908     int i, num;
 1909 
 1910     clear_message();
 1911 
 1912     if ((num = (prompt_num(ch, _(txt_select_art)) - 1)) == -1) {
 1913         clear_message();
 1914         return -1;
 1915     }
 1916 
 1917     if ((i = which_thread(curr_respnum)) >= 0)
 1918         return find_response(i, num);
 1919     else
 1920         return -1;
 1921 }
 1922 
 1923 
 1924 /*
 1925  * Reposition within message as needed, highlight searched string
 1926  * This is tied quite closely to the information stored by
 1927  * get_search_vectors()
 1928  */
 1929 static void
 1930 process_search(
 1931     int *lcurr_line,
 1932     size_t message_lines,
 1933     size_t screen_lines,
 1934     int help_level)
 1935 {
 1936     int i, start, end;
 1937 
 1938     if ((i = get_search_vectors(&start, &end)) == -1)
 1939         return;
 1940 
 1941     /*
 1942      * Is matching line off the current view?
 1943      * Reposition within article if needed, try to get matched line
 1944      * in the middle of the screen
 1945      */
 1946     if (i < *lcurr_line || i >= (int) (*lcurr_line + screen_lines)) {
 1947         *lcurr_line = i - (screen_lines / 2);
 1948         if (*lcurr_line + screen_lines > message_lines) /* off the end */
 1949             *lcurr_line = message_lines - screen_lines;
 1950         /* else pos. is just fine */
 1951     }
 1952 
 1953     switch (help_level) {
 1954         case PAGE_LEVEL:
 1955             draw_page(curr_group->name, 0);
 1956             break;
 1957 
 1958         case INFO_PAGER:
 1959             display_info_page(0);
 1960             break;
 1961 
 1962         default:
 1963             break;
 1964     }
 1965     search_line = i;                                /* draw_page() resets this to 0 */
 1966 
 1967     /*
 1968      * Highlight found string
 1969      */
 1970     highlight_string(i - *lcurr_line + scroll_region_top, start, end - start);
 1971 }
 1972 
 1973 
 1974 /*
 1975  * Implement ^H toggle between cooked and raw views of article
 1976  */
 1977 void
 1978 toggle_raw(
 1979     struct t_group *group)
 1980 {
 1981     if (show_raw_article) {
 1982         artline = pgart.cookl;
 1983         artlines = pgart.cooked_lines;
 1984         note_fp = pgart.cooked;
 1985     } else {
 1986         static int j;               /* Needed on successive invocations */
 1987         int chunk = note_h->ext->line_count;
 1988 
 1989         /*
 1990          * We do this on the fly, since most of the time it won't be used
 1991          */
 1992         if (!pgart.rawl) {          /* Already done this for this article? */
 1993             char *line;
 1994             char *p;
 1995             long offset;
 1996 
 1997             j = 0;
 1998             rewind(pgart.raw);
 1999             pgart.rawl = my_malloc(sizeof(t_lineinfo) * chunk);
 2000             offset = ftell(pgart.raw);
 2001 
 2002             while ((line = tin_fgets(pgart.raw, FALSE)) != NULL) {
 2003                 int space;
 2004 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
 2005                 int num_bytes;
 2006                 wchar_t wc;
 2007 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
 2008 
 2009                 pgart.rawl[j].offset = offset;
 2010                 pgart.rawl[j].flags = 0;
 2011                 j++;
 2012                 if (j >= chunk) {
 2013                     chunk += 50;
 2014                     pgart.rawl = my_realloc(pgart.rawl, sizeof(t_lineinfo) * chunk);
 2015                 }
 2016 
 2017                 p = line;
 2018                 while (*p) {
 2019                     space = cCOLS - 1;
 2020 
 2021                     while ((space > 0) && *p) {
 2022 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
 2023                         num_bytes = mbtowc(&wc, p, MB_CUR_MAX);
 2024                         if (num_bytes != -1 && iswprint(wc)) {
 2025                             if ((space -= wcwidth(wc)) < 0)
 2026                                 break;
 2027                             p += num_bytes;
 2028                             offset += num_bytes;
 2029                         }
 2030 #else
 2031                         if (my_isprint((unsigned char) *p)) {
 2032                             space--;
 2033                             p++;
 2034                             offset++;
 2035                         }
 2036 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
 2037                         else if (IS_LOCAL_CHARSET("Big5") && (unsigned char) *p >= 0xa1 && (unsigned char) *p <= 0xfe && *(p + 1)) {
 2038                             /*
 2039                              * Big5: ASCII chars are handled by the normal code
 2040                              * check only for 2-byte chars
 2041                              * TODO: should we also check if the second byte is
 2042                              * also valid?
 2043                              */
 2044                             p += 2;
 2045                             offset += 2;
 2046                             space--;
 2047                         } else {
 2048                             /*
 2049                              * the current character can't be displayed print it as
 2050                              * an octal value (needs 4 columns) see also
 2051                              * color.c:draw_pager_line()
 2052                              */
 2053                             if ((space -= 4) < 0)
 2054                                 break;
 2055                             offset++;
 2056                             p++;
 2057                         }
 2058                     }
 2059                     /*
 2060                      * if we reached the end of the line we don't need to
 2061                      * remember anything
 2062                      */
 2063                     if (*p) {
 2064                         pgart.rawl[j].offset = offset;
 2065                         pgart.rawl[j].flags = 0;
 2066                         if (++j >= chunk) {
 2067                             chunk += 50;
 2068                             pgart.rawl = my_realloc(pgart.rawl, sizeof(t_lineinfo) * chunk);
 2069                         }
 2070                     }
 2071                 }
 2072 
 2073                 /*
 2074                  * only use ftell's return value here because we didn't
 2075                  * take the \n into account above.
 2076                  */
 2077                 offset = ftell(pgart.raw);
 2078             }
 2079 
 2080             pgart.rawl = my_realloc(pgart.rawl, sizeof(t_lineinfo) * j);
 2081         }
 2082         artline = pgart.rawl;
 2083         artlines = j;
 2084         note_fp = pgart.raw;
 2085     }
 2086     curr_line = 0;
 2087     show_raw_article = bool_not(show_raw_article);
 2088     draw_page(group ? group->name : "", 0);
 2089 }
 2090 
 2091 
 2092 /*
 2093  * Re-cook an article
 2094  */
 2095 void
 2096 resize_article(
 2097     t_bool wrap_lines,
 2098     t_openartinfo *artinfo)
 2099 {
 2100     free(artinfo->cookl);
 2101     if (artinfo->cooked)
 2102         fclose(artinfo->cooked);
 2103 
 2104     if (!cook_article(wrap_lines, artinfo, hide_uue, show_all_headers))
 2105         tin_done(EXIT_FAILURE, _(txt_cook_article_failed_exiting), tin_progname);
 2106 
 2107     show_raw_article = FALSE;
 2108     artline = pgart.cookl;
 2109     artlines = pgart.cooked_lines;
 2110     note_fp = pgart.cooked;
 2111 }
 2112 
 2113 
 2114 /*
 2115  * Infopager: simply page files
 2116  */
 2117 void
 2118 info_pager(
 2119     FILE *info_fh,
 2120     const char *title,
 2121     t_bool wrap_at_ends)
 2122 {
 2123     int offset;
 2124     t_function func;
 2125 
 2126     search_line = 0;
 2127     reset_srch_offsets();
 2128     info_file = info_fh;
 2129     info_title = title;
 2130     curr_info_line = 0;
 2131     preprocess_info_message(info_fh);
 2132     if (!info_fh)
 2133         return;
 2134     set_xclick_off();
 2135     display_info_page(0);
 2136 
 2137     forever {
 2138         switch (func = handle_keypad(page_left, page_right, page_mouse_action, info_keys)) {
 2139             case GLOBAL_ABORT:  /* common arrow keys */
 2140                 break;
 2141 
 2142             case GLOBAL_LINE_UP:
 2143                 if (num_info_lines <= NOTESLINES) {
 2144                     info_message(_(txt_begin_of_page));
 2145                     break;
 2146                 }
 2147                 if (curr_info_line == 0) {
 2148                     if (!wrap_at_ends) {
 2149                         info_message(_(txt_begin_of_page));
 2150                         break;
 2151                     }
 2152                     curr_info_line = num_info_lines - NOTESLINES;
 2153                     display_info_page(0);
 2154                     break;
 2155                 }
 2156                 offset = scroll_page(KEYMAP_UP);
 2157                 curr_info_line += offset;
 2158                 display_info_page(offset);
 2159                 break;
 2160 
 2161             case GLOBAL_LINE_DOWN:
 2162                 if (num_info_lines <= NOTESLINES) {
 2163                     info_message(_(txt_end_of_page));
 2164                     break;
 2165                 }
 2166                 if (curr_info_line + NOTESLINES >= num_info_lines) {
 2167                     if (!wrap_at_ends) {
 2168                         info_message(_(txt_end_of_page));
 2169                         break;
 2170                     }
 2171                     curr_info_line = 0;
 2172                     display_info_page(0);
 2173                     break;
 2174                 }
 2175                 offset = scroll_page(KEYMAP_DOWN);
 2176                 curr_info_line += offset;
 2177                 display_info_page(offset);
 2178                 break;
 2179 
 2180             case GLOBAL_PAGE_DOWN:
 2181                 if (num_info_lines <= NOTESLINES) {
 2182                     info_message(_(txt_end_of_page));
 2183                     break;
 2184                 }
 2185                 if (curr_info_line + NOTESLINES >= num_info_lines) {    /* End is already on screen */
 2186                     if (!wrap_at_ends) {
 2187                         info_message(_(txt_end_of_page));
 2188                         break;
 2189                     }
 2190                     curr_info_line = 0;
 2191                     display_info_page(0);
 2192                     break;
 2193                 }
 2194                 curr_info_line += ((tinrc.scroll_lines == -2) ? NOTESLINES / 2 : NOTESLINES);
 2195                 display_info_page(0);
 2196                 break;
 2197 
 2198             case GLOBAL_PAGE_UP:
 2199                 if (num_info_lines <= NOTESLINES) {
 2200                     info_message(_(txt_begin_of_page));
 2201                     break;
 2202                 }
 2203                 if (curr_info_line == 0) {
 2204                     if (!wrap_at_ends) {
 2205                         info_message(_(txt_begin_of_page));
 2206                         break;
 2207                     }
 2208                     curr_info_line = num_info_lines - NOTESLINES;
 2209                     display_info_page(0);
 2210                     break;
 2211                 }
 2212                 curr_info_line -= ((tinrc.scroll_lines == -2) ? NOTESLINES / 2 : NOTESLINES);
 2213                 display_info_page(0);
 2214                 break;
 2215 
 2216             case GLOBAL_FIRST_PAGE:
 2217                 if (curr_info_line) {
 2218                     curr_info_line = 0;
 2219                     display_info_page(0);
 2220                 }
 2221                 break;
 2222 
 2223             case GLOBAL_LAST_PAGE:
 2224                 if (curr_info_line + NOTESLINES != num_info_lines) {
 2225                     /* Display a full last page for neatness */
 2226                     curr_info_line = num_info_lines - NOTESLINES;
 2227                     display_info_page(0);
 2228                 }
 2229                 break;
 2230 
 2231             case GLOBAL_TOGGLE_HELP_DISPLAY:
 2232                 toggle_mini_help(INFO_PAGER);
 2233                 display_info_page(0);
 2234                 break;
 2235 
 2236             case GLOBAL_SEARCH_SUBJECT_FORWARD:
 2237             case GLOBAL_SEARCH_SUBJECT_BACKWARD:
 2238             case GLOBAL_SEARCH_REPEAT:
 2239                 if (func == GLOBAL_SEARCH_REPEAT && last_search != GLOBAL_SEARCH_SUBJECT_FORWARD && last_search != GLOBAL_SEARCH_SUBJECT_BACKWARD)
 2240                     break;
 2241 
 2242                 if ((search_article((func == GLOBAL_SEARCH_SUBJECT_FORWARD), (func == GLOBAL_SEARCH_REPEAT), search_line, num_info_lines, infoline, num_info_lines - 1, info_file)) == -1)
 2243                     break;
 2244 
 2245                 process_search(&curr_info_line, num_info_lines, NOTESLINES, INFO_PAGER);
 2246                 break;
 2247 
 2248             case GLOBAL_QUIT:   /* quit */
 2249                 ClearScreen();
 2250                 return;
 2251 
 2252             default:
 2253                 break;
 2254         }
 2255     }
 2256 }
 2257 
 2258 
 2259 /*
 2260  * Redraw the current page, curr_info_line will be the first line displayed
 2261  * If part is !=0, then only draw the first (-ve) or last (+ve) few lines
 2262  */
 2263 void
 2264 display_info_page(
 2265     int part)
 2266 {
 2267     int start, end; /* 1st, last line to draw */
 2268 
 2269     signal_context = cInfopager;
 2270 
 2271     /*
 2272      * Can't do partial draw if term can't scroll properly
 2273      */
 2274     if (part && !have_linescroll)
 2275         part = 0;
 2276 
 2277     if (curr_info_line < 0)
 2278         curr_info_line = 0;
 2279     if (curr_info_line >= num_info_lines)
 2280         curr_info_line = num_info_lines - 1;
 2281 
 2282     scroll_region_top = INDEX_TOP;
 2283 
 2284     /* Down-scroll, only redraw bottom 'part' lines of screen */
 2285     if ((start = (part > 0) ? NOTESLINES - part : 0) < 0)
 2286         start = 0;
 2287 
 2288     /* Up-scroll, only redraw the top 'part' lines of screen */
 2289     if ((end = (part < 0) ? -part : NOTESLINES) > NOTESLINES)
 2290         end = NOTESLINES;
 2291 
 2292     /* Print title */
 2293     if ((end - start >= NOTESLINES) || (part == 0)) {
 2294         ClearScreen();
 2295         center_line(0, TRUE, info_title);
 2296     }
 2297 
 2298     print_message_page(info_file, infoline, num_info_lines, curr_info_line, start, end, INFO_PAGER);
 2299 
 2300     /* print footer */
 2301     draw_percent_mark(curr_info_line + (curr_info_line + NOTESLINES < num_info_lines ? NOTESLINES : num_info_lines - curr_info_line), num_info_lines);
 2302     stow_cursor();
 2303 }
 2304 
 2305 
 2306 static void
 2307 preprocess_info_message(
 2308     FILE *info_fh)
 2309 {
 2310     int chunk = 50;
 2311 
 2312     FreeAndNull(infoline);
 2313     if (!info_fh)
 2314         return;
 2315 
 2316     rewind(info_fh);
 2317     infoline = my_malloc(sizeof(t_lineinfo) * chunk);
 2318     num_info_lines = 0;
 2319 
 2320     do {
 2321         infoline[num_info_lines].offset = ftell(info_fh);
 2322         infoline[num_info_lines].flags = 0;
 2323         num_info_lines++;
 2324         if (num_info_lines >= chunk) {
 2325             chunk += 50;
 2326             infoline = my_realloc(infoline, sizeof(t_lineinfo) * chunk);
 2327         }
 2328     } while (tin_fgets(info_fh, FALSE) != NULL);
 2329 
 2330     num_info_lines--;
 2331     infoline = my_realloc(infoline, sizeof(t_lineinfo) * num_info_lines);
 2332 }
 2333 
 2334 
 2335 /*
 2336  * URL menu
 2337  */
 2338 static t_function
 2339 url_left(
 2340     void)
 2341 {
 2342     return GLOBAL_QUIT;
 2343 }
 2344 
 2345 
 2346 static t_function
 2347 url_right(
 2348     void)
 2349 {
 2350     return URL_SELECT;
 2351 }
 2352 
 2353 
 2354 static void
 2355 show_url_page(
 2356     void)
 2357 {
 2358     int i;
 2359 
 2360     signal_context = cURL;
 2361     currmenu = &urlmenu;
 2362     mark_offset = 0;
 2363 
 2364     if (urlmenu.curr < 0)
 2365         urlmenu.curr = 0;
 2366 
 2367     ClearScreen();
 2368     set_first_screen_item();
 2369     center_line(0, TRUE, _(txt_url_menu));
 2370 
 2371     for (i = urlmenu.first; i < urlmenu.first + NOTESLINES && i < urlmenu.max; ++i)
 2372         build_url_line(i);
 2373 
 2374     show_mini_help(URL_LEVEL);
 2375 
 2376     draw_url_arrow();
 2377 }
 2378 
 2379 
 2380 static t_bool
 2381 url_page(
 2382     void)
 2383 {
 2384     char key[MAXKEYLEN];
 2385     t_function func;
 2386     t_menu *oldmenu = NULL;
 2387 
 2388     if (currmenu)
 2389         oldmenu = currmenu;
 2390     urlmenu.curr = 0;
 2391     urlmenu.max = build_url_list();
 2392     if (urlmenu.max == 0)
 2393         return FALSE;
 2394 
 2395     clear_note_area();
 2396     show_url_page();
 2397     set_xclick_off();
 2398 
 2399     forever {
 2400         switch ((func = handle_keypad(url_left, url_right, NULL, url_keys))) {
 2401             case GLOBAL_QUIT:
 2402                 free_url_list();
 2403                 if (oldmenu)
 2404                     currmenu = oldmenu;
 2405                 return TRUE;
 2406 
 2407             case DIGIT_1:
 2408             case DIGIT_2:
 2409             case DIGIT_3:
 2410             case DIGIT_4:
 2411             case DIGIT_5:
 2412             case DIGIT_6:
 2413             case DIGIT_7:
 2414             case DIGIT_8:
 2415             case DIGIT_9:
 2416                 if (urlmenu.max)
 2417                     prompt_item_num(func_to_key(func, url_keys), _(txt_url_select));
 2418                 break;
 2419 
 2420 #ifndef NO_SHELL_ESCAPE
 2421             case GLOBAL_SHELL_ESCAPE:
 2422                 do_shell_escape();
 2423                 break;
 2424 #endif /* !NO_SHELL_ESCAPE */
 2425 
 2426             case GLOBAL_HELP:
 2427                 show_help_page(URL_LEVEL, _(txt_url_menu_com));
 2428                 show_url_page();
 2429                 break;
 2430 
 2431             case GLOBAL_FIRST_PAGE:
 2432                 top_of_list();
 2433                 break;
 2434 
 2435             case GLOBAL_LAST_PAGE:
 2436                 end_of_list();
 2437                 break;
 2438 
 2439             case GLOBAL_REDRAW_SCREEN:
 2440                 my_retouch();
 2441                 show_url_page();
 2442                 break;
 2443 
 2444             case GLOBAL_LINE_DOWN:
 2445                 move_down();
 2446                 break;
 2447 
 2448             case GLOBAL_LINE_UP:
 2449                 move_up();
 2450                 break;
 2451 
 2452             case GLOBAL_PAGE_DOWN:
 2453                 page_down();
 2454                 break;
 2455 
 2456             case GLOBAL_PAGE_UP:
 2457                 page_up();
 2458                 break;
 2459 
 2460             case GLOBAL_SCROLL_DOWN:
 2461                 scroll_down();
 2462                 break;
 2463 
 2464             case GLOBAL_SCROLL_UP:
 2465                 scroll_up();
 2466                 break;
 2467 
 2468             case GLOBAL_TOGGLE_HELP_DISPLAY:
 2469                 toggle_mini_help(URL_LEVEL);
 2470                 show_url_page();
 2471                 break;
 2472 
 2473             case GLOBAL_TOGGLE_INFO_LAST_LINE:
 2474                 tinrc.info_in_last_line = bool_not(tinrc.info_in_last_line);
 2475                 show_url_page();
 2476                 break;
 2477 
 2478             case URL_SELECT:
 2479                 if (urlmenu.max) {
 2480                     if (process_url(urlmenu.curr))
 2481                         show_url_page();
 2482                     else
 2483                         draw_url_arrow();
 2484                 }
 2485                 break;
 2486 
 2487             case GLOBAL_SEARCH_SUBJECT_FORWARD:
 2488             case GLOBAL_SEARCH_SUBJECT_BACKWARD:
 2489             case GLOBAL_SEARCH_REPEAT:
 2490                 if (func == GLOBAL_SEARCH_REPEAT && last_search != GLOBAL_SEARCH_SUBJECT_FORWARD && last_search != GLOBAL_SEARCH_SUBJECT_BACKWARD)
 2491                     info_message(_(txt_no_prev_search));
 2492                 else if (urlmenu.max) {
 2493                     int new_pos, old_pos = urlmenu.curr;
 2494 
 2495                     new_pos = generic_search((func == GLOBAL_SEARCH_SUBJECT_FORWARD), (func == GLOBAL_SEARCH_REPEAT), urlmenu.curr, urlmenu.max - 1, URL_LEVEL);
 2496                     if (new_pos != old_pos)
 2497                         move_to_item(new_pos);
 2498                 }
 2499                 break;
 2500 
 2501             default:
 2502                 info_message(_(txt_bad_command), printascii(key, func_to_key(GLOBAL_HELP, url_keys)));
 2503                 break;
 2504         }
 2505     }
 2506 }
 2507 
 2508 
 2509 static void
 2510 draw_url_arrow(
 2511     void)
 2512 {
 2513     draw_arrow_mark(INDEX_TOP + urlmenu.curr - urlmenu.first);
 2514     if (tinrc.info_in_last_line) {
 2515         t_url *lptr;
 2516 
 2517         lptr = find_url(urlmenu.curr);
 2518         info_message("%s", lptr->url);
 2519     } else if (urlmenu.curr == urlmenu.max - 1)
 2520         info_message(_(txt_end_of_urls));
 2521 }
 2522 
 2523 
 2524 t_url *
 2525 find_url(
 2526     int n)
 2527 {
 2528     t_url *lptr;
 2529 
 2530     lptr = url_list;
 2531     while (n-- > 0 && lptr->next)
 2532         lptr = lptr->next;
 2533 
 2534     return lptr;
 2535 }
 2536 
 2537 
 2538 static void
 2539 build_url_line(
 2540     int i)
 2541 {
 2542     char *sptr;
 2543     int len = cCOLS - 9;
 2544     t_url *lptr;
 2545 
 2546 #ifdef USE_CURSES
 2547     /*
 2548      * Allocate line buffer
 2549      * make it the same size like in !USE_CURSES case to simplify some code
 2550      */
 2551     sptr = my_malloc(cCOLS + 2);
 2552 #else
 2553     sptr = screen[INDEX2SNUM(i)].col;
 2554 #endif /* USE_CURSES */
 2555 
 2556     lptr = find_url(i);
 2557     snprintf(sptr, cCOLS, "  %s  %-*.*s%s", tin_ltoa(i + 1, 4), len, len, lptr->url, cCRLF);
 2558     WriteLine(INDEX2LNUM(i), sptr);
 2559 
 2560 #ifdef USE_CURSES
 2561     free(sptr);
 2562 #endif /* USE_CURSES */
 2563 }
 2564 
 2565 
 2566 static t_bool
 2567 process_url(
 2568     int n)
 2569 {
 2570     char *url, *url_esc;
 2571     size_t len;
 2572     t_url *lptr;
 2573 
 2574     lptr = find_url(n);
 2575     len = strlen(lptr->url) << 1; /* double size; room for editing URL */
 2576     url = my_malloc(len + 1);
 2577     if (prompt_default_string("URL:", url, len, lptr->url, HIST_URL)) {
 2578         if (!*url) {            /* Don't try and open nothing */
 2579             free(url);
 2580             return FALSE;
 2581         }
 2582         wait_message(2, _(txt_url_open), url);
 2583         url_esc = escape_shell_meta(url, no_quote);
 2584         len = strlen(url_esc) + strlen(tinrc.url_handler) + 2;
 2585         url = my_realloc(url, len);
 2586         snprintf(url, len, "%s %s", tinrc.url_handler, url_esc);
 2587         invoke_cmd(url);
 2588         free(url);
 2589         cursoroff();
 2590         return TRUE;
 2591     }
 2592     free(url);
 2593     return FALSE;
 2594 }
 2595 
 2596 
 2597 static int
 2598 build_url_list(
 2599     void)
 2600 {
 2601     char *ptr;
 2602     int i, count = 0;
 2603     int offsets[6];
 2604     int offsets_size = ARRAY_SIZE(offsets);
 2605     t_url *lptr = NULL;
 2606 
 2607     for (i = 0; i < artlines; ++i) {
 2608         if (!(artline[i].flags & (C_URL | C_NEWS | C_MAIL)))
 2609             continue;
 2610 
 2611         /*
 2612          * Line contains a URL, so read it in
 2613          */
 2614         fseek(pgart.cooked, artline[i].offset, SEEK_SET);
 2615         if ((ptr = tin_fgets(pgart.cooked, FALSE)) == NULL)
 2616             continue;
 2617 
 2618         /*
 2619          * Step through, finding URL's
 2620          */
 2621         forever {
 2622             /* any matches left? */
 2623             if (pcre_exec(url_regex.re, url_regex.extra, ptr, strlen(ptr), 0, 0, offsets, offsets_size) == PCRE_ERROR_NOMATCH)
 2624                 if (pcre_exec(mail_regex.re, mail_regex.extra, ptr, strlen(ptr), 0, 0, offsets, offsets_size) == PCRE_ERROR_NOMATCH)
 2625                     if (pcre_exec(news_regex.re, news_regex.extra, ptr, strlen(ptr), 0, 0, offsets, offsets_size) == PCRE_ERROR_NOMATCH)
 2626                         break;
 2627 
 2628             *(ptr + offsets[1]) = '\0';
 2629 
 2630             if (!lptr)
 2631                 lptr = url_list = my_malloc(sizeof(t_url));
 2632             else {
 2633                 lptr->next = my_malloc(sizeof(t_url));
 2634                 lptr = lptr->next;
 2635             }
 2636             lptr->url = my_strdup(ptr + offsets[0]);
 2637             lptr->next = NULL;
 2638             ++count;
 2639 
 2640             ptr += offsets[1] + 1;
 2641         }
 2642     }
 2643     return count;
 2644 }
 2645 
 2646 
 2647 static void
 2648 free_url_list(
 2649     void)
 2650 {
 2651     t_url *p, *q;
 2652 
 2653     for (p = url_list; p != NULL; p = q) {
 2654         q = p->next;
 2655         free(p->url);
 2656         free(p);
 2657     }
 2658     url_list = NULL;
 2659 }