"Fossies" - the Fresh Open Source Software Archive

Member "tin-2.4.1/src/page.c" (12 Oct 2016, 66100 Bytes) of archive /linux/misc/tin-2.4.1.tar.gz:


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.0_vs_2.4.1.

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