"Fossies" - the Fresh Open Source Software Archive

Member "tin-2.6.2/src/page.c" (9 Dec 2022, 69299 Bytes) of package /linux/misc/tin-2.6.2.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.1_vs_2.6.2.

    1 /*
    2  *  Project   : tin - a Usenet reader
    3  *  Module    : page.c
    4  *  Author    : I. Lea & R. Skrenta
    5  *  Created   : 1991-04-01
    6  *  Updated   : 2022-10-27
    7  *  Notes     :
    8  *
    9  * Copyright (c) 1991-2023 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_CONNECTION_INFO:
  822                 XFACE_CLEAR();
  823                 show_connection_page(PAGE_LEVEL, _(txt_connection_info));
  824                 draw_page(group->name, 0);
  825                 break;
  826 
  827             case GLOBAL_TOGGLE_HELP_DISPLAY:    /* toggle mini help menu */
  828                 toggle_mini_help(PAGE_LEVEL);
  829                 draw_page(group->name, 0);
  830                 break;
  831 
  832             case GLOBAL_QUIT:   /* return to index page */
  833 return_to_index:
  834                 XFACE_CLEAR();
  835                 i = which_thread(this_resp);
  836                 if (threadnum)
  837                     *threadnum = which_response(this_resp);
  838 
  839                 return i;
  840 
  841             case GLOBAL_TOGGLE_INVERSE_VIDEO:   /* toggle inverse video */
  842                 toggle_inverse_video();
  843                 draw_page(group->name, 0);
  844                 show_inverse_video_status();
  845                 break;
  846 
  847 #ifdef HAVE_COLOR
  848             case GLOBAL_TOGGLE_COLOR:       /* toggle color */
  849                 if (toggle_color()) {
  850                     draw_page(group->name, 0);
  851                     show_color_status();
  852                 }
  853                 break;
  854 #endif /* HAVE_COLOR */
  855 
  856             case PAGE_LIST_THREAD:  /* -> thread page that this article is in */
  857                 XFACE_CLEAR();
  858                 fixup_thread(this_resp, FALSE);
  859                 return GRP_GOTOTHREAD;
  860 
  861             case GLOBAL_OPTION_MENU:    /* option menu */
  862                 XFACE_CLEAR();
  863                 old_artnum = arts[this_resp].artnum;
  864                 config_page(group->name, signal_context);
  865                 if ((this_resp = find_artnum(old_artnum)) == -1 || which_thread(this_resp) == -1) { /* We have lost the thread */
  866                     pos_first_unread_thread();
  867                     return GRP_EXIT;
  868                 }
  869                 fixup_thread(this_resp, FALSE);
  870                 draw_page(group->name, 0);
  871                 break;
  872 
  873             case PAGE_NEXT_ARTICLE: /* skip to next article */
  874                 XFACE_CLEAR();
  875                 if ((n = next_response(this_resp)) == -1)
  876                     return (which_thread(this_resp));
  877 
  878                 if ((i = load_article(n, group)) < 0)
  879                     return i;
  880                 break;
  881 
  882             case PAGE_MARK_THREAD_READ: /* mark rest of thread as read */
  883                 thd_mark_read(group, this_resp);
  884                 if ((n = next_unread(next_response(this_resp))) == -1)
  885                     goto return_to_index;
  886                 if ((i = load_article(n, group)) < 0) {
  887                     XFACE_CLEAR();
  888                     return i;
  889                 }
  890                 break;
  891 
  892             case PAGE_NEXT_UNREAD_ARTICLE:  /* next unread article */
  893                 goto page_goto_next_unread;
  894 
  895             case PAGE_PREVIOUS_ARTICLE: /* previous article */
  896                 XFACE_CLEAR();
  897                 if ((n = prev_response(this_resp)) == -1)
  898                     return this_resp;
  899 
  900                 if ((i = load_article(n, group)) < 0)
  901                     return i;
  902                 break;
  903 
  904             case PAGE_PREVIOUS_UNREAD_ARTICLE:  /* previous unread article */
  905                 if ((n = prev_unread(prev_response(this_resp))) == -1)
  906                     info_message(_(txt_no_prev_unread_art));
  907                 else {
  908                     if ((i = load_article(n, group)) < 0) {
  909                         XFACE_CLEAR();
  910                         return i;
  911                     }
  912                 }
  913                 break;
  914 
  915             case GLOBAL_QUIT_TIN:   /* quit */
  916                 XFACE_CLEAR();
  917                 return GRP_QUIT;
  918 
  919             case PAGE_REPLY_QUOTE:  /* reply to author through mail */
  920             case PAGE_REPLY_QUOTE_HEADERS:
  921             case PAGE_REPLY:
  922                 XFACE_CLEAR();
  923                 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);
  924                 draw_page(group->name, 0);
  925                 break;
  926 
  927             case PAGE_TAG:  /* tag/untag article for saving */
  928                 tag_article(this_resp);
  929                 break;
  930 
  931             case PAGE_GROUP_SELECT: /* return to group selection page */
  932                 XFACE_CLEAR();
  933                 return GRP_RETSELECT;
  934 
  935             case GLOBAL_VERSION:
  936                 info_message(cvers);
  937                 break;
  938 
  939             case GLOBAL_POST:   /* post a basenote */
  940                 XFACE_SUPPRESS();
  941                 if (post_article(group->name))
  942                     draw_page(group->name, 0);
  943                 XFACE_SHOW();
  944                 break;
  945 
  946             case GLOBAL_POSTPONED:  /* post postponed article */
  947                 if (can_post || art_type != GROUP_TYPE_NEWS) {
  948                     XFACE_SUPPRESS();
  949                     if (pickup_postponed_articles(FALSE, FALSE))
  950                         draw_page(group->name, 0);
  951                     XFACE_SHOW();
  952                 } else
  953                     info_message(_(txt_cannot_post));
  954                 break;
  955 
  956             case GLOBAL_DISPLAY_POST_HISTORY:   /* display messages posted by user */
  957                 XFACE_SUPPRESS();
  958                 if (post_hist_page())
  959                     return GRP_EXIT;
  960                 else {
  961                     XFACE_SHOW();
  962                 }
  963                 break;
  964 
  965             case MARK_ARTICLE_UNREAD:   /* mark article as unread(to return) */
  966                 art_mark(group, &arts[this_resp], ART_WILL_RETURN);
  967                 info_message(_(txt_marked_as_unread), _(txt_article_upper));
  968                 break;
  969 
  970             case PAGE_SKIP_INCLUDED_TEXT:   /* skip included text */
  971                 for (i = j = curr_line; i < artlines; i++) {
  972                     if (artline[i].flags & (C_QUOTE1 | C_QUOTE2 | C_QUOTE3)) {
  973                         j = i;
  974                         break;
  975                     }
  976                 }
  977 
  978                 for (; j < artlines; j++) {
  979                     if (!(artline[j].flags & (C_QUOTE1 | C_QUOTE2 | C_QUOTE3)))
  980                         break;
  981                 }
  982 
  983                 if (j != curr_line) {
  984                     curr_line = j;
  985                     draw_page(group->name, 0);
  986                 }
  987                 break;
  988 
  989             case GLOBAL_TOGGLE_INFO_LAST_LINE: /* this is _not_ correct, we do not toggle status here */
  990                 info_message("%s", arts[this_resp].subject);
  991                 break;
  992 
  993             case PAGE_TOGGLE_HIGHLIGHTING:
  994                 word_highlight = bool_not(word_highlight);
  995                 draw_page(group->name, 0);
  996                 info_message(_(txt_toggled_high), txt_onoff[word_highlight != FALSE ? 1 : 0]);
  997                 break;
  998 
  999             case PAGE_VIEW_ATTACHMENTS:
 1000                 XFACE_SUPPRESS();
 1001                 hide_uue_tmp = hide_uue;
 1002                 hide_uue = UUE_NO;
 1003                 resize_article(TRUE, &pgart);
 1004                 attachment_page(&pgart);
 1005                 hide_uue = hide_uue_tmp;
 1006                 resize_article(TRUE, &pgart);
 1007                 draw_page(group->name, 0);
 1008                 XFACE_SHOW();
 1009                 break;
 1010 
 1011             case PAGE_VIEW_URL:
 1012                 if (!show_raw_article) { /* cooked mode? */
 1013                     t_bool success;
 1014 
 1015                     XFACE_SUPPRESS();
 1016                     resize_article(FALSE, &pgart); /* unbreak long lines */
 1017                     success = url_page();
 1018                     resize_article(TRUE, &pgart); /* rebreak long lines */
 1019                     draw_page(group->name, 0);
 1020                     if (!success)
 1021                         info_message(_(txt_url_done));
 1022                     XFACE_SHOW();
 1023                 }
 1024                 break;
 1025 
 1026             default:
 1027                 info_message(_(txt_bad_command), PrintFuncKey(key, GLOBAL_HELP, page_keys));
 1028         }
 1029     }
 1030     /* NOTREACHED */
 1031     return GRP_ARTUNAVAIL;
 1032 }
 1033 
 1034 
 1035 static void
 1036 print_message_page(
 1037     FILE *file,
 1038     t_lineinfo *messageline,
 1039     size_t messagelines,
 1040     size_t base_line,
 1041     size_t begin,
 1042     size_t end,
 1043     int help_level)
 1044 {
 1045     char *line;
 1046     char *p;
 1047     int bytes;
 1048     size_t i = begin;
 1049     t_lineinfo *curr;
 1050 
 1051     for (; i < end; i++) {
 1052         if (base_line + i >= messagelines)      /* ran out of message */
 1053             break;
 1054 
 1055         curr = &messageline[base_line + i];
 1056 
 1057         if (fseek(file, curr->offset, SEEK_SET) != 0)
 1058             break;
 1059 
 1060         if ((line = tin_fgets(file, FALSE)) == NULL)
 1061             break;  /* ran out of message */
 1062 
 1063         if ((help_level == INFO_PAGER) && (strwidth(line) >= cCOLS - 1))
 1064 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
 1065             {
 1066                 char *tmp, *f, *t;
 1067 
 1068                 f = tmp = strunc(line, cCOLS - 1);
 1069                 t = line;
 1070                 while (*f)
 1071                     *t++ = *f++;
 1072                 *t = '\0';
 1073                 free(tmp);
 1074             }
 1075 #else
 1076             line[cCOLS - 1] = '\0';
 1077 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
 1078 
 1079         /*
 1080          * use the offsets gained while doing line wrapping to
 1081          * determine the correct position to truncate the line
 1082          */
 1083         if ((help_level != INFO_PAGER) && (base_line + i < messagelines - 1)) { /* not last line of message */
 1084             bytes = (int) ((curr + 1)->offset - curr->offset);
 1085             line[bytes] = '\0';
 1086         }
 1087 
 1088         /*
 1089          * rotN encoding on body and sig data only
 1090          */
 1091         if ((rotate != 0) && ((curr->flags & (C_BODY | C_SIG)) || show_raw_article)) {
 1092             for (p = line; *p; p++) {
 1093                 if (*p >= 'A' && *p <= 'Z')
 1094                     *p = (char) ((*p - 'A' + rotate) % 26 + 'A');
 1095                 else if (*p >= 'a' && *p <= 'z')
 1096                     *p = (char) ((*p - 'a' + rotate) % 26 + 'a');
 1097             }
 1098         }
 1099 
 1100         strip_line(line);
 1101 
 1102 #ifndef USE_CURSES
 1103         snprintf(screen[i + (size_t) scroll_region_top].col, (size_t) cCOLS, "%s" cCRLF, line);
 1104 #endif /* !USE_CURSES */
 1105 
 1106         MoveCursor((int) (i + (size_t) scroll_region_top), 0);
 1107         draw_pager_line(line, curr->flags, show_raw_article);
 1108 
 1109         /*
 1110          * Highlight URL's and mail addresses
 1111          */
 1112         if (tinrc.url_highlight) {
 1113             if (curr->flags & C_URL)
 1114 #ifdef HAVE_COLOR
 1115                 highlight_regexes((int) (i + (size_t) scroll_region_top), &url_regex, use_color ? tinrc.col_urls : -1);
 1116 #else
 1117                 highlight_regexes((int) (i + (size_t) scroll_region_top), &url_regex, -1);
 1118 #endif /* HAVE_COLOR */
 1119 
 1120             if (curr->flags & C_MAIL)
 1121 #ifdef HAVE_COLOR
 1122                 highlight_regexes((int) (i + (size_t) scroll_region_top), &mail_regex, use_color ? tinrc.col_urls : -1);
 1123 #else
 1124                 highlight_regexes((int) (i + (size_t) scroll_region_top), &mail_regex, -1);
 1125 #endif /* HAVE_COLOR */
 1126 
 1127             if (curr->flags & C_NEWS)
 1128 #ifdef HAVE_COLOR
 1129                 highlight_regexes((int) (i + (size_t) scroll_region_top), &news_regex, use_color ? tinrc.col_urls : -1);
 1130 #else
 1131                 highlight_regexes((int) (i + (size_t) scroll_region_top), &news_regex, -1);
 1132 #endif /* HAVE_COLOR */
 1133         }
 1134 
 1135         /*
 1136          * Highlight /slashes/, *stars*, _underscores_ and -strokes-
 1137          */
 1138         if (word_highlight && (curr->flags & C_BODY) && !(curr->flags & C_CTRLL)) {
 1139 #ifdef HAVE_COLOR
 1140             highlight_regexes((int) (i + (size_t) scroll_region_top), &slashes_regex, use_color ? tinrc.col_markslash : tinrc.mono_markslash);
 1141             highlight_regexes((int) (i + (size_t) scroll_region_top), &stars_regex, use_color ? tinrc.col_markstar : tinrc.mono_markstar);
 1142             highlight_regexes((int) (i + (size_t) scroll_region_top), &underscores_regex, use_color ? tinrc.col_markdash : tinrc.mono_markdash);
 1143             highlight_regexes((int) (i + (size_t) scroll_region_top), &strokes_regex, use_color ? tinrc.col_markstroke : tinrc.mono_markstroke);
 1144 #else
 1145             highlight_regexes((int) (i + (size_t) scroll_region_top), &slashes_regex, tinrc.mono_markslash);
 1146             highlight_regexes((int) (i + (size_t) scroll_region_top), &stars_regex, tinrc.mono_markstar);
 1147             highlight_regexes((int) (i + (size_t) scroll_region_top), &underscores_regex, tinrc.mono_markdash);
 1148             highlight_regexes((int) (i + (size_t) scroll_region_top), &strokes_regex, tinrc.mono_markstroke);
 1149 #endif /* HAVE_COLOR */
 1150         }
 1151 
 1152         /* Blank the screen after a ^L (only occurs when showing cooked) */
 1153         if (!reveal_ctrl_l && (curr->flags & C_CTRLL) && (int) (base_line + i) > reveal_ctrl_l_lines) {
 1154             CleartoEOS();
 1155             break;
 1156         }
 1157     }
 1158 
 1159 #ifdef HAVE_COLOR
 1160     fcol(tinrc.col_text);
 1161 #endif /* HAVE_COLOR */
 1162 
 1163     show_mini_help(help_level);
 1164 }
 1165 
 1166 
 1167 /*
 1168  * Redraw the current page, curr_line will be the first line displayed
 1169  * Everything that calls draw_page() just sets curr_line, this function must
 1170  * ensure it is set to something sane
 1171  * If part is !=0, then only draw the first (-ve) or last (+ve) few lines
 1172  */
 1173 void
 1174 draw_page(
 1175     const char *group,
 1176     int part)
 1177 {
 1178     int start, end; /* 1st, last line to draw */
 1179 
 1180     signal_context = cPage;
 1181 
 1182     /*
 1183      * Can't do partial draw if term can't scroll properly
 1184      */
 1185     if (part && !have_linescroll)
 1186         part = 0;
 1187 
 1188     /*
 1189      * Ensure curr_line is in bounds
 1190      */
 1191     if (curr_line < 0)
 1192         curr_line = 0;          /* Oops - off the top */
 1193     else {
 1194         if (curr_line > artlines)
 1195             curr_line = artlines;   /* Oops - off the end */
 1196     }
 1197 
 1198     search_line = curr_line;    /* Reset search to start from top of display */
 1199 
 1200     scroll_region_top = PAGE_HEADER;
 1201 
 1202     /* Down-scroll, only redraw bottom 'part' lines of screen */
 1203     if ((start = (part > 0) ? ARTLINES - part : 0) < 0)
 1204         start = 0;
 1205 
 1206     /* Up-scroll, only redraw the top 'part' lines of screen */
 1207     if ((end = (part < 0) ? -part : ARTLINES) > ARTLINES)
 1208         end = ARTLINES;
 1209 
 1210     /*
 1211      * ncurses doesn't clear the scroll area when you scroll by more than the
 1212      * window size - force full redraw
 1213      */
 1214     if ((end - start >= ARTLINES) || (part == 0)) {
 1215         ClearScreen();
 1216         draw_page_header(group);
 1217     } else
 1218         MoveCursor(0, 0);
 1219 
 1220     print_message_page(note_fp, artline, (size_t) artlines, (size_t) curr_line, (size_t) start, (size_t) end, PAGE_LEVEL);
 1221 
 1222     /*
 1223      * Print an appropriate footer
 1224      */
 1225     if (curr_line + ARTLINES >= artlines) {
 1226         char buf[LEN], *buf2;
 1227         int len;
 1228 
 1229         STRCPY(buf, (arts[this_resp].thread != -1) ? _(txt_next_resp) : _(txt_last_resp));
 1230         buf2 = strunc(buf, cCOLS - 1);
 1231         len = strwidth(buf2);
 1232         clear_message();
 1233         MoveCursor(cLINES, cCOLS - len - (1 + BLANK_PAGE_COLS));
 1234 #ifdef HAVE_COLOR
 1235         fcol(tinrc.col_normal);
 1236 #endif /* HAVE_COLOR */
 1237         StartInverse();
 1238         my_fputs(buf2, stdout);
 1239         EndInverse();
 1240         my_flush();
 1241         free(buf2);
 1242     } else
 1243         draw_percent_mark(curr_line + ARTLINES, artlines);
 1244 
 1245 #ifdef XFACE_ABLE
 1246     if (tinrc.use_slrnface && !show_raw_article)
 1247         slrnface_display_xface(note_h->xface);
 1248 #endif /* XFACE_ABLE */
 1249 
 1250     stow_cursor();
 1251 }
 1252 
 1253 
 1254 /*
 1255  * Start external metamail program
 1256  */
 1257 static void
 1258 invoke_metamail(
 1259     FILE *fp)
 1260 {
 1261     char *ptr = tinrc.metamail_prog;
 1262     char buf[LEN];
 1263     long offset;
 1264     FILE *mime_fp;
 1265 #ifdef DONT_HAVE_PIPING
 1266     char mimefile[PATH_LEN];
 1267     int fd_mime;
 1268 #endif /* DONT_HAVE_PIPING */
 1269 
 1270     if ((*ptr == '\0') || (!strcmp(ptr, INTERNAL_CMD)) || (getenv("NOMETAMAIL") != NULL))
 1271         return;
 1272 
 1273     if ((offset = ftell(fp)) == -1) {
 1274         perror_message(_(txt_command_failed), ptr);
 1275         return;
 1276     }
 1277 
 1278     EndWin();
 1279     Raw(FALSE);
 1280 
 1281 #ifdef DONT_HAVE_PIPING
 1282     if ((fd_mime = my_tmpfile(mimefile, sizeof(mimefile) - 1, homedir)) == -1) {
 1283         perror_message(_(txt_command_failed), ptr);
 1284         return;
 1285     }
 1286     if ((mime_fp = fdopen(fd_mime, "w")))
 1287 #else
 1288     if ((mime_fp = popen(ptr, "w")))
 1289 #endif /* DONT_HAVE_PIPING */
 1290     {
 1291         rewind(fp);
 1292         while (fgets(buf, (int) sizeof(buf), fp) != NULL)
 1293             fputs(buf, mime_fp);
 1294 
 1295         fflush(mime_fp);
 1296         /* This is needed if we are viewing the raw art */
 1297         fseek(fp, offset, SEEK_SET);    /* goto old position */
 1298 
 1299 #ifdef DONT_HAVE_PIPING
 1300         snprintf(buf, sizeof(buf) - 1, "%s %s", tinrc.metamail_prog, mimefile);
 1301         invoke_cmd(buf);
 1302         fclose(mime_fp);
 1303         unlink(mimefile);
 1304 #else
 1305         pclose(mime_fp);
 1306 #endif /* DONT_HAVE_PIPING */
 1307     } else
 1308         perror_message(_(txt_command_failed), ptr);
 1309 
 1310 #ifdef USE_CURSES
 1311     Raw(TRUE);
 1312     InitWin();
 1313 #endif /* USE_CURSES */
 1314     prompt_continue();
 1315 #ifndef USE_CURSES
 1316     Raw(TRUE);
 1317     InitWin();
 1318 #endif /* !USE_CURSES */
 1319 }
 1320 
 1321 
 1322 /*
 1323  * PAGE_HEADER defines the size in lines of this header
 1324  */
 1325 static void
 1326 draw_page_header(
 1327     const char *group)
 1328 {
 1329     char *buf, *tmp;
 1330     int i;
 1331     int whichresp, x_resp;
 1332     int len, right_len, center_pos, cur_pos;
 1333     size_t line_len;
 1334 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
 1335     wchar_t *fmt_resp = NULL, *fmt_thread, *wtmp, *wtmp2, *wbuf;
 1336 #else
 1337     char *tmp2;
 1338 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
 1339 
 1340     whichresp = which_response(this_resp);
 1341     if ((i = which_thread(this_resp)) >= 0)
 1342         x_resp = num_of_responses(i);
 1343     else
 1344         x_resp = 0;
 1345 
 1346     line_len = LEN + 1;
 1347     buf = my_malloc(line_len);
 1348 
 1349     if (!my_strftime(buf, line_len, curr_group->attribute->date_format, localtime(&arts[this_resp].date))) {
 1350         strncpy(buf, BlankIfNull(note_h->date), line_len);
 1351         buf[line_len - 1] = '\0';
 1352     }
 1353 
 1354 #   ifdef HAVE_COLOR
 1355     fcol(tinrc.col_head);
 1356 #   endif /* HAVE_COLOR */
 1357 
 1358     /* first line */
 1359     cur_pos = 0;
 1360 
 1361 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
 1362 
 1363     /* convert to wide-char format string */
 1364     fmt_thread = char2wchar_t(_(txt_thread_x_of_n));
 1365 
 1366     /*
 1367      * Determine the needed space for the text at the right hand margin.
 1368      * The formatting info (%4s) needs 3 positions but we need 4 positions
 1369      * on the screen for each counter.
 1370      */
 1371     if (fmt_thread)
 1372         right_len = wcswidth(fmt_thread, wcslen(fmt_thread)) - 6 + 8;
 1373     else
 1374         right_len = 0;
 1375     FreeIfNeeded(fmt_thread);
 1376 
 1377     /*
 1378      * limit right_len to cCOLS / 3
 1379      */
 1380     if (right_len > cCOLS / 3 + 1)
 1381         right_len = cCOLS / 3 + 1;
 1382 
 1383     /* date */
 1384     if ((wtmp = char2wchar_t(buf)) != NULL) {
 1385         my_fputws(wtmp, stdout);
 1386         cur_pos += wcswidth(wtmp, wcslen(wtmp));
 1387         free(wtmp);
 1388     }
 1389 
 1390     /*
 1391      * determine max len for centered group name
 1392      * allow one space before and after group name
 1393      */
 1394     len = cCOLS - 2 * MAX(cur_pos, right_len) - 3;
 1395 
 1396     /* group name */
 1397     if ((wtmp = char2wchar_t(group)) != NULL) {
 1398         /* wconvert_to_printable(wtmp, FALSE); */
 1399         if (tinrc.abbreviate_groupname)
 1400             wtmp2 = abbr_wcsgroupname(wtmp, len);
 1401         else
 1402             wtmp2 = wstrunc(wtmp, len);
 1403 
 1404         if ((i = wcswidth(wtmp2, wcslen(wtmp2))) < len)
 1405             len = i;
 1406 
 1407         center_pos = (cCOLS - len) / 2;
 1408 
 1409         /* pad out to left */
 1410         for (; cur_pos < center_pos; cur_pos++)
 1411             my_fputc(' ', stdout);
 1412 
 1413         my_fputws(wtmp2, stdout);
 1414         cur_pos += wcswidth(wtmp2, wcslen(wtmp2));
 1415         free(wtmp2);
 1416         free(wtmp);
 1417     }
 1418 
 1419     /* pad out to right */
 1420     for (; cur_pos < cCOLS - right_len - 1; cur_pos++)
 1421         my_fputc(' ', stdout);
 1422 
 1423     /* thread info */
 1424     /* can't eval tin_ltoa() more than once in a statement due to statics */
 1425     strcpy(buf, tin_ltoa(which_thread(this_resp) + 1, 4));
 1426     tmp = strunc(_(txt_thread_x_of_n), cCOLS / 3 - 1);
 1427     my_printf(tmp, buf, tin_ltoa(grpmenu.max, 4));
 1428     free(tmp);
 1429 
 1430     my_fputs(cCRLF, stdout);
 1431 
 1432 #   if 0
 1433     /* display a ruler for layout checking purposes */
 1434     my_fputs("....|....3....|....2....|....1....|....0....|....1....|....2....|....3....|....\n", stdout);
 1435 #   endif /* 0 */
 1436 
 1437     /*
 1438      * second line
 1439      */
 1440     cur_pos = 0;
 1441 
 1442     /*
 1443      * Convert to wide-char format string and determine the needed space
 1444      * for the text at the right hand margin. The formatting info (%4s)
 1445      * needs 3 positions but we need 4 positions on the screen for each
 1446      * counter.
 1447      */
 1448 
 1449     right_len = 0;
 1450     if (whichresp && (fmt_resp = char2wchar_t(_(txt_art_x_of_n)))) {
 1451         right_len = wcswidth(fmt_resp, wcslen(fmt_resp)) - 6 + 8;
 1452     } else {
 1453         if ((!x_resp && (fmt_resp = char2wchar_t(_(txt_no_responses)))) || (x_resp == 1 && (fmt_resp = char2wchar_t(_(txt_1_resp)))))
 1454             right_len = wcswidth(fmt_resp, wcslen(fmt_resp));
 1455         else if ((fmt_resp = char2wchar_t(_(txt_x_resp))))
 1456             right_len = wcswidth(fmt_resp, wcslen(fmt_resp)) - 3 + 4;
 1457     }
 1458     FreeIfNeeded(fmt_resp);
 1459 
 1460     /*
 1461      * limit right_len to cCOLS / 3
 1462      */
 1463     if (right_len > cCOLS / 3 + 1)
 1464         right_len = cCOLS / 3 + 1;
 1465 
 1466     /* line count */
 1467     if (arts[this_resp].line_count < 0)
 1468         strcpy(buf, "?");
 1469     else
 1470         snprintf(buf, line_len, "%-4d", arts[this_resp].line_count);
 1471 
 1472     {
 1473         wchar_t *fmt;
 1474 
 1475         if ((wtmp = char2wchar_t(_(txt_lines))) != NULL) {
 1476             int tex_space = pgart.tex2iso ? 5 : 0;
 1477 
 1478             fmt = wstrunc(wtmp, cCOLS / 3 - 1 - tex_space);
 1479             wtmp = my_realloc(wtmp, sizeof(wchar_t) * line_len);
 1480             swprintf(wtmp, line_len, fmt, buf);
 1481             my_fputws(wtmp, stdout);
 1482             cur_pos += wcswidth(wtmp, wcslen(wtmp));
 1483             free(fmt);
 1484             free(wtmp);
 1485         }
 1486     }
 1487 
 1488 #   ifdef HAVE_COLOR
 1489     fcol(tinrc.col_subject);
 1490 #   endif /* HAVE_COLOR */
 1491 
 1492     /* tex2iso */
 1493     if (pgart.tex2iso) {
 1494         if ((wtmp = char2wchar_t(_(txt_tex))) != NULL) {
 1495             wtmp2 = wstrunc(wtmp, 5);
 1496             my_fputws(wtmp2, stdout);
 1497             cur_pos += wcswidth(wtmp2, wcslen(wtmp2));
 1498             free(wtmp);
 1499             free(wtmp2);
 1500         }
 1501     }
 1502 
 1503     /* subject */
 1504     /*
 1505      * TODO: why do we fall back to arts[this_resp].subject if !note_h->subj?
 1506      *       if !note_h->subj then the article just has no subject, no matter
 1507      *       what the overview says.
 1508      *
 1509      *       add BiDi handling
 1510      */
 1511     strncpy(buf, (note_h->subj ? note_h->subj : arts[this_resp].subject), line_len);
 1512     buf[line_len - 1] = '\0';
 1513     if ((wtmp = char2wchar_t(buf)) != NULL) {
 1514         wbuf = wexpand_tab(wtmp, tabwidth);
 1515         wtmp2 = wstrunc(wbuf, cCOLS - 2 * right_len - 4);
 1516         center_pos = (cCOLS - wcswidth(wtmp2, wcslen(wtmp2))) / 2;
 1517 
 1518         /* pad out to left */
 1519         for (; cur_pos < center_pos; cur_pos++)
 1520             my_fputc(' ', stdout);
 1521 
 1522         StartInverse();
 1523         my_fputws(wtmp2, stdout);
 1524         EndInverse();
 1525         cur_pos += wcswidth(wtmp2, wcslen(wtmp2));
 1526         free(wtmp2);
 1527         free(wtmp);
 1528         free(wbuf);
 1529     }
 1530 
 1531 #   ifdef HAVE_COLOR
 1532     fcol(tinrc.col_response);
 1533 #   endif /* HAVE_COLOR */
 1534 
 1535     /* pad out to right */
 1536     for (; cur_pos < cCOLS - right_len - 1; cur_pos++)
 1537         my_fputc(' ', stdout);
 1538 
 1539     if (whichresp) {
 1540         tmp = strunc(_(txt_art_x_of_n), cCOLS / 3 - 1);
 1541         my_printf(tmp, whichresp + 1, x_resp + 1);
 1542         free(tmp);
 1543     } else {
 1544         /* TODO: ngettext */
 1545         if (!x_resp) {
 1546             tmp = strunc(_(txt_no_responses), cCOLS / 3 - 1);
 1547             my_printf("%s", tmp);
 1548         } else if (x_resp == 1) {
 1549             tmp = strunc(_(txt_1_resp), cCOLS / 3 - 1);
 1550             my_printf("%s", tmp);
 1551         } else {
 1552             tmp = strunc(_(txt_x_resp), cCOLS / 3 - 1);
 1553             my_printf(tmp, x_resp);
 1554         }
 1555         free(tmp);
 1556     }
 1557     my_fputs(cCRLF, stdout);
 1558 
 1559     /*
 1560      * third line
 1561      */
 1562     cur_pos = 0;
 1563 
 1564 #   ifdef HAVE_COLOR
 1565     fcol(tinrc.col_from);
 1566 #   endif /* HAVE_COLOR */
 1567     /* from */
 1568     /*
 1569      * TODO: don't use arts[this_resp].name/arts[this_resp].from
 1570      *       split up note_h->from and use that instead as it might
 1571      *       be different _if_ the overviews are broken
 1572      *
 1573      *       add BiDi handling
 1574      */
 1575     {
 1576         char *p = idna_decode(arts[this_resp].from);
 1577 
 1578         if (arts[this_resp].name)
 1579             snprintf(buf, line_len, "%s <%s>", arts[this_resp].name, p);
 1580         else {
 1581             strncpy(buf, p, line_len);
 1582             buf[line_len - 1] = '\0';
 1583         }
 1584         free(p);
 1585     }
 1586 
 1587     if ((wtmp = char2wchar_t(buf)) != NULL) {
 1588         wtmp2 = wstrunc(wtmp, cCOLS - 1);
 1589         my_fputws(wtmp2, stdout);
 1590         cur_pos += wcswidth(wtmp2, wcslen(wtmp2));
 1591         free(wtmp2);
 1592         free(wtmp);
 1593     }
 1594 
 1595     /*
 1596      * Organization
 1597      *
 1598      * TODO: IDNA decoding, see also comment in
 1599      *       cook.c:cook_article()
 1600      *       add BiDi handling
 1601      */
 1602     if (note_h->org) {
 1603         snprintf(buf, line_len, _(txt_at_s), note_h->org);
 1604 
 1605         if ((wtmp = char2wchar_t(buf)) != NULL) {
 1606             wbuf = wexpand_tab(wtmp, tabwidth);
 1607             wtmp2 = wstrunc(wbuf, cCOLS - cur_pos - 1);
 1608 
 1609             i = cCOLS - wcswidth(wtmp2, wcslen(wtmp2)) - 1;
 1610             for (; cur_pos < i; cur_pos++)
 1611                 my_fputc(' ', stdout);
 1612 
 1613             my_fputws(wtmp2, stdout);
 1614             free(wtmp2);
 1615             free(wtmp);
 1616             free(wbuf);
 1617         }
 1618     }
 1619 
 1620 #else /* !MULTIBYTE_ABLE || NO_LOCALE */
 1621 
 1622     /*
 1623      * determine the needed space for the text at the right hand margin
 1624      * the formatting info (%4s) needs 3 positions but we need 4 positions
 1625      * on the screen for each counter
 1626      */
 1627     right_len = strlen(_(txt_thread_x_of_n)) - 6 + 8;
 1628 
 1629     /* date */
 1630     my_fputs(buf, stdout);
 1631     cur_pos += strlen(buf);
 1632 
 1633     /*
 1634      * determine max len for centered group name
 1635      * allow one space before and after group name
 1636      */
 1637     len = cCOLS - 2 * MAX(cur_pos, right_len) - 3;
 1638 
 1639     /* group name */
 1640     if (tinrc.abbreviate_groupname)
 1641         tmp = abbr_groupname(group, len);
 1642     else
 1643         tmp = strunc(group, len);
 1644 
 1645     if ((i = strlen(tmp)) < len)
 1646         len = i;
 1647 
 1648     center_pos = (cCOLS - len) / 2;
 1649 
 1650     /* pad out to left */
 1651     for (; cur_pos < center_pos; cur_pos++)
 1652         my_fputc(' ', stdout);
 1653 
 1654     my_fputs(tmp, stdout);
 1655     cur_pos += strlen(tmp);
 1656     free(tmp);
 1657 
 1658     /* pad out to right */
 1659     for (; cur_pos < cCOLS - right_len - 1; cur_pos++)
 1660         my_fputc(' ', stdout);
 1661 
 1662     /* thread info */
 1663     /* can't eval tin_ltoa() more than once in a statement due to statics */
 1664     strcpy(buf, tin_ltoa(which_thread(this_resp) + 1, 4));
 1665     my_printf(_(txt_thread_x_of_n), buf, tin_ltoa(grpmenu.max, 4));
 1666 
 1667     my_fputs(cCRLF, stdout);
 1668 
 1669 #   if 0
 1670     /* display a ruler for layout checking purposes */
 1671     my_fputs("....|....3....|....2....|....1....|....0....|....1....|....2....|....3....|....\n", stdout);
 1672 #   endif /* 0 */
 1673 
 1674     /*
 1675      * second line
 1676      */
 1677     cur_pos = 0;
 1678 
 1679     /*
 1680      * determine the needed space for the text at the right hand margin
 1681      * the formatting info (%4s) needs 3 positions but we need 4 positions
 1682      * on the screen for each counter
 1683      */
 1684     if (whichresp) {
 1685         right_len = strlen(_(txt_art_x_of_n)) - 6 + 8;
 1686     } else {
 1687         if (!x_resp)
 1688             right_len = strlen(_(txt_no_responses));
 1689         else if (x_resp == 1)
 1690             right_len = strlen(_(txt_1_resp));
 1691         else
 1692             right_len = strlen(_(txt_x_resp)) - 3 + 4;
 1693     }
 1694 
 1695     /* line count */
 1696     /* an accurate line count will appear in the footer anymay */
 1697     if (arts[this_resp].line_count < 0)
 1698         strcpy(buf, "?");
 1699     else
 1700         snprintf(buf, line_len, "%-4d", arts[this_resp].line_count);
 1701 
 1702     tmp = my_malloc(line_len);
 1703     snprintf(tmp, line_len, _(txt_lines), buf);
 1704     my_fputs(tmp, stdout);
 1705     cur_pos += strlen(tmp);
 1706     free(tmp);
 1707 
 1708 #   ifdef HAVE_COLOR
 1709     fcol(tinrc.col_subject);
 1710 #   endif /* HAVE_COLOR */
 1711 
 1712     /* tex2iso */
 1713     if (pgart.tex2iso) {
 1714         my_fputs(_(txt_tex), stdout);
 1715         cur_pos += strlen(_(txt_tex));
 1716     }
 1717 
 1718     /* subject */
 1719     /*
 1720      * TODO: why do we fall back to arts[this_resp].subject if !note_h->subj?
 1721      *       if !note_h->subj then the article just has no subject, no matter
 1722      *       what the overview says.
 1723      */
 1724     strncpy(buf, (note_h->subj ? note_h->subj : arts[this_resp].subject), line_len);
 1725     buf[line_len - 1] = '\0';
 1726 
 1727     tmp2 = expand_tab(buf, tabwidth);
 1728     tmp = strunc(tmp2, cCOLS - 2 * right_len - 3);
 1729 
 1730     center_pos = (cCOLS - strlen(tmp)) / 2;
 1731 
 1732     /* pad out to left */
 1733     for (; cur_pos < center_pos; cur_pos++)
 1734         my_fputc(' ', stdout);
 1735 
 1736     StartInverse();
 1737     my_fputs(tmp, stdout);
 1738     EndInverse();
 1739     cur_pos += strlen(tmp);
 1740     free(tmp);
 1741     free(tmp2);
 1742 
 1743 #   ifdef HAVE_COLOR
 1744     fcol(tinrc.col_response);
 1745 #   endif /* HAVE_COLOR */
 1746 
 1747     /* pad out to right */
 1748     for (; cur_pos < cCOLS - right_len - 1; cur_pos++)
 1749         my_fputc(' ', stdout);
 1750 
 1751     if (whichresp)
 1752         my_printf(_(txt_art_x_of_n), whichresp + 1, x_resp + 1);
 1753     else {
 1754         /* TODO: ngettext */
 1755         if (!x_resp)
 1756             my_printf("%s", _(txt_no_responses));
 1757         else if (x_resp == 1)
 1758             my_printf("%s", _(txt_1_resp));
 1759         else
 1760             my_printf(_(txt_x_resp), x_resp);
 1761     }
 1762     my_fputs(cCRLF, stdout);
 1763 
 1764     /*
 1765      * third line
 1766      */
 1767     cur_pos = 0;
 1768 
 1769 #   ifdef HAVE_COLOR
 1770     fcol(tinrc.col_from);
 1771 #   endif /* HAVE_COLOR */
 1772     /* from */
 1773     /*
 1774      * TODO: don't use arts[this_resp].name/arts[this_resp].from
 1775      *       split up note_h->from and use that instead as it might
 1776      *       be different _if_ the overviews are broken
 1777      */
 1778     if (arts[this_resp].name)
 1779         snprintf(buf, line_len, "%s <%s>", arts[this_resp].name, arts[this_resp].from);
 1780     else {
 1781         strncpy(buf, arts[this_resp].from, line_len);
 1782         buf[line_len - 1] = '\0';
 1783     }
 1784 
 1785     tmp = strunc(buf, cCOLS - 1);
 1786     my_fputs(tmp, stdout);
 1787     cur_pos += strlen(tmp);
 1788     free(tmp);
 1789 
 1790     if (note_h->org && cCOLS - cur_pos - 1 >= (int) strlen(_(txt_at_s)) - 2 + 3) {
 1791         /* we have enough space to print at least " at ..." */
 1792         snprintf(buf, line_len, _(txt_at_s), note_h->org);
 1793 
 1794         tmp2 = expand_tab(buf, tabwidth);
 1795         tmp = strunc(tmp2, cCOLS - cur_pos - 1);
 1796         len = cCOLS - (int) strlen(tmp) - 1;
 1797         for (; cur_pos < len; cur_pos++)
 1798             my_fputc(' ', stdout);
 1799         my_fputs(tmp, stdout);
 1800         free(tmp);
 1801         free(tmp2);
 1802     }
 1803 
 1804 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
 1805 
 1806     my_fputs(cCRLF, stdout);
 1807     my_fputs(cCRLF, stdout);
 1808 
 1809     free(buf);
 1810 
 1811 #ifdef HAVE_COLOR
 1812     fcol(tinrc.col_normal);
 1813 #endif /* HAVE_COLOR */
 1814 }
 1815 
 1816 
 1817 /*
 1818  * Change the pager article context to arts[new_respnum]
 1819  * Return GRP_ARTUNAVAIL if article could not be opened
 1820  * or GRP_ARTABORT if load of article was interrupted
 1821  * or 0 on success
 1822  */
 1823 static int
 1824 load_article(
 1825     int new_respnum,
 1826     struct t_group *group)
 1827 {
 1828     static t_bool art_closed = FALSE;
 1829 
 1830 #ifdef DEBUG
 1831     if (debug & DEBUG_MISC)
 1832         fprintf(stderr, "load_art %s(new=%d, curr=%d)\n", (new_respnum == this_resp && !art_closed) ? "ALREADY OPEN!" : "", new_respnum, this_resp);
 1833 #endif /* DEBUG */
 1834 
 1835     if (new_respnum != this_resp || art_closed) {
 1836         int ret;
 1837 
 1838         art_close(&pgart);          /* close previously opened art in pager */
 1839         ret = art_open(TRUE, &arts[new_respnum], group, &pgart, TRUE, _(txt_reading_article));
 1840 
 1841         switch (ret) {
 1842             case ART_UNAVAILABLE:
 1843                 art_mark(group, &arts[new_respnum], ART_READ);
 1844                 /* prevent retagging as unread in unfilter_articles() */
 1845                 if (arts[new_respnum].killed == ART_KILLED_UNREAD)
 1846                     arts[new_respnum].killed = ART_KILLED;
 1847                 art_closed = TRUE;
 1848                 wait_message(1, _(txt_art_unavailable));
 1849                 return GRP_ARTUNAVAIL;
 1850 
 1851             case ART_ABORT:
 1852                 art_close(&pgart);
 1853                 art_closed = TRUE;
 1854                 return GRP_ARTABORT;    /* special retcode to stop redrawing screen */
 1855 
 1856             default:                    /* Normal case */
 1857 #if 0           /* Very useful debugging tool */
 1858                 if (prompt_yn("Fake art unavailable? ", FALSE) == 1) {
 1859                     art_close(&pgart);
 1860                     art_mark(group, &arts[new_respnum], ART_READ);
 1861                     art_closed = TRUE;
 1862                     return GRP_ARTUNAVAIL;
 1863                 }
 1864 #endif /* 0 */
 1865                 if (art_closed)
 1866                     art_closed = FALSE;
 1867                 if (new_respnum != this_resp) {
 1868                     /*
 1869                      * Remember current & previous articles for '-' command
 1870                      */
 1871                     last_resp = this_resp;
 1872                     this_resp = new_respnum;        /* Set new art globally */
 1873                 }
 1874                 break;
 1875         }
 1876     } else if (show_all_headers) {
 1877         /*
 1878          * article is already opened with show_all_headers ON
 1879          * -> re-cook it
 1880          */
 1881         show_all_headers = FALSE;
 1882         resize_article(TRUE, &pgart);
 1883     }
 1884 
 1885     art_mark(group, &arts[this_resp], ART_READ);
 1886 
 1887     /*
 1888      * Change status if art was unread before killing to
 1889      * prevent retagging as unread in unfilter_articles()
 1890      */
 1891     if (arts[this_resp].killed == ART_KILLED_UNREAD)
 1892         arts[this_resp].killed = ART_KILLED;
 1893 
 1894     if (pgart.cooked == NULL) { /* harmony corruption */
 1895         wait_message(1, _(txt_art_unavailable));
 1896         return GRP_ARTUNAVAIL;
 1897     }
 1898 
 1899     /*
 1900      * Setup to start viewing cooked version
 1901      */
 1902     show_raw_article = FALSE;
 1903     show_all_headers = FALSE;
 1904     curr_line = 0;
 1905     note_fp = pgart.cooked;
 1906     artline = pgart.cookl;
 1907     artlines = pgart.cooked_lines;
 1908     search_line = 0;
 1909     /*
 1910      * Reset offsets only if not invoked during 'body search' (srch_lineno != -1)
 1911      * otherwise the found string will not be highlighted
 1912      */
 1913     if (srch_lineno == -1)
 1914         reset_srch_offsets();
 1915     rotate = 0;         /* normal mode, not rot13 */
 1916     reveal_ctrl_l = FALSE;
 1917     reveal_ctrl_l_lines = -1;   /* all ^L's active */
 1918     hide_uue = tinrc.hide_uue;
 1919 
 1920     draw_page(group->name, 0);
 1921 
 1922     /*
 1923      * Automatically invoke attachment viewing if requested
 1924      */
 1925     if (!note_h->mime || IS_PLAINTEXT(note_h->ext))     /* Text only article */
 1926         return 0;
 1927 
 1928     if (*tinrc.metamail_prog == '\0' || getenv("NOMETAMAIL") != NULL)   /* Viewer turned off */
 1929         return 0;
 1930 
 1931     if (group->attribute->ask_for_metamail) {
 1932         if (prompt_yn(_(txt_use_mime), TRUE) != 1)
 1933             return 0;
 1934     }
 1935 
 1936     XFACE_SUPPRESS();
 1937     if (strcmp(tinrc.metamail_prog, INTERNAL_CMD) == 0) /* Use internal viewer */
 1938         decode_save_mime(&pgart, FALSE);
 1939     else
 1940         invoke_metamail(pgart.raw);
 1941     XFACE_SHOW();
 1942     return 0;
 1943 }
 1944 
 1945 
 1946 static int
 1947 prompt_response(
 1948     int ch,
 1949     int curr_respnum)
 1950 {
 1951     int i, num;
 1952 
 1953     clear_message();
 1954 
 1955     if ((num = prompt_num(ch, _(txt_select_art))) < 0) {
 1956         clear_message();
 1957         return -1;
 1958     }
 1959 
 1960     if ((i = which_thread(curr_respnum)) >= 0)
 1961         return find_response(i, num - 1);
 1962     else
 1963         return -1;
 1964 }
 1965 
 1966 
 1967 /*
 1968  * Reposition within message as needed, highlight searched string
 1969  * This is tied quite closely to the information stored by
 1970  * get_search_vectors()
 1971  */
 1972 static void
 1973 process_search(
 1974     int *lcurr_line,
 1975     size_t message_lines,
 1976     size_t screen_lines,
 1977     int help_level)
 1978 {
 1979     int i, start, end;
 1980 
 1981     if ((i = get_search_vectors(&start, &end)) == -1)
 1982         return;
 1983 
 1984     /*
 1985      * Is matching line off the current view?
 1986      * Reposition within article if needed, try to get matched line
 1987      * in the middle of the screen
 1988      */
 1989     if (i < *lcurr_line || i >= (int) ((size_t) *lcurr_line + screen_lines)) {
 1990         *lcurr_line = (int) ((size_t) i - (screen_lines / 2));
 1991         if (((size_t) *lcurr_line + screen_lines) > message_lines)  /* off the end */
 1992             *lcurr_line = (int) (message_lines - screen_lines);
 1993         /* else pos. is just fine */
 1994     }
 1995 
 1996     switch (help_level) {
 1997         case PAGE_LEVEL:
 1998             draw_page(curr_group->name, 0);
 1999             break;
 2000 
 2001         case INFO_PAGER:
 2002             display_info_page(0);
 2003             break;
 2004 
 2005         default:
 2006             break;
 2007     }
 2008     search_line = i;                                /* draw_page() resets this to 0 */
 2009 
 2010     /*
 2011      * Highlight found string
 2012      */
 2013     highlight_string(i - *lcurr_line + scroll_region_top, start, end - start);
 2014 }
 2015 
 2016 
 2017 /*
 2018  * Implement ^H toggle between cooked and raw views of article
 2019  */
 2020 void
 2021 toggle_raw(
 2022     struct t_group *group)
 2023 {
 2024     if (show_raw_article) {
 2025         artline = pgart.cookl;
 2026         artlines = pgart.cooked_lines;
 2027         note_fp = pgart.cooked;
 2028     } else {
 2029         static int j;               /* Needed on successive invocations */
 2030         int chunk = note_h->ext->line_count;
 2031 
 2032         /*
 2033          * We do this on the fly, since most of the time it won't be used
 2034          */
 2035         if (!pgart.rawl) {          /* Already done this for this article? */
 2036             char *line;
 2037             char *p;
 2038             long offset;
 2039 
 2040             j = 0;
 2041             rewind(pgart.raw);
 2042             pgart.rawl = my_malloc(sizeof(t_lineinfo) * (size_t) chunk);
 2043             offset = ftell(pgart.raw);
 2044 
 2045             while ((line = tin_fgets(pgart.raw, FALSE)) != NULL) {
 2046                 int space;
 2047 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
 2048                 int num_bytes;
 2049                 wchar_t wc;
 2050 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
 2051 
 2052                 pgart.rawl[j].offset = offset;
 2053                 pgart.rawl[j].flags = 0;
 2054                 j++;
 2055                 if (j >= chunk) {
 2056                     chunk += 50;
 2057                     pgart.rawl = my_realloc(pgart.rawl, sizeof(t_lineinfo) * (size_t) chunk);
 2058                 }
 2059 
 2060                 p = line;
 2061                 while (*p) {
 2062                     space = cCOLS - 1;
 2063 
 2064                     while ((space > 0) && *p) {
 2065 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
 2066                         num_bytes = mbtowc(&wc, p, MB_CUR_MAX);
 2067                         if (num_bytes != -1 && iswprint((wint_t) wc)) {
 2068                             if ((space -= wcwidth(wc)) < 0)
 2069                                 break;
 2070                             p += num_bytes;
 2071                             offset += num_bytes;
 2072                         }
 2073 #else
 2074                         if (my_isprint((unsigned char) *p)) {
 2075                             space--;
 2076                             p++;
 2077                             offset++;
 2078                         }
 2079 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
 2080                         else if (IS_LOCAL_CHARSET("Big5") && (unsigned char) *p >= 0xa1 && (unsigned char) *p <= 0xfe && *(p + 1)) {
 2081                             /*
 2082                              * Big5: ASCII chars are handled by the normal code
 2083                              * check only for 2-byte chars
 2084                              * TODO: should we also check if the second byte is
 2085                              * also valid?
 2086                              */
 2087                             p += 2;
 2088                             offset += 2;
 2089                             space--;
 2090                         } else {
 2091                             /*
 2092                              * the current character can't be displayed print it as
 2093                              * an octal value (needs 4 columns) see also
 2094                              * color.c:draw_pager_line()
 2095                              */
 2096                             if ((space -= 4) < 0)
 2097                                 break;
 2098                             offset++;
 2099                             p++;
 2100                         }
 2101                     }
 2102                     /*
 2103                      * if we reached the end of the line we don't need to
 2104                      * remember anything
 2105                      */
 2106                     if (*p) {
 2107                         pgart.rawl[j].offset = offset;
 2108                         pgart.rawl[j].flags = 0;
 2109                         if (++j >= chunk) {
 2110                             chunk += 50;
 2111                             pgart.rawl = my_realloc(pgart.rawl, sizeof(t_lineinfo) * (size_t) chunk);
 2112                         }
 2113                     }
 2114                 }
 2115 
 2116                 /*
 2117                  * only use ftell's return value here because we didn't
 2118                  * take the \n into account above.
 2119                  */
 2120                 offset = ftell(pgart.raw);
 2121             }
 2122 
 2123             pgart.rawl = my_realloc(pgart.rawl, sizeof(t_lineinfo) * (size_t) j);
 2124         }
 2125         artline = pgart.rawl;
 2126         artlines = j;
 2127         note_fp = pgart.raw;
 2128     }
 2129     curr_line = 0;
 2130     show_raw_article = bool_not(show_raw_article);
 2131     draw_page(group ? group->name : "", 0);
 2132 }
 2133 
 2134 
 2135 /*
 2136  * Re-cook an article
 2137  */
 2138 void
 2139 resize_article(
 2140     t_bool wrap_lines,
 2141     t_openartinfo *artinfo)
 2142 {
 2143     free(artinfo->cookl);
 2144     if (artinfo->cooked)
 2145         fclose(artinfo->cooked);
 2146 
 2147     if (!cook_article(wrap_lines, artinfo, hide_uue, show_all_headers))
 2148         tin_done(EXIT_FAILURE, _(txt_cook_article_failed_exiting), tin_progname);
 2149 
 2150     show_raw_article = FALSE;
 2151     artline = pgart.cookl;
 2152     artlines = pgart.cooked_lines;
 2153     note_fp = pgart.cooked;
 2154 }
 2155 
 2156 
 2157 /*
 2158  * Infopager: simply page files
 2159  */
 2160 void
 2161 info_pager(
 2162     FILE *info_fh,
 2163     const char *title,
 2164     t_bool wrap_at_ends)
 2165 {
 2166     int offset;
 2167     t_function func;
 2168 
 2169     search_line = 0;
 2170     reset_srch_offsets();
 2171     info_file = info_fh;
 2172     info_title = title;
 2173     curr_info_line = 0;
 2174     preprocess_info_message(info_fh);
 2175     if (!info_fh)
 2176         return;
 2177     set_xclick_off();
 2178     display_info_page(0);
 2179 
 2180     forever {
 2181         switch (func = handle_keypad(page_left, page_right, page_mouse_action, info_keys)) {
 2182             case GLOBAL_ABORT:  /* common arrow keys */
 2183                 break;
 2184 
 2185             case GLOBAL_LINE_UP:
 2186                 if (num_info_lines <= NOTESLINES) {
 2187                     info_message(_(txt_begin_of_page));
 2188                     break;
 2189                 }
 2190                 if (curr_info_line == 0) {
 2191                     if (!wrap_at_ends) {
 2192                         info_message(_(txt_begin_of_page));
 2193                         break;
 2194                     }
 2195                     curr_info_line = num_info_lines - NOTESLINES;
 2196                     display_info_page(0);
 2197                     break;
 2198                 }
 2199                 offset = scroll_page(KEYMAP_UP);
 2200                 curr_info_line += offset;
 2201                 display_info_page(offset);
 2202                 break;
 2203 
 2204             case GLOBAL_LINE_DOWN:
 2205                 if (num_info_lines <= NOTESLINES) {
 2206                     info_message(_(txt_end_of_page));
 2207                     break;
 2208                 }
 2209                 if (curr_info_line + NOTESLINES >= num_info_lines) {
 2210                     if (!wrap_at_ends) {
 2211                         info_message(_(txt_end_of_page));
 2212                         break;
 2213                     }
 2214                     curr_info_line = 0;
 2215                     display_info_page(0);
 2216                     break;
 2217                 }
 2218                 offset = scroll_page(KEYMAP_DOWN);
 2219                 curr_info_line += offset;
 2220                 display_info_page(offset);
 2221                 break;
 2222 
 2223             case GLOBAL_PAGE_DOWN:
 2224                 if (num_info_lines <= NOTESLINES) {
 2225                     info_message(_(txt_end_of_page));
 2226                     break;
 2227                 }
 2228                 if (curr_info_line + NOTESLINES >= num_info_lines) {    /* End is already on screen */
 2229                     if (!wrap_at_ends) {
 2230                         info_message(_(txt_end_of_page));
 2231                         break;
 2232                     }
 2233                     curr_info_line = 0;
 2234                     display_info_page(0);
 2235                     break;
 2236                 }
 2237                 curr_info_line += ((tinrc.scroll_lines == -2) ? NOTESLINES / 2 : NOTESLINES);
 2238                 display_info_page(0);
 2239                 break;
 2240 
 2241             case GLOBAL_PAGE_UP:
 2242                 if (num_info_lines <= NOTESLINES) {
 2243                     info_message(_(txt_begin_of_page));
 2244                     break;
 2245                 }
 2246                 if (curr_info_line == 0) {
 2247                     if (!wrap_at_ends) {
 2248                         info_message(_(txt_begin_of_page));
 2249                         break;
 2250                     }
 2251                     curr_info_line = num_info_lines - NOTESLINES;
 2252                     display_info_page(0);
 2253                     break;
 2254                 }
 2255                 curr_info_line -= ((tinrc.scroll_lines == -2) ? NOTESLINES / 2 : NOTESLINES);
 2256                 display_info_page(0);
 2257                 break;
 2258 
 2259             case GLOBAL_FIRST_PAGE:
 2260                 if (curr_info_line) {
 2261                     curr_info_line = 0;
 2262                     display_info_page(0);
 2263                 }
 2264                 break;
 2265 
 2266             case GLOBAL_LAST_PAGE:
 2267                 if (curr_info_line + NOTESLINES != num_info_lines) {
 2268                     /* Display a full last page for neatness */
 2269                     curr_info_line = num_info_lines - NOTESLINES;
 2270                     display_info_page(0);
 2271                 }
 2272                 break;
 2273 
 2274             case GLOBAL_TOGGLE_HELP_DISPLAY:
 2275                 toggle_mini_help(INFO_PAGER);
 2276                 display_info_page(0);
 2277                 break;
 2278 
 2279             case GLOBAL_SEARCH_SUBJECT_FORWARD:
 2280             case GLOBAL_SEARCH_SUBJECT_BACKWARD:
 2281             case GLOBAL_SEARCH_REPEAT:
 2282                 if (func == GLOBAL_SEARCH_REPEAT && last_search != GLOBAL_SEARCH_SUBJECT_FORWARD && last_search != GLOBAL_SEARCH_SUBJECT_BACKWARD)
 2283                     break;
 2284 
 2285                 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)
 2286                     break;
 2287 
 2288                 process_search(&curr_info_line, (size_t) num_info_lines, (size_t) NOTESLINES, INFO_PAGER);
 2289                 break;
 2290 
 2291             case GLOBAL_QUIT:   /* quit */
 2292                 ClearScreen();
 2293                 return;
 2294 
 2295             default:
 2296                 break;
 2297         }
 2298     }
 2299 }
 2300 
 2301 
 2302 /*
 2303  * Redraw the current page, curr_info_line will be the first line displayed
 2304  * If part is !=0, then only draw the first (-ve) or last (+ve) few lines
 2305  */
 2306 void
 2307 display_info_page(
 2308     int part)
 2309 {
 2310     int start, end; /* 1st, last line to draw */
 2311 
 2312     signal_context = cInfopager;
 2313 
 2314     /*
 2315      * Can't do partial draw if term can't scroll properly
 2316      */
 2317     if (part && !have_linescroll)
 2318         part = 0;
 2319 
 2320     if (curr_info_line < 0)
 2321         curr_info_line = 0;
 2322     if (curr_info_line >= num_info_lines)
 2323         curr_info_line = num_info_lines - 1;
 2324 
 2325     scroll_region_top = INDEX_TOP;
 2326 
 2327     /* Down-scroll, only redraw bottom 'part' lines of screen */
 2328     if ((start = (part > 0) ? NOTESLINES - part : 0) < 0)
 2329         start = 0;
 2330 
 2331     /* Up-scroll, only redraw the top 'part' lines of screen */
 2332     if ((end = (part < 0) ? -part : NOTESLINES) > NOTESLINES)
 2333         end = NOTESLINES;
 2334 
 2335     /* Print title */
 2336     if ((end - start >= NOTESLINES) || (part == 0)) {
 2337         ClearScreen();
 2338         center_line(0, TRUE, info_title);
 2339     }
 2340 
 2341     print_message_page(info_file, infoline, (size_t) num_info_lines, (size_t) curr_info_line, (size_t) start, (size_t) end, INFO_PAGER);
 2342 
 2343     /* print footer */
 2344     draw_percent_mark(curr_info_line + (curr_info_line + NOTESLINES < num_info_lines ? NOTESLINES : num_info_lines - curr_info_line), num_info_lines);
 2345     stow_cursor();
 2346 }
 2347 
 2348 
 2349 static void
 2350 preprocess_info_message(
 2351     FILE *info_fh)
 2352 {
 2353     int chunk = 50;
 2354 
 2355     FreeAndNull(infoline);
 2356     if (!info_fh)
 2357         return;
 2358 
 2359     rewind(info_fh);
 2360     infoline = my_malloc(sizeof(t_lineinfo) * (size_t) chunk);
 2361     num_info_lines = 0;
 2362 
 2363     do {
 2364         infoline[num_info_lines].offset = ftell(info_fh);
 2365         infoline[num_info_lines].flags = 0;
 2366         num_info_lines++;
 2367         if (num_info_lines >= chunk) {
 2368             chunk += 50;
 2369             infoline = my_realloc(infoline, sizeof(t_lineinfo) * (size_t) chunk);
 2370         }
 2371     } while (tin_fgets(info_fh, FALSE) != NULL);
 2372 
 2373     num_info_lines--;
 2374     infoline = my_realloc(infoline, sizeof(t_lineinfo) * (size_t) num_info_lines);
 2375 }
 2376 
 2377 
 2378 /*
 2379  * URL menu
 2380  */
 2381 static t_function
 2382 url_left(
 2383     void)
 2384 {
 2385     return GLOBAL_QUIT;
 2386 }
 2387 
 2388 
 2389 static t_function
 2390 url_right(
 2391     void)
 2392 {
 2393     return URL_SELECT;
 2394 }
 2395 
 2396 
 2397 static void
 2398 show_url_page(
 2399     void)
 2400 {
 2401     int i;
 2402 
 2403     signal_context = cURL;
 2404     currmenu = &urlmenu;
 2405     mark_offset = 0;
 2406 
 2407     if (urlmenu.curr < 0)
 2408         urlmenu.curr = 0;
 2409 
 2410     ClearScreen();
 2411     set_first_screen_item();
 2412     center_line(0, TRUE, _(txt_url_menu));
 2413 
 2414     for (i = urlmenu.first; i < urlmenu.first + NOTESLINES && i < urlmenu.max; ++i)
 2415         build_url_line(i);
 2416 
 2417     show_mini_help(URL_LEVEL);
 2418 
 2419     draw_url_arrow();
 2420 }
 2421 
 2422 
 2423 static t_bool
 2424 url_page(
 2425     void)
 2426 {
 2427     char key[MAXKEYLEN];
 2428     t_function func;
 2429     t_menu *oldmenu = NULL;
 2430 
 2431     if (currmenu)
 2432         oldmenu = currmenu;
 2433     urlmenu.curr = 0;
 2434     urlmenu.max = build_url_list();
 2435     if (urlmenu.max == 0)
 2436         return FALSE;
 2437 
 2438     clear_note_area();
 2439     show_url_page();
 2440     set_xclick_off();
 2441 
 2442     forever {
 2443         switch ((func = handle_keypad(url_left, url_right, NULL, url_keys))) {
 2444             case GLOBAL_QUIT:
 2445                 free_url_list();
 2446                 if (oldmenu)
 2447                     currmenu = oldmenu;
 2448                 return TRUE;
 2449 
 2450             case DIGIT_1:
 2451             case DIGIT_2:
 2452             case DIGIT_3:
 2453             case DIGIT_4:
 2454             case DIGIT_5:
 2455             case DIGIT_6:
 2456             case DIGIT_7:
 2457             case DIGIT_8:
 2458             case DIGIT_9:
 2459                 if (urlmenu.max)
 2460                     prompt_item_num(func_to_key(func, url_keys), _(txt_url_select));
 2461                 break;
 2462 
 2463 #ifndef NO_SHELL_ESCAPE
 2464             case GLOBAL_SHELL_ESCAPE:
 2465                 do_shell_escape();
 2466                 break;
 2467 #endif /* !NO_SHELL_ESCAPE */
 2468 
 2469             case GLOBAL_HELP:
 2470                 show_help_page(URL_LEVEL, _(txt_url_menu_com));
 2471                 show_url_page();
 2472                 break;
 2473 
 2474             case GLOBAL_FIRST_PAGE:
 2475                 top_of_list();
 2476                 break;
 2477 
 2478             case GLOBAL_LAST_PAGE:
 2479                 end_of_list();
 2480                 break;
 2481 
 2482             case GLOBAL_REDRAW_SCREEN:
 2483                 my_retouch();
 2484                 show_url_page();
 2485                 break;
 2486 
 2487             case GLOBAL_LINE_DOWN:
 2488                 move_down();
 2489                 break;
 2490 
 2491             case GLOBAL_LINE_UP:
 2492                 move_up();
 2493                 break;
 2494 
 2495             case GLOBAL_PAGE_DOWN:
 2496                 page_down();
 2497                 break;
 2498 
 2499             case GLOBAL_PAGE_UP:
 2500                 page_up();
 2501                 break;
 2502 
 2503             case GLOBAL_SCROLL_DOWN:
 2504                 scroll_down();
 2505                 break;
 2506 
 2507             case GLOBAL_SCROLL_UP:
 2508                 scroll_up();
 2509                 break;
 2510 
 2511             case GLOBAL_TOGGLE_HELP_DISPLAY:
 2512                 toggle_mini_help(URL_LEVEL);
 2513                 show_url_page();
 2514                 break;
 2515 
 2516             case GLOBAL_TOGGLE_INFO_LAST_LINE:
 2517                 tinrc.info_in_last_line = bool_not(tinrc.info_in_last_line);
 2518                 show_url_page();
 2519                 break;
 2520 
 2521             case URL_SELECT:
 2522                 if (urlmenu.max) {
 2523                     if (process_url(urlmenu.curr))
 2524                         show_url_page();
 2525                     else
 2526                         draw_url_arrow();
 2527                 }
 2528                 break;
 2529 
 2530             case GLOBAL_SEARCH_SUBJECT_FORWARD:
 2531             case GLOBAL_SEARCH_SUBJECT_BACKWARD:
 2532             case GLOBAL_SEARCH_REPEAT:
 2533                 if (func == GLOBAL_SEARCH_REPEAT && last_search != GLOBAL_SEARCH_SUBJECT_FORWARD && last_search != GLOBAL_SEARCH_SUBJECT_BACKWARD)
 2534                     info_message(_(txt_no_prev_search));
 2535                 else if (urlmenu.max) {
 2536                     int new_pos, old_pos = urlmenu.curr;
 2537 
 2538                     new_pos = generic_search((func == GLOBAL_SEARCH_SUBJECT_FORWARD), (func == GLOBAL_SEARCH_REPEAT), urlmenu.curr, urlmenu.max - 1, URL_LEVEL);
 2539                     if (new_pos != old_pos)
 2540                         move_to_item(new_pos);
 2541                 }
 2542                 break;
 2543 
 2544             default:
 2545                 info_message(_(txt_bad_command), PrintFuncKey(key, GLOBAL_HELP, url_keys));
 2546                 break;
 2547         }
 2548     }
 2549 }
 2550 
 2551 
 2552 static void
 2553 draw_url_arrow(
 2554     void)
 2555 {
 2556     draw_arrow_mark(INDEX_TOP + urlmenu.curr - urlmenu.first);
 2557     if (tinrc.info_in_last_line) {
 2558         t_url *lptr;
 2559 
 2560         lptr = find_url(urlmenu.curr);
 2561         info_message("%s", lptr->url);
 2562     } else if (urlmenu.curr == urlmenu.max - 1)
 2563         info_message(_(txt_end_of_urls));
 2564 }
 2565 
 2566 
 2567 t_url *
 2568 find_url(
 2569     int n)
 2570 {
 2571     t_url *lptr;
 2572 
 2573     lptr = url_list;
 2574     while (n-- > 0 && lptr->next)
 2575         lptr = lptr->next;
 2576 
 2577     return lptr;
 2578 }
 2579 
 2580 
 2581 static void
 2582 build_url_line(
 2583     int i)
 2584 {
 2585     char *sptr;
 2586     int len = cCOLS - 9;
 2587     t_url *lptr;
 2588 
 2589 #ifdef USE_CURSES
 2590     /*
 2591      * Allocate line buffer
 2592      * make it the same size like in !USE_CURSES case to simplify some code
 2593      */
 2594     sptr = my_malloc(cCOLS + 2);
 2595 #else
 2596     sptr = screen[INDEX2SNUM(i)].col;
 2597 #endif /* USE_CURSES */
 2598 
 2599     lptr = find_url(i);
 2600     snprintf(sptr, (size_t) cCOLS, "  %s  %-*.*s%s", tin_ltoa(i + 1, 4), len, len, lptr->url, cCRLF);
 2601     WriteLine(INDEX2LNUM(i), sptr);
 2602 
 2603 #ifdef USE_CURSES
 2604     free(sptr);
 2605 #endif /* USE_CURSES */
 2606 }
 2607 
 2608 
 2609 static t_bool
 2610 process_url(
 2611     int n)
 2612 {
 2613     char *url, *url_esc;
 2614     size_t len;
 2615     t_url *lptr;
 2616 
 2617     lptr = find_url(n);
 2618     len = strlen(lptr->url) << 1; /* double size; room for editing URL */
 2619     url = my_malloc(len + 1);
 2620     if (prompt_default_string("URL:", url, (int) len, lptr->url, HIST_URL)) {
 2621         if (!*url) {            /* Don't try and open nothing */
 2622             free(url);
 2623             return FALSE;
 2624         }
 2625         wait_message(2, _(txt_url_open), url);
 2626         url_esc = escape_shell_meta(url, no_quote);
 2627         len = strlen(url_esc) + strlen(tinrc.url_handler) + 2;
 2628         url = my_realloc(url, len);
 2629         snprintf(url, len, "%s %s", tinrc.url_handler, url_esc);
 2630         invoke_cmd(url);
 2631         free(url);
 2632         cursoroff();
 2633         return TRUE;
 2634     }
 2635     free(url);
 2636     return FALSE;
 2637 }
 2638 
 2639 
 2640 static int
 2641 build_url_list(
 2642     void)
 2643 {
 2644     char *ptr;
 2645     int i, count = 0;
 2646     REGEX_SIZE *offsets = NULL;
 2647     t_url *lptr = NULL;
 2648 
 2649     for (i = 0; i < artlines; ++i) {
 2650         if (!(artline[i].flags & (C_URL | C_NEWS | C_MAIL)))
 2651             continue;
 2652 
 2653         /*
 2654          * Line contains a URL, so read it in
 2655          */
 2656         if (fseek(pgart.cooked, artline[i].offset, SEEK_SET) == -1) /* skip on error */
 2657             continue;
 2658         if ((ptr = tin_fgets(pgart.cooked, FALSE)) == NULL)
 2659             continue;
 2660 
 2661         /*
 2662          * Step through, finding URL's
 2663          */
 2664         forever {
 2665             /* any matches left? */
 2666             if (match_regex_ex(ptr, (int) strlen(ptr), 0, 0, &url_regex) >= 0) {
 2667                 offsets = regex_get_ovector_pointer(&url_regex);
 2668             } else if (match_regex_ex(ptr, (int) strlen(ptr), 0, 0, &mail_regex) >= 0) {
 2669                 offsets = regex_get_ovector_pointer(&mail_regex);
 2670             } else if (match_regex_ex(ptr, (int) strlen(ptr), 0, 0, &news_regex) >= 0) {
 2671                 offsets = regex_get_ovector_pointer(&news_regex);
 2672             } else {
 2673                 break;
 2674             }
 2675 
 2676             *(ptr + offsets[1]) = '\0';
 2677 
 2678             if (!lptr)
 2679                 lptr = url_list = my_malloc(sizeof(t_url));
 2680             else {
 2681                 lptr->next = my_malloc(sizeof(t_url));
 2682                 lptr = lptr->next;
 2683             }
 2684             lptr->url = my_strdup(ptr + offsets[0]);
 2685             lptr->next = NULL;
 2686             ++count;
 2687 
 2688             ptr += offsets[1] + 1;
 2689         }
 2690     }
 2691     return count;
 2692 }
 2693 
 2694 
 2695 static void
 2696 free_url_list(
 2697     void)
 2698 {
 2699     t_url *p, *q;
 2700 
 2701     for (p = url_list; p != NULL; p = q) {
 2702         q = p->next;
 2703         free(p->url);
 2704         free(p);
 2705     }
 2706     url_list = NULL;
 2707 }
 2708 
 2709 
 2710 static void
 2711 draw_percent_mark(
 2712     long cur_num,
 2713     long max_num)
 2714 {
 2715     char buf[32]; /* FIXME: may get truncated with long localized _(txt_more) ... */
 2716     int len;
 2717 
 2718     if (NOTESLINES <= 0)
 2719         return;
 2720 
 2721     if (cur_num <= 0 && max_num <= 0)
 2722         return;
 2723 
 2724     clear_message();
 2725     snprintf(buf, sizeof(buf), "%s(%d%%) [%ld/%ld]", _(txt_more), (int) (cur_num * 100 / max_num), cur_num, max_num);
 2726     len = strwidth(buf);
 2727     MoveCursor(cLINES, cCOLS - len - (1 + BLANK_PAGE_COLS));
 2728 #ifdef HAVE_COLOR
 2729     fcol(tinrc.col_normal);
 2730 #endif /* HAVE_COLOR */
 2731     StartInverse();
 2732     my_fputs(buf, stdout);
 2733     EndInverse();
 2734     my_flush();
 2735 }