"Fossies" - the Fresh Open Source Software Archive

Member "tin-2.4.4/src/page.c" (20 Nov 2019, 67366 Bytes) of package /linux/misc/tin-2.4.4.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.3_vs_2.4.4.

    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-2020 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     /*
 1377      * first line
 1378      */
 1379     cur_pos = 0;
 1380 
 1381 #   ifdef HAVE_COLOR
 1382     fcol(tinrc.col_head);
 1383 #   endif /* HAVE_COLOR */
 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 #   ifdef HAVE_COLOR
 1445     fcol(tinrc.col_head);
 1446 #   endif /* HAVE_COLOR */
 1447 
 1448     /* line count */
 1449     if (arts[this_resp].line_count < 0)
 1450         strcpy(buf, "?");
 1451     else
 1452         snprintf(buf, line_len, "%-4d", arts[this_resp].line_count);
 1453 
 1454     {
 1455         wchar_t *fmt;
 1456 
 1457         if ((wtmp = char2wchar_t(_(txt_lines))) != NULL) {
 1458             int tex_space = pgart.tex2iso ? 5 : 0;
 1459 
 1460             fmt = wstrunc(wtmp, cCOLS / 3 - 1 - tex_space);
 1461             wtmp = my_realloc(wtmp, sizeof(wchar_t) * line_len);
 1462             swprintf(wtmp, line_len, fmt, buf);
 1463             my_fputws(wtmp, stdout);
 1464             cur_pos += wcswidth(wtmp, wcslen(wtmp));
 1465             free(fmt);
 1466             free(wtmp);
 1467         }
 1468     }
 1469 
 1470 #   ifdef HAVE_COLOR
 1471     fcol(tinrc.col_subject);
 1472 #   endif /* HAVE_COLOR */
 1473 
 1474     /* tex2iso */
 1475     if (pgart.tex2iso) {
 1476         if ((wtmp = char2wchar_t(_(txt_tex))) != NULL) {
 1477             wtmp2 = wstrunc(wtmp, 5);
 1478             my_fputws(wtmp2, stdout);
 1479             cur_pos += wcswidth(wtmp2, wcslen(wtmp2));
 1480             free(wtmp);
 1481             free(wtmp2);
 1482         }
 1483     }
 1484 
 1485     /* subject */
 1486     /*
 1487      * TODO: why do we fall back to arts[this_resp].subject if !note_h->subj?
 1488      *       if !note_h->subj then the article just has no subject, no matter
 1489      *       what the overview says.
 1490      */
 1491     strncpy(buf, (note_h->subj ? note_h->subj : arts[this_resp].subject), line_len);
 1492     buf[line_len - 1] = '\0';
 1493     if ((wtmp = char2wchar_t(buf)) != NULL) {
 1494         wbuf = wexpand_tab(wtmp, tabwidth);
 1495         wtmp2 = wstrunc(wbuf, cCOLS - 2 * right_len - 3);
 1496         center_pos = (cCOLS - wcswidth(wtmp2, wcslen(wtmp2))) / 2;
 1497 
 1498         /* pad out to left */
 1499         for (; cur_pos < center_pos; cur_pos++)
 1500             my_fputc(' ', stdout);
 1501 
 1502         StartInverse();
 1503         my_fputws(wtmp2, stdout);
 1504         EndInverse();
 1505         cur_pos += wcswidth(wtmp2, wcslen(wtmp2));
 1506         free(wtmp2);
 1507         free(wtmp);
 1508         free(wbuf);
 1509     }
 1510 
 1511 #   ifdef HAVE_COLOR
 1512     fcol(tinrc.col_response);
 1513 #   endif /* HAVE_COLOR */
 1514 
 1515     /* pad out to right */
 1516     for (; cur_pos < cCOLS - right_len - 1; cur_pos++)
 1517         my_fputc(' ', stdout);
 1518 
 1519     if (whichresp) {
 1520         tmp = strunc(_(txt_art_x_of_n), cCOLS / 3 - 1);
 1521         my_printf(tmp, whichresp + 1, x_resp + 1);
 1522         free(tmp);
 1523     } else {
 1524         /* TODO: ngettext */
 1525         if (!x_resp) {
 1526             tmp = strunc(_(txt_no_responses), cCOLS / 3 - 1);
 1527             my_printf("%s", tmp);
 1528         } else if (x_resp == 1) {
 1529             tmp = strunc(_(txt_1_resp), cCOLS / 3 - 1);
 1530             my_printf("%s", tmp);
 1531         } else {
 1532             tmp = strunc(_(txt_x_resp), cCOLS / 3 - 1);
 1533             my_printf(tmp, x_resp);
 1534         }
 1535         free(tmp);
 1536     }
 1537     my_fputs(cCRLF, stdout);
 1538 
 1539     /*
 1540      * third line
 1541      */
 1542     cur_pos = 0;
 1543 
 1544 #   ifdef HAVE_COLOR
 1545     fcol(tinrc.col_from);
 1546 #   endif /* HAVE_COLOR */
 1547     /* from */
 1548     /*
 1549      * TODO: don't use arts[this_resp].name/arts[this_resp].from
 1550      *       split up note_h->from and use that instead as it might
 1551      *       be different _if_ the overviews are broken
 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      */
 1579     if (note_h->org) {
 1580         snprintf(buf, line_len, _(txt_at_s), note_h->org);
 1581 
 1582         if ((wtmp = char2wchar_t(buf)) != NULL) {
 1583             wbuf = wexpand_tab(wtmp, tabwidth);
 1584             wtmp2 = wstrunc(wbuf, cCOLS - cur_pos - 1);
 1585 
 1586             i = cCOLS - wcswidth(wtmp2, wcslen(wtmp2)) - 1;
 1587             for (; cur_pos < i; cur_pos++)
 1588                 my_fputc(' ', stdout);
 1589 
 1590             my_fputws(wtmp2, stdout);
 1591             free(wtmp2);
 1592             free(wtmp);
 1593             free(wbuf);
 1594         }
 1595     }
 1596 
 1597     my_fputs(cCRLF, stdout);
 1598     my_fputs(cCRLF, stdout);
 1599 
 1600 #else /* !MULTIBYTE_ABLE || NO_LOCALE */
 1601     /*
 1602      * determine the needed space for the text at the right hand margin
 1603      * the formatting info (%4s) needs 3 positions but we need 4 positions
 1604      * on the screen for each counter
 1605      */
 1606     right_len = MAX((strlen(_(txt_thread_x_of_n)) - 6 + 8), (strlen(_(txt_art_x_of_n)) - 6 + 8));
 1607 
 1608     /*
 1609      * first line
 1610      */
 1611     cur_pos = 0;
 1612 
 1613 #   ifdef HAVE_COLOR
 1614     fcol(tinrc.col_head);
 1615 #   endif /* HAVE_COLOR */
 1616 
 1617     /* date */
 1618     my_fputs(buf, stdout);
 1619     cur_pos += strlen(buf);
 1620 
 1621     /*
 1622      * determine max len for centered group name
 1623      * allow one space before and after group name
 1624      */
 1625     len = cCOLS - 2 * MAX(cur_pos, right_len) - 3;
 1626 
 1627     /* group name */
 1628     if (tinrc.abbreviate_groupname)
 1629         tmp = abbr_groupname(group, len);
 1630     else
 1631         tmp = strunc(group, len);
 1632 
 1633     if ((i = strlen(tmp)) < len)
 1634         len = i;
 1635 
 1636     center_pos = (cCOLS - len) / 2;
 1637 
 1638     /* pad out to left */
 1639     for (; cur_pos < center_pos; cur_pos++)
 1640         my_fputc(' ', stdout);
 1641 
 1642     my_fputs(tmp, stdout);
 1643     cur_pos += strlen(tmp);
 1644     free(tmp);
 1645 
 1646     /* pad out to right */
 1647     for (; cur_pos < cCOLS - right_len - 1; cur_pos++)
 1648         my_fputc(' ', stdout);
 1649 
 1650     /* thread info */
 1651     /* can't eval tin_ltoa() more than once in a statement due to statics */
 1652     strcpy(buf, tin_ltoa(which_thread(this_resp) + 1, 4));
 1653     my_printf(_(txt_thread_x_of_n), buf, tin_ltoa(grpmenu.max, 4));
 1654 
 1655     my_fputs(cCRLF, stdout);
 1656 
 1657 #   if 0
 1658     /* display a ruler for layout checking purposes */
 1659     my_fputs("....|....3....|....2....|....1....|....0....|....1....|....2....|....3....|....\n", stdout);
 1660 #   endif /* 0 */
 1661 
 1662     /*
 1663      * second line
 1664      */
 1665     cur_pos = 0;
 1666 
 1667 #   ifdef HAVE_COLOR
 1668     fcol(tinrc.col_head);
 1669 #   endif /* HAVE_COLOR */
 1670 
 1671     /* line count */
 1672     /* an accurate line count will appear in the footer anymay */
 1673     if (arts[this_resp].line_count < 0)
 1674         strcpy(buf, "?");
 1675     else
 1676         snprintf(buf, line_len, "%-4d", arts[this_resp].line_count);
 1677 
 1678     tmp = my_malloc(line_len);
 1679     snprintf(tmp, line_len, _(txt_lines), buf);
 1680     my_fputs(tmp, stdout);
 1681     cur_pos += strlen(tmp);
 1682     free(tmp);
 1683 
 1684 #   ifdef HAVE_COLOR
 1685     fcol(tinrc.col_subject);
 1686 #   endif /* HAVE_COLOR */
 1687 
 1688     /* tex2iso */
 1689     if (pgart.tex2iso) {
 1690         my_fputs(_(txt_tex), stdout);
 1691         cur_pos += strlen(_(txt_tex));
 1692     }
 1693 
 1694     /* subject */
 1695     /*
 1696      * TODO: why do we fall back to arts[this_resp].subject if !note_h->subj?
 1697      *       if !note_h->subj then the article just has no subject, no matter
 1698      *       what the overview says.
 1699      */
 1700     strncpy(buf, (note_h->subj ? note_h->subj : arts[this_resp].subject), line_len);
 1701     buf[line_len - 1] = '\0';
 1702 
 1703     tmp2 = expand_tab(buf, tabwidth);
 1704     tmp = strunc(tmp2, cCOLS - 2 * right_len - 3);
 1705 
 1706     center_pos = (cCOLS - strlen(tmp)) / 2;
 1707 
 1708     /* pad out to left */
 1709     for (; cur_pos < center_pos; cur_pos++)
 1710         my_fputc(' ', stdout);
 1711 
 1712     StartInverse();
 1713     my_fputs(tmp, stdout);
 1714     EndInverse();
 1715     cur_pos += strlen(tmp);
 1716     free(tmp);
 1717     free(tmp2);
 1718 
 1719 #   ifdef HAVE_COLOR
 1720     fcol(tinrc.col_response);
 1721 #   endif /* HAVE_COLOR */
 1722 
 1723     /* pad out to right */
 1724     for (; cur_pos < cCOLS - right_len - 1; cur_pos++)
 1725         my_fputc(' ', stdout);
 1726 
 1727     if (whichresp)
 1728         my_printf(_(txt_art_x_of_n), whichresp + 1, x_resp + 1);
 1729     else {
 1730         /* TODO: ngettext */
 1731         if (!x_resp)
 1732             my_printf("%s", _(txt_no_responses));
 1733         else if (x_resp == 1)
 1734             my_printf("%s", _(txt_1_resp));
 1735         else
 1736             my_printf(_(txt_x_resp), x_resp);
 1737     }
 1738     my_fputs(cCRLF, stdout);
 1739 
 1740     /*
 1741      * third line
 1742      */
 1743     cur_pos = 0;
 1744 
 1745 #   ifdef HAVE_COLOR
 1746     fcol(tinrc.col_from);
 1747 #   endif /* HAVE_COLOR */
 1748     /* from */
 1749     /*
 1750      * TODO: don't use arts[this_resp].name/arts[this_resp].from
 1751      *       split up note_h->from and use that instead as it might
 1752      *       be different _if_ the overviews are broken
 1753      */
 1754     if (arts[this_resp].name)
 1755         snprintf(buf, line_len, "%s <%s>", arts[this_resp].name, arts[this_resp].from);
 1756     else {
 1757         strncpy(buf, arts[this_resp].from, line_len);
 1758         buf[line_len - 1] = '\0';
 1759     }
 1760 
 1761     tmp = strunc(buf, cCOLS - 1);
 1762     my_fputs(tmp, stdout);
 1763     cur_pos += strlen(tmp);
 1764     free(tmp);
 1765 
 1766     if (note_h->org && cCOLS - cur_pos - 1 >= (int) strlen(_(txt_at_s)) - 2 + 3) {
 1767         /* we have enough space to print at least " at ..." */
 1768         snprintf(buf, line_len, _(txt_at_s), note_h->org);
 1769 
 1770         tmp2 = expand_tab(buf, tabwidth);
 1771         tmp = strunc(tmp2, cCOLS - cur_pos - 1);
 1772         len = cCOLS - (int) strlen(tmp) - 1;
 1773         for (; cur_pos < len; cur_pos++)
 1774             my_fputc(' ', stdout);
 1775         my_fputs(tmp, stdout);
 1776         free(tmp);
 1777         free(tmp2);
 1778     }
 1779 
 1780     my_fputs(cCRLF, stdout);
 1781     my_fputs(cCRLF, stdout);
 1782 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
 1783     free(buf);
 1784 
 1785 #ifdef HAVE_COLOR
 1786     fcol(tinrc.col_normal);
 1787 #endif /* HAVE_COLOR */
 1788 }
 1789 
 1790 
 1791 /*
 1792  * Change the pager article context to arts[new_respnum]
 1793  * Return GRP_ARTUNAVAIL if article could not be opened
 1794  * or GRP_ARTABORT if load of article was interrupted
 1795  * or 0 on success
 1796  */
 1797 static int
 1798 load_article(
 1799     int new_respnum,
 1800     struct t_group *group)
 1801 {
 1802     static t_bool art_closed = FALSE;
 1803 
 1804 #ifdef DEBUG
 1805     if (debug & DEBUG_MISC)
 1806         fprintf(stderr, "load_art %s(new=%d, curr=%d)\n", (new_respnum == this_resp && !art_closed) ? "ALREADY OPEN!" : "", new_respnum, this_resp);
 1807 #endif /* DEBUG */
 1808 
 1809     if (new_respnum != this_resp || art_closed) {
 1810         int ret;
 1811 
 1812         art_close(&pgart);          /* close previously opened art in pager */
 1813         ret = art_open(TRUE, &arts[new_respnum], group, &pgart, TRUE, _(txt_reading_article));
 1814 
 1815         switch (ret) {
 1816             case ART_UNAVAILABLE:
 1817                 art_mark(group, &arts[new_respnum], ART_READ);
 1818                 /* prevent retagging as unread in unfilter_articles() */
 1819                 if (arts[new_respnum].killed == ART_KILLED_UNREAD)
 1820                     arts[new_respnum].killed = ART_KILLED;
 1821                 art_closed = TRUE;
 1822                 wait_message(1, _(txt_art_unavailable));
 1823                 return GRP_ARTUNAVAIL;
 1824 
 1825             case ART_ABORT:
 1826                 art_close(&pgart);
 1827                 art_closed = TRUE;
 1828                 return GRP_ARTABORT;    /* special retcode to stop redrawing screen */
 1829 
 1830             default:                    /* Normal case */
 1831 #if 0           /* Very useful debugging tool */
 1832                 if (prompt_yn("Fake art unavailable? ", FALSE) == 1) {
 1833                     art_close(&pgart);
 1834                     art_mark(group, &arts[new_respnum], ART_READ);
 1835                     art_closed = TRUE;
 1836                     return GRP_ARTUNAVAIL;
 1837                 }
 1838 #endif /* 0 */
 1839                 if (art_closed)
 1840                     art_closed = FALSE;
 1841                 if (new_respnum != this_resp) {
 1842                     /*
 1843                      * Remember current & previous articles for '-' command
 1844                      */
 1845                     last_resp = this_resp;
 1846                     this_resp = new_respnum;        /* Set new art globally */
 1847                 }
 1848                 break;
 1849         }
 1850     } else if (show_all_headers) {
 1851         /*
 1852          * article is already opened with show_all_headers ON
 1853          * -> re-cook it
 1854          */
 1855         show_all_headers = FALSE;
 1856         resize_article(TRUE, &pgart);
 1857     }
 1858 
 1859     art_mark(group, &arts[this_resp], ART_READ);
 1860 
 1861     /*
 1862      * Change status if art was unread before killing to
 1863      * prevent retagging as unread in unfilter_articles()
 1864      */
 1865     if (arts[this_resp].killed == ART_KILLED_UNREAD)
 1866         arts[this_resp].killed = ART_KILLED;
 1867 
 1868     if (pgart.cooked == NULL) { /* harmony corruption */
 1869         wait_message(1, _(txt_art_unavailable));
 1870         return GRP_ARTUNAVAIL;
 1871     }
 1872 
 1873     /*
 1874      * Setup to start viewing cooked version
 1875      */
 1876     show_raw_article = FALSE;
 1877     show_all_headers = FALSE;
 1878     curr_line = 0;
 1879     note_fp = pgart.cooked;
 1880     artline = pgart.cookl;
 1881     artlines = pgart.cooked_lines;
 1882     search_line = 0;
 1883     /*
 1884      * Reset offsets only if not invoked during 'body search' (srch_lineno != -1)
 1885      * otherwise the found string will not be highlighted
 1886      */
 1887     if (srch_lineno == -1)
 1888         reset_srch_offsets();
 1889     rotate = 0;         /* normal mode, not rot13 */
 1890     reveal_ctrl_l = FALSE;
 1891     reveal_ctrl_l_lines = -1;   /* all ^L's active */
 1892     hide_uue = tinrc.hide_uue;
 1893 
 1894     draw_page(group->name, 0);
 1895 
 1896     /*
 1897      * Automatically invoke attachment viewing if requested
 1898      */
 1899     if (!note_h->mime || IS_PLAINTEXT(note_h->ext))     /* Text only article */
 1900         return 0;
 1901 
 1902     if (*tinrc.metamail_prog == '\0' || getenv("NOMETAMAIL") != NULL)   /* Viewer turned off */
 1903         return 0;
 1904 
 1905     if (group->attribute->ask_for_metamail) {
 1906         if (prompt_yn(_(txt_use_mime), TRUE) != 1)
 1907             return 0;
 1908     }
 1909 
 1910     XFACE_SUPPRESS();
 1911     if (strcmp(tinrc.metamail_prog, INTERNAL_CMD) == 0) /* Use internal viewer */
 1912         decode_save_mime(&pgart, FALSE);
 1913     else
 1914         invoke_metamail(pgart.raw);
 1915     XFACE_SHOW();
 1916     return 0;
 1917 }
 1918 
 1919 
 1920 static int
 1921 prompt_response(
 1922     int ch,
 1923     int curr_respnum)
 1924 {
 1925     int i, num;
 1926 
 1927     clear_message();
 1928 
 1929     if ((num = (prompt_num(ch, _(txt_select_art)) - 1)) == -1) {
 1930         clear_message();
 1931         return -1;
 1932     }
 1933 
 1934     if ((i = which_thread(curr_respnum)) >= 0)
 1935         return find_response(i, num);
 1936     else
 1937         return -1;
 1938 }
 1939 
 1940 
 1941 /*
 1942  * Reposition within message as needed, highlight searched string
 1943  * This is tied quite closely to the information stored by
 1944  * get_search_vectors()
 1945  */
 1946 static void
 1947 process_search(
 1948     int *lcurr_line,
 1949     size_t message_lines,
 1950     size_t screen_lines,
 1951     int help_level)
 1952 {
 1953     int i, start, end;
 1954 
 1955     if ((i = get_search_vectors(&start, &end)) == -1)
 1956         return;
 1957 
 1958     /*
 1959      * Is matching line off the current view?
 1960      * Reposition within article if needed, try to get matched line
 1961      * in the middle of the screen
 1962      */
 1963     if (i < *lcurr_line || i >= (int) (*lcurr_line + screen_lines)) {
 1964         *lcurr_line = i - (screen_lines / 2);
 1965         if (*lcurr_line + screen_lines > message_lines) /* off the end */
 1966             *lcurr_line = message_lines - screen_lines;
 1967         /* else pos. is just fine */
 1968     }
 1969 
 1970     switch (help_level) {
 1971         case PAGE_LEVEL:
 1972             draw_page(curr_group->name, 0);
 1973             break;
 1974 
 1975         case INFO_PAGER:
 1976             display_info_page(0);
 1977             break;
 1978 
 1979         default:
 1980             break;
 1981     }
 1982     search_line = i;                                /* draw_page() resets this to 0 */
 1983 
 1984     /*
 1985      * Highlight found string
 1986      */
 1987     highlight_string(i - *lcurr_line + scroll_region_top, start, end - start);
 1988 }
 1989 
 1990 
 1991 /*
 1992  * Implement ^H toggle between cooked and raw views of article
 1993  */
 1994 void
 1995 toggle_raw(
 1996     struct t_group *group)
 1997 {
 1998     if (show_raw_article) {
 1999         artline = pgart.cookl;
 2000         artlines = pgart.cooked_lines;
 2001         note_fp = pgart.cooked;
 2002     } else {
 2003         static int j;               /* Needed on successive invocations */
 2004         int chunk = note_h->ext->line_count;
 2005 
 2006         /*
 2007          * We do this on the fly, since most of the time it won't be used
 2008          */
 2009         if (!pgart.rawl) {          /* Already done this for this article? */
 2010             char *line;
 2011             char *p;
 2012             long offset;
 2013 
 2014             j = 0;
 2015             rewind(pgart.raw);
 2016             pgart.rawl = my_malloc(sizeof(t_lineinfo) * chunk);
 2017             offset = ftell(pgart.raw);
 2018 
 2019             while ((line = tin_fgets(pgart.raw, FALSE)) != NULL) {
 2020                 int space;
 2021 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
 2022                 int num_bytes;
 2023                 wchar_t wc;
 2024 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
 2025 
 2026                 pgart.rawl[j].offset = offset;
 2027                 pgart.rawl[j].flags = 0;
 2028                 j++;
 2029                 if (j >= chunk) {
 2030                     chunk += 50;
 2031                     pgart.rawl = my_realloc(pgart.rawl, sizeof(t_lineinfo) * chunk);
 2032                 }
 2033 
 2034                 p = line;
 2035                 while (*p) {
 2036                     space = cCOLS - 1;
 2037 
 2038                     while ((space > 0) && *p) {
 2039 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
 2040                         num_bytes = mbtowc(&wc, p, MB_CUR_MAX);
 2041                         if (num_bytes != -1 && iswprint(wc)) {
 2042                             if ((space -= wcwidth(wc)) < 0)
 2043                                 break;
 2044                             p += num_bytes;
 2045                             offset += num_bytes;
 2046                         }
 2047 #else
 2048                         if (my_isprint((unsigned char) *p)) {
 2049                             space--;
 2050                             p++;
 2051                             offset++;
 2052                         }
 2053 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
 2054                         else if (IS_LOCAL_CHARSET("Big5") && (unsigned char) *p >= 0xa1 && (unsigned char) *p <= 0xfe && *(p + 1)) {
 2055                             /*
 2056                              * Big5: ASCII chars are handled by the normal code
 2057                              * check only for 2-byte chars
 2058                              * TODO: should we also check if the second byte is
 2059                              * also valid?
 2060                              */
 2061                             p += 2;
 2062                             offset += 2;
 2063                             space--;
 2064                         } else {
 2065                             /*
 2066                              * the current character can't be displayed print it as
 2067                              * an octal value (needs 4 columns) see also
 2068                              * color.c:draw_pager_line()
 2069                              */
 2070                             if ((space -= 4) < 0)
 2071                                 break;
 2072                             offset++;
 2073                             p++;
 2074                         }
 2075                     }
 2076                     /*
 2077                      * if we reached the end of the line we don't need to
 2078                      * remember anything
 2079                      */
 2080                     if (*p) {
 2081                         pgart.rawl[j].offset = offset;
 2082                         pgart.rawl[j].flags = 0;
 2083                         if (++j >= chunk) {
 2084                             chunk += 50;
 2085                             pgart.rawl = my_realloc(pgart.rawl, sizeof(t_lineinfo) * chunk);
 2086                         }
 2087                     }
 2088                 }
 2089 
 2090                 /*
 2091                  * only use ftell's return value here because we didn't
 2092                  * take the \n into account above.
 2093                  */
 2094                 offset = ftell(pgart.raw);
 2095             }
 2096 
 2097             pgart.rawl = my_realloc(pgart.rawl, sizeof(t_lineinfo) * j);
 2098         }
 2099         artline = pgart.rawl;
 2100         artlines = j;
 2101         note_fp = pgart.raw;
 2102     }
 2103     curr_line = 0;
 2104     show_raw_article = bool_not(show_raw_article);
 2105     draw_page(group ? group->name : "", 0);
 2106 }
 2107 
 2108 
 2109 /*
 2110  * Re-cook an article
 2111  */
 2112 void
 2113 resize_article(
 2114     t_bool wrap_lines,
 2115     t_openartinfo *artinfo)
 2116 {
 2117     free(artinfo->cookl);
 2118     if (artinfo->cooked)
 2119         fclose(artinfo->cooked);
 2120 
 2121     if (!cook_article(wrap_lines, artinfo, hide_uue, show_all_headers))
 2122         tin_done(EXIT_FAILURE, _(txt_cook_article_failed_exiting), tin_progname);
 2123 
 2124     show_raw_article = FALSE;
 2125     artline = pgart.cookl;
 2126     artlines = pgart.cooked_lines;
 2127     note_fp = pgart.cooked;
 2128 }
 2129 
 2130 
 2131 /*
 2132  * Infopager: simply page files
 2133  */
 2134 void
 2135 info_pager(
 2136     FILE *info_fh,
 2137     const char *title,
 2138     t_bool wrap_at_ends)
 2139 {
 2140     int offset;
 2141     t_function func;
 2142 
 2143     search_line = 0;
 2144     reset_srch_offsets();
 2145     info_file = info_fh;
 2146     info_title = title;
 2147     curr_info_line = 0;
 2148     preprocess_info_message(info_fh);
 2149     if (!info_fh)
 2150         return;
 2151     set_xclick_off();
 2152     display_info_page(0);
 2153 
 2154     forever {
 2155         switch (func = handle_keypad(page_left, page_right, page_mouse_action, info_keys)) {
 2156             case GLOBAL_ABORT:  /* common arrow keys */
 2157                 break;
 2158 
 2159             case GLOBAL_LINE_UP:
 2160                 if (num_info_lines <= NOTESLINES) {
 2161                     info_message(_(txt_begin_of_page));
 2162                     break;
 2163                 }
 2164                 if (curr_info_line == 0) {
 2165                     if (!wrap_at_ends) {
 2166                         info_message(_(txt_begin_of_page));
 2167                         break;
 2168                     }
 2169                     curr_info_line = num_info_lines - NOTESLINES;
 2170                     display_info_page(0);
 2171                     break;
 2172                 }
 2173                 offset = scroll_page(KEYMAP_UP);
 2174                 curr_info_line += offset;
 2175                 display_info_page(offset);
 2176                 break;
 2177 
 2178             case GLOBAL_LINE_DOWN:
 2179                 if (num_info_lines <= NOTESLINES) {
 2180                     info_message(_(txt_end_of_page));
 2181                     break;
 2182                 }
 2183                 if (curr_info_line + NOTESLINES >= num_info_lines) {
 2184                     if (!wrap_at_ends) {
 2185                         info_message(_(txt_end_of_page));
 2186                         break;
 2187                     }
 2188                     curr_info_line = 0;
 2189                     display_info_page(0);
 2190                     break;
 2191                 }
 2192                 offset = scroll_page(KEYMAP_DOWN);
 2193                 curr_info_line += offset;
 2194                 display_info_page(offset);
 2195                 break;
 2196 
 2197             case GLOBAL_PAGE_DOWN:
 2198                 if (num_info_lines <= NOTESLINES) {
 2199                     info_message(_(txt_end_of_page));
 2200                     break;
 2201                 }
 2202                 if (curr_info_line + NOTESLINES >= num_info_lines) {    /* End is already on screen */
 2203                     if (!wrap_at_ends) {
 2204                         info_message(_(txt_end_of_page));
 2205                         break;
 2206                     }
 2207                     curr_info_line = 0;
 2208                     display_info_page(0);
 2209                     break;
 2210                 }
 2211                 curr_info_line += ((tinrc.scroll_lines == -2) ? NOTESLINES / 2 : NOTESLINES);
 2212                 display_info_page(0);
 2213                 break;
 2214 
 2215             case GLOBAL_PAGE_UP:
 2216                 if (num_info_lines <= NOTESLINES) {
 2217                     info_message(_(txt_begin_of_page));
 2218                     break;
 2219                 }
 2220                 if (curr_info_line == 0) {
 2221                     if (!wrap_at_ends) {
 2222                         info_message(_(txt_begin_of_page));
 2223                         break;
 2224                     }
 2225                     curr_info_line = num_info_lines - NOTESLINES;
 2226                     display_info_page(0);
 2227                     break;
 2228                 }
 2229                 curr_info_line -= ((tinrc.scroll_lines == -2) ? NOTESLINES / 2 : NOTESLINES);
 2230                 display_info_page(0);
 2231                 break;
 2232 
 2233             case GLOBAL_FIRST_PAGE:
 2234                 if (curr_info_line) {
 2235                     curr_info_line = 0;
 2236                     display_info_page(0);
 2237                 }
 2238                 break;
 2239 
 2240             case GLOBAL_LAST_PAGE:
 2241                 if (curr_info_line + NOTESLINES != num_info_lines) {
 2242                     /* Display a full last page for neatness */
 2243                     curr_info_line = num_info_lines - NOTESLINES;
 2244                     display_info_page(0);
 2245                 }
 2246                 break;
 2247 
 2248             case GLOBAL_TOGGLE_HELP_DISPLAY:
 2249                 toggle_mini_help(INFO_PAGER);
 2250                 display_info_page(0);
 2251                 break;
 2252 
 2253             case GLOBAL_SEARCH_SUBJECT_FORWARD:
 2254             case GLOBAL_SEARCH_SUBJECT_BACKWARD:
 2255             case GLOBAL_SEARCH_REPEAT:
 2256                 if (func == GLOBAL_SEARCH_REPEAT && last_search != GLOBAL_SEARCH_SUBJECT_FORWARD && last_search != GLOBAL_SEARCH_SUBJECT_BACKWARD)
 2257                     break;
 2258 
 2259                 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)
 2260                     break;
 2261 
 2262                 process_search(&curr_info_line, num_info_lines, NOTESLINES, INFO_PAGER);
 2263                 break;
 2264 
 2265             case GLOBAL_QUIT:   /* quit */
 2266                 ClearScreen();
 2267                 return;
 2268 
 2269             default:
 2270                 break;
 2271         }
 2272     }
 2273 }
 2274 
 2275 
 2276 /*
 2277  * Redraw the current page, curr_info_line will be the first line displayed
 2278  * If part is !=0, then only draw the first (-ve) or last (+ve) few lines
 2279  */
 2280 void
 2281 display_info_page(
 2282     int part)
 2283 {
 2284     int start, end; /* 1st, last line to draw */
 2285 
 2286     signal_context = cInfopager;
 2287 
 2288     /*
 2289      * Can't do partial draw if term can't scroll properly
 2290      */
 2291     if (part && !have_linescroll)
 2292         part = 0;
 2293 
 2294     if (curr_info_line < 0)
 2295         curr_info_line = 0;
 2296     if (curr_info_line >= num_info_lines)
 2297         curr_info_line = num_info_lines - 1;
 2298 
 2299     scroll_region_top = INDEX_TOP;
 2300 
 2301     /* Down-scroll, only redraw bottom 'part' lines of screen */
 2302     if ((start = (part > 0) ? NOTESLINES - part : 0) < 0)
 2303         start = 0;
 2304 
 2305     /* Up-scroll, only redraw the top 'part' lines of screen */
 2306     if ((end = (part < 0) ? -part : NOTESLINES) > NOTESLINES)
 2307         end = NOTESLINES;
 2308 
 2309     /* Print title */
 2310     if ((end - start >= NOTESLINES) || (part == 0)) {
 2311         ClearScreen();
 2312         center_line(0, TRUE, info_title);
 2313     }
 2314 
 2315     print_message_page(info_file, infoline, num_info_lines, curr_info_line, start, end, INFO_PAGER);
 2316 
 2317     /* print footer */
 2318     draw_percent_mark(curr_info_line + (curr_info_line + NOTESLINES < num_info_lines ? NOTESLINES : num_info_lines - curr_info_line), num_info_lines);
 2319     stow_cursor();
 2320 }
 2321 
 2322 
 2323 static void
 2324 preprocess_info_message(
 2325     FILE *info_fh)
 2326 {
 2327     int chunk = 50;
 2328 
 2329     FreeAndNull(infoline);
 2330     if (!info_fh)
 2331         return;
 2332 
 2333     rewind(info_fh);
 2334     infoline = my_malloc(sizeof(t_lineinfo) * chunk);
 2335     num_info_lines = 0;
 2336 
 2337     do {
 2338         infoline[num_info_lines].offset = ftell(info_fh);
 2339         infoline[num_info_lines].flags = 0;
 2340         num_info_lines++;
 2341         if (num_info_lines >= chunk) {
 2342             chunk += 50;
 2343             infoline = my_realloc(infoline, sizeof(t_lineinfo) * chunk);
 2344         }
 2345     } while (tin_fgets(info_fh, FALSE) != NULL);
 2346 
 2347     num_info_lines--;
 2348     infoline = my_realloc(infoline, sizeof(t_lineinfo) * num_info_lines);
 2349 }
 2350 
 2351 
 2352 /*
 2353  * URL menu
 2354  */
 2355 static t_function
 2356 url_left(
 2357     void)
 2358 {
 2359     return GLOBAL_QUIT;
 2360 }
 2361 
 2362 
 2363 static t_function
 2364 url_right(
 2365     void)
 2366 {
 2367     return URL_SELECT;
 2368 }
 2369 
 2370 
 2371 static void
 2372 show_url_page(
 2373     void)
 2374 {
 2375     int i;
 2376 
 2377     signal_context = cURL;
 2378     currmenu = &urlmenu;
 2379     mark_offset = 0;
 2380 
 2381     if (urlmenu.curr < 0)
 2382         urlmenu.curr = 0;
 2383 
 2384     ClearScreen();
 2385     set_first_screen_item();
 2386     center_line(0, TRUE, _(txt_url_menu));
 2387 
 2388     for (i = urlmenu.first; i < urlmenu.first + NOTESLINES && i < urlmenu.max; ++i)
 2389         build_url_line(i);
 2390 
 2391     show_mini_help(URL_LEVEL);
 2392 
 2393     draw_url_arrow();
 2394 }
 2395 
 2396 
 2397 static t_bool
 2398 url_page(
 2399     void)
 2400 {
 2401     char key[MAXKEYLEN];
 2402     t_function func;
 2403     t_menu *oldmenu = NULL;
 2404 
 2405     if (currmenu)
 2406         oldmenu = currmenu;
 2407     urlmenu.curr = 0;
 2408     urlmenu.max = build_url_list();
 2409     if (urlmenu.max == 0)
 2410         return FALSE;
 2411 
 2412     clear_note_area();
 2413     show_url_page();
 2414     set_xclick_off();
 2415 
 2416     forever {
 2417         switch ((func = handle_keypad(url_left, url_right, NULL, url_keys))) {
 2418             case GLOBAL_QUIT:
 2419                 free_url_list();
 2420                 if (oldmenu)
 2421                     currmenu = oldmenu;
 2422                 return TRUE;
 2423 
 2424             case DIGIT_1:
 2425             case DIGIT_2:
 2426             case DIGIT_3:
 2427             case DIGIT_4:
 2428             case DIGIT_5:
 2429             case DIGIT_6:
 2430             case DIGIT_7:
 2431             case DIGIT_8:
 2432             case DIGIT_9:
 2433                 if (urlmenu.max)
 2434                     prompt_item_num(func_to_key(func, url_keys), _(txt_url_select));
 2435                 break;
 2436 
 2437 #ifndef NO_SHELL_ESCAPE
 2438             case GLOBAL_SHELL_ESCAPE:
 2439                 do_shell_escape();
 2440                 break;
 2441 #endif /* !NO_SHELL_ESCAPE */
 2442 
 2443             case GLOBAL_HELP:
 2444                 show_help_page(URL_LEVEL, _(txt_url_menu_com));
 2445                 show_url_page();
 2446                 break;
 2447 
 2448             case GLOBAL_FIRST_PAGE:
 2449                 top_of_list();
 2450                 break;
 2451 
 2452             case GLOBAL_LAST_PAGE:
 2453                 end_of_list();
 2454                 break;
 2455 
 2456             case GLOBAL_REDRAW_SCREEN:
 2457                 my_retouch();
 2458                 show_url_page();
 2459                 break;
 2460 
 2461             case GLOBAL_LINE_DOWN:
 2462                 move_down();
 2463                 break;
 2464 
 2465             case GLOBAL_LINE_UP:
 2466                 move_up();
 2467                 break;
 2468 
 2469             case GLOBAL_PAGE_DOWN:
 2470                 page_down();
 2471                 break;
 2472 
 2473             case GLOBAL_PAGE_UP:
 2474                 page_up();
 2475                 break;
 2476 
 2477             case GLOBAL_SCROLL_DOWN:
 2478                 scroll_down();
 2479                 break;
 2480 
 2481             case GLOBAL_SCROLL_UP:
 2482                 scroll_up();
 2483                 break;
 2484 
 2485             case GLOBAL_TOGGLE_HELP_DISPLAY:
 2486                 toggle_mini_help(URL_LEVEL);
 2487                 show_url_page();
 2488                 break;
 2489 
 2490             case GLOBAL_TOGGLE_INFO_LAST_LINE:
 2491                 tinrc.info_in_last_line = bool_not(tinrc.info_in_last_line);
 2492                 show_url_page();
 2493                 break;
 2494 
 2495             case URL_SELECT:
 2496                 if (urlmenu.max) {
 2497                     if (process_url(urlmenu.curr))
 2498                         show_url_page();
 2499                     else
 2500                         draw_url_arrow();
 2501                 }
 2502                 break;
 2503 
 2504             case GLOBAL_SEARCH_SUBJECT_FORWARD:
 2505             case GLOBAL_SEARCH_SUBJECT_BACKWARD:
 2506             case GLOBAL_SEARCH_REPEAT:
 2507                 if (func == GLOBAL_SEARCH_REPEAT && last_search != GLOBAL_SEARCH_SUBJECT_FORWARD && last_search != GLOBAL_SEARCH_SUBJECT_BACKWARD)
 2508                     info_message(_(txt_no_prev_search));
 2509                 else if (urlmenu.max) {
 2510                     int new_pos, old_pos = urlmenu.curr;
 2511 
 2512                     new_pos = generic_search((func == GLOBAL_SEARCH_SUBJECT_FORWARD), (func == GLOBAL_SEARCH_REPEAT), urlmenu.curr, urlmenu.max - 1, URL_LEVEL);
 2513                     if (new_pos != old_pos)
 2514                         move_to_item(new_pos);
 2515                 }
 2516                 break;
 2517 
 2518             default:
 2519                 info_message(_(txt_bad_command), printascii(key, func_to_key(GLOBAL_HELP, url_keys)));
 2520                 break;
 2521         }
 2522     }
 2523 }
 2524 
 2525 
 2526 static void
 2527 draw_url_arrow(
 2528     void)
 2529 {
 2530     draw_arrow_mark(INDEX_TOP + urlmenu.curr - urlmenu.first);
 2531     if (tinrc.info_in_last_line) {
 2532         t_url *lptr;
 2533 
 2534         lptr = find_url(urlmenu.curr);
 2535         info_message("%s", lptr->url);
 2536     } else if (urlmenu.curr == urlmenu.max - 1)
 2537         info_message(_(txt_end_of_urls));
 2538 }
 2539 
 2540 
 2541 t_url *
 2542 find_url(
 2543     int n)
 2544 {
 2545     t_url *lptr;
 2546 
 2547     lptr = url_list;
 2548     while (n-- > 0 && lptr->next)
 2549         lptr = lptr->next;
 2550 
 2551     return lptr;
 2552 }
 2553 
 2554 
 2555 static void
 2556 build_url_line(
 2557     int i)
 2558 {
 2559     char *sptr;
 2560     int len = cCOLS - 9;
 2561     t_url *lptr;
 2562 
 2563 #ifdef USE_CURSES
 2564     /*
 2565      * Allocate line buffer
 2566      * make it the same size like in !USE_CURSES case to simplify some code
 2567      */
 2568     sptr = my_malloc(cCOLS + 2);
 2569 #else
 2570     sptr = screen[INDEX2SNUM(i)].col;
 2571 #endif /* USE_CURSES */
 2572 
 2573     lptr = find_url(i);
 2574     snprintf(sptr, cCOLS, "  %s  %-*.*s%s", tin_ltoa(i + 1, 4), len, len, lptr->url, cCRLF);
 2575     WriteLine(INDEX2LNUM(i), sptr);
 2576 
 2577 #ifdef USE_CURSES
 2578     free(sptr);
 2579 #endif /* USE_CURSES */
 2580 }
 2581 
 2582 
 2583 static t_bool
 2584 process_url(
 2585     int n)
 2586 {
 2587     char *url, *url_esc;
 2588     size_t len;
 2589     t_url *lptr;
 2590 
 2591     lptr = find_url(n);
 2592     len = strlen(lptr->url) << 1; /* double size; room for editing URL */
 2593     url = my_malloc(len + 1);
 2594     if (prompt_default_string("URL:", url, len, lptr->url, HIST_URL)) {
 2595         if (!*url) {            /* Don't try and open nothing */
 2596             free(url);
 2597             return FALSE;
 2598         }
 2599         wait_message(2, _(txt_url_open), url);
 2600         url_esc = escape_shell_meta(url, no_quote);
 2601         len = strlen(url_esc) + strlen(tinrc.url_handler) + 2;
 2602         url = my_realloc(url, len);
 2603         snprintf(url, len, "%s %s", tinrc.url_handler, url_esc);
 2604         invoke_cmd(url);
 2605         free(url);
 2606         cursoroff();
 2607         return TRUE;
 2608     }
 2609     free(url);
 2610     return FALSE;
 2611 }
 2612 
 2613 
 2614 static int
 2615 build_url_list(
 2616     void)
 2617 {
 2618     char *ptr;
 2619     int i, count = 0;
 2620     int offsets[6];
 2621     int offsets_size = ARRAY_SIZE(offsets);
 2622     t_url *lptr = NULL;
 2623 
 2624     for (i = 0; i < artlines; ++i) {
 2625         if (!(artline[i].flags & (C_URL | C_NEWS | C_MAIL)))
 2626             continue;
 2627 
 2628         /*
 2629          * Line contains a URL, so read it in
 2630          */
 2631         if (fseek(pgart.cooked, artline[i].offset, SEEK_SET) == -1) /* skip on error */
 2632             continue;
 2633         if ((ptr = tin_fgets(pgart.cooked, FALSE)) == NULL)
 2634             continue;
 2635 
 2636         /*
 2637          * Step through, finding URL's
 2638          */
 2639         forever {
 2640             /* any matches left? */
 2641             if (pcre_exec(url_regex.re, url_regex.extra, ptr, strlen(ptr), 0, 0, offsets, offsets_size) == PCRE_ERROR_NOMATCH)
 2642                 if (pcre_exec(mail_regex.re, mail_regex.extra, ptr, strlen(ptr), 0, 0, offsets, offsets_size) == PCRE_ERROR_NOMATCH)
 2643                     if (pcre_exec(news_regex.re, news_regex.extra, ptr, strlen(ptr), 0, 0, offsets, offsets_size) == PCRE_ERROR_NOMATCH)
 2644                         break;
 2645 
 2646             *(ptr + offsets[1]) = '\0';
 2647 
 2648             if (!lptr)
 2649                 lptr = url_list = my_malloc(sizeof(t_url));
 2650             else {
 2651                 lptr->next = my_malloc(sizeof(t_url));
 2652                 lptr = lptr->next;
 2653             }
 2654             lptr->url = my_strdup(ptr + offsets[0]);
 2655             lptr->next = NULL;
 2656             ++count;
 2657 
 2658             ptr += offsets[1] + 1;
 2659         }
 2660     }
 2661     return count;
 2662 }
 2663 
 2664 
 2665 static void
 2666 free_url_list(
 2667     void)
 2668 {
 2669     t_url *p, *q;
 2670 
 2671     for (p = url_list; p != NULL; p = q) {
 2672         q = p->next;
 2673         free(p->url);
 2674         free(p);
 2675     }
 2676     url_list = NULL;
 2677 }