"Fossies" - the Fresh Open Source Software Archive

Member "tin-2.4.5/src/page.c" (17 Dec 2020, 67323 Bytes) of package /linux/misc/tin-2.4.5.tar.xz:


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

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