"Fossies" - the Fresh Open Source Software Archive

Member "tin-2.6.1/src/page.c" (22 Dec 2021, 69506 Bytes) of package /linux/misc/tin-2.6.1.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.6.0_vs_2.6.1.

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