"Fossies" - the Fresh Open Source Software Archive

Member "tin-2.4.2/src/page.c" (8 Dec 2017, 66233 Bytes) of package /linux/misc/tin-2.4.2.tar.xz:


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

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