"Fossies" - the Fresh Open Source Software Archive

Member "tin-2.4.1/src/group.c" (12 Oct 2016, 44905 Bytes) of package /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 "group.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    : group.c
    4  *  Author    : I. Lea & R. Skrenta
    5  *  Created   : 1991-04-01
    6  *  Updated   : 2016-10-10
    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  * Globally accessible pointer to currently active group
   48  * Any functionality accessed from group level or below can use this pointer.
   49  * Any code invoked from selection level that requires a group context will
   50  * need to manually fix this up
   51  */
   52 struct t_group *curr_group;
   53 static struct t_fmt grp_fmt;
   54 
   55 /*
   56  * Local prototypes
   57  */
   58 static int do_search(t_function func, t_bool repeat);
   59 static int enter_pager(int art, t_bool ignore_unavail);
   60 static int enter_thread(int depth, t_pagerinfo *page);
   61 static int find_new_pos(long old_artnum, int cur_pos);
   62 static int group_catchup(t_function func);
   63 static int tab_pressed(void);
   64 static t_bool prompt_getart_limit(void);
   65 static t_function group_left(void);
   66 static t_function group_right(void);
   67 static void build_sline(int i);
   68 static void build_multipart_header(char *dest, int maxlen, const char *src, int cmplen, int have, int total);
   69 static void draw_subject_arrow(void);
   70 static void show_group_title(t_bool clear_title);
   71 static void show_tagged_lines(void);
   72 static void toggle_read_unread(t_bool force);
   73 static void update_group_page(void);
   74 
   75 /*
   76  * grpmenu.curr is an index into base[] and so equates to the cursor location
   77  * (thread number) on group page
   78  * grpmenu.first is static here
   79  */
   80 t_menu grpmenu = { 0, 0, 0, show_group_page, draw_subject_arrow, build_sline };
   81 
   82 /* TODO: find a better solution */
   83 static int ret_code = 0;        /* Set to < 0 when it is time to leave the group level */
   84 
   85 static void
   86 show_tagged_lines(
   87     void)
   88 {
   89     int i, j;
   90 
   91     for (i = grpmenu.first; i < grpmenu.first + NOTESLINES && i < grpmenu.max; ++i) {
   92         if ((i != grpmenu.curr) && (j = line_is_tagged(base[i])))
   93             mark_screen(i, mark_offset - 2, tin_ltoa(j, 3));
   94     }
   95 }
   96 
   97 
   98 static t_function
   99 group_left(
  100     void)
  101 {
  102     if (curr_group->attribute->group_catchup_on_exit)
  103         return SPECIAL_CATCHUP_LEFT;        /* ie, not via 'c' or 'C' */
  104 
  105     return GLOBAL_QUIT;
  106 }
  107 
  108 
  109 static t_function
  110 group_right(
  111     void)
  112 {
  113     if (grpmenu.curr >= 0 && HAS_FOLLOWUPS(grpmenu.curr)) {
  114         if (curr_group->attribute->auto_list_thread)
  115             return GROUP_LIST_THREAD;
  116         else {
  117             int n = next_unread((int) base[grpmenu.curr]);
  118 
  119             if (n >= 0 && grpmenu.curr == which_thread(n)) {
  120                 ret_code = enter_pager(n, TRUE);
  121                 return GLOBAL_ABORT;    /* TODO: should we return something else? */
  122             }
  123         }
  124     }
  125     return GROUP_READ_BASENOTE;
  126 }
  127 
  128 
  129 /*
  130  * Return Codes:
  131  * GRP_EXIT         'Normal' return to selection menu
  132  * GRP_RETSELECT    We are en route from pager to the selection screen
  133  * GRP_QUIT         User has done a 'Q'
  134  * GRP_NEXT         User wants to move onto next group
  135  * GRP_NEXTUNREAD   User did a 'C'atchup
  136  * GRP_ENTER        'g' command has been used to set group to enter
  137  */
  138 int
  139 group_page(
  140     struct t_group *group)
  141 {
  142     char key[MAXKEYLEN];
  143     int i, n, ii;
  144     int thread_depth;   /* Starting depth in threads we enter */
  145     t_artnum old_artnum = T_ARTNUM_CONST(0);
  146     struct t_art_stat sbuf;
  147     struct t_article *art;
  148     t_bool flag;
  149     t_bool xflag = FALSE;   /* 'X'-flag */
  150     t_bool repeat_search;
  151     t_function func;
  152 
  153     /*
  154      * Set the group attributes
  155      */
  156     group->read_during_session = TRUE;
  157 
  158     curr_group = group;                 /* For global access to the current group */
  159     num_of_tagged_arts = 0;
  160     range_active = FALSE;
  161 
  162     last_resp = -1;
  163     this_resp = -1;
  164 
  165     /*
  166      * update index file. quit group level if user aborts indexing
  167      */
  168     if (!index_group(group)) {
  169         for_each_art(i) {
  170             art = &arts[i];
  171             FreeAndNull(art->refs);
  172             FreeAndNull(art->msgid);
  173         }
  174         curr_group = NULL;
  175         tin_errno = 0;
  176         return GRP_RETSELECT;
  177     }
  178 
  179     /*
  180      * Position 'grpmenu.curr' accordingly
  181      */
  182     pos_first_unread_thread();
  183     /* reset grpmenu.first */
  184     grpmenu.first = 0;
  185 
  186     clear_note_area();
  187 
  188     if (group->attribute->auto_select) {
  189         error_message(2, _(txt_autoselecting_articles), printascii(key, func_to_key(GROUP_MARK_UNSELECTED_ARTICLES_READ, group_keys)));
  190         do_auto_select_arts();                      /* 'X' command */
  191         xflag = TRUE;
  192     }
  193 
  194     show_group_page();
  195 
  196 #   ifdef DEBUG
  197     if (debug & DEBUG_NEWSRC)
  198         debug_print_bitmap(group, NULL);
  199 #   endif /* DEBUG */
  200 
  201     /* reset ret_code */
  202     ret_code = 0;
  203     while (ret_code >= 0) {
  204         set_xclick_on();
  205         if ((func = handle_keypad(group_left, group_right, global_mouse_action, group_keys)) == GLOBAL_SEARCH_REPEAT) {
  206             func = last_search;
  207             repeat_search = TRUE;
  208         } else
  209             repeat_search = FALSE;
  210 
  211         switch (func) {
  212             case GLOBAL_ABORT:
  213                 break;
  214 
  215             case DIGIT_1:
  216             case DIGIT_2:
  217             case DIGIT_3:
  218             case DIGIT_4:
  219             case DIGIT_5:
  220             case DIGIT_6:
  221             case DIGIT_7:
  222             case DIGIT_8:
  223             case DIGIT_9:
  224                 if (grpmenu.max)
  225                     prompt_item_num(func_to_key(func, group_keys), group->attribute->thread_articles == THREAD_NONE ? _(txt_select_art) : _(txt_select_thread));
  226                 break;
  227 
  228 #   ifndef NO_SHELL_ESCAPE
  229             case GLOBAL_SHELL_ESCAPE:
  230                 do_shell_escape();
  231                 break;
  232 #   endif /* !NO_SHELL_ESCAPE */
  233 
  234             case GLOBAL_FIRST_PAGE:     /* show first page of threads */
  235                 top_of_list();
  236                 break;
  237 
  238             case GLOBAL_LAST_PAGE:      /* show last page of threads */
  239                 end_of_list();
  240                 break;
  241 
  242             case GLOBAL_LAST_VIEWED:    /* go to last viewed article */
  243                 /*
  244                  * If the last art is no longer in a thread then we can't display it
  245                  */
  246                 if (this_resp < 0 || (which_thread(this_resp) == -1))
  247                     info_message(_(txt_no_last_message));
  248                 else
  249                     ret_code = enter_pager(this_resp, FALSE);
  250                 break;
  251 
  252             case GLOBAL_PIPE:       /* pipe article/thread/tagged arts to command */
  253                 if (grpmenu.curr >= 0)
  254                     feed_articles(FEED_PIPE, GROUP_LEVEL, NOT_ASSIGNED, group, (int) base[grpmenu.curr]);
  255                 break;
  256 
  257             case GROUP_MAIL:    /* mail article to somebody */
  258                 if (grpmenu.curr >= 0)
  259                     feed_articles(FEED_MAIL, GROUP_LEVEL, NOT_ASSIGNED, group, (int) base[grpmenu.curr]);
  260                 break;
  261 
  262 #ifndef DISABLE_PRINTING
  263             case GLOBAL_PRINT:  /* output art/thread/tagged arts to printer */
  264                 if (grpmenu.curr >= 0)
  265                     feed_articles(FEED_PRINT, GROUP_LEVEL, NOT_ASSIGNED, group, (int) base[grpmenu.curr]);
  266                 break;
  267 #endif /* !DISABLE_PRINTING */
  268 
  269             case GROUP_REPOST:  /* repost current article */
  270                 if (can_post) {
  271                     if (grpmenu.curr >= 0)
  272                         feed_articles(FEED_REPOST, GROUP_LEVEL, NOT_ASSIGNED, group, (int) base[grpmenu.curr]);
  273                 } else
  274                     info_message(_(txt_cannot_post));
  275                 break;
  276 
  277             case GROUP_SAVE:    /* save articles with prompting */
  278                 if (grpmenu.curr >= 0)
  279                     feed_articles(FEED_SAVE, GROUP_LEVEL, NOT_ASSIGNED, group, (int) base[grpmenu.curr]);
  280                 break;
  281 
  282             case GROUP_AUTOSAVE:    /* Auto-save articles without prompting */
  283                 if (grpmenu.curr >= 0)
  284                     feed_articles(FEED_AUTOSAVE, GROUP_LEVEL, NOT_ASSIGNED, group, (int) base[grpmenu.curr]);
  285                 break;
  286 
  287             case GLOBAL_SET_RANGE:  /* set range */
  288                 if (grpmenu.curr >= 0 && set_range(GROUP_LEVEL, 1, grpmenu.max, grpmenu.curr + 1)) {
  289                     range_active = TRUE;
  290                     show_group_page();
  291                 }
  292                 break;
  293 
  294             case GLOBAL_SEARCH_REPEAT:
  295                 info_message(_(txt_no_prev_search));
  296                 break;
  297 
  298             case GLOBAL_SEARCH_AUTHOR_FORWARD:
  299             case GLOBAL_SEARCH_AUTHOR_BACKWARD:
  300             case GLOBAL_SEARCH_SUBJECT_FORWARD:
  301             case GLOBAL_SEARCH_SUBJECT_BACKWARD:
  302                 if ((thread_depth = do_search(func, repeat_search)) != 0)
  303                     ret_code = enter_thread(thread_depth, NULL);
  304                 break;
  305 
  306             case GLOBAL_SEARCH_BODY:    /* search article body */
  307                 if (grpmenu.curr >= 0) {
  308                     if ((n = search_body(group, (int) base[grpmenu.curr], repeat_search)) != -1)
  309                         ret_code = enter_pager(n, FALSE);
  310                 } else
  311                     info_message(_(txt_no_arts));
  312                 break;
  313 
  314             case GROUP_READ_BASENOTE:   /* read current basenote */
  315                 if (grpmenu.curr >= 0)
  316                     ret_code = enter_pager((int) base[grpmenu.curr], FALSE /*TRUE*/);
  317                 else
  318                     info_message(_(txt_no_arts));
  319                 break;
  320 
  321             case GROUP_CANCEL:  /* cancel current basenote */
  322                 if (grpmenu.curr >= 0) {
  323                     if (can_post || group->attribute->mailing_list != NULL) {
  324                         int ret;
  325 
  326                         n = (int) base[grpmenu.curr];
  327                         ret = art_open(TRUE, &arts[n], group, &pgart, TRUE, _(txt_reading_article));
  328                         if (ret != ART_UNAVAILABLE && ret != ART_ABORT && cancel_article(group, &arts[n], n))
  329                             show_group_page();
  330                         art_close(&pgart);
  331                     } else
  332                         info_message(_(txt_cannot_post));
  333                 } else
  334                     info_message(_(txt_no_arts));
  335                 break;
  336 
  337             case GROUP_NEXT_UNREAD_ARTICLE_OR_GROUP:    /* goto next unread article/group */
  338                 ret_code = tab_pressed();
  339                 break;
  340 
  341             case GLOBAL_PAGE_DOWN:
  342                 page_down();
  343                 break;
  344 
  345             case GLOBAL_MENU_FILTER_SELECT:     /* auto-select article menu */
  346             case GLOBAL_MENU_FILTER_KILL:       /* kill article menu */
  347                 if (grpmenu.curr < 0) {
  348                     info_message(_(txt_no_arts));
  349                     break;
  350                 }
  351                 n = (int) base[grpmenu.curr];
  352                 if (filter_menu(func, group, &arts[n])) {
  353                     old_artnum = arts[n].artnum;
  354                     unfilter_articles(group);
  355                     filter_articles(group);
  356                     make_threads(group, FALSE);
  357                     grpmenu.curr = find_new_pos(old_artnum, grpmenu.curr);
  358                 }
  359                 show_group_page();
  360                 break;
  361 
  362             case GLOBAL_EDIT_FILTER:
  363                 if (invoke_editor(filter_file, filter_file_offset, NULL)) {
  364                     old_artnum = grpmenu.max > 0 ? arts[(int) base[grpmenu.curr]].artnum : T_ARTNUM_CONST(-1);
  365                     unfilter_articles(group);
  366                     (void) read_filter_file(filter_file);
  367                     filter_articles(group);
  368                     make_threads(group, FALSE);
  369                     grpmenu.curr = old_artnum >= T_ARTNUM_CONST(0) ? find_new_pos(old_artnum, grpmenu.curr) : grpmenu.max - 1;
  370                 }
  371                 show_group_page();
  372                 break;
  373 
  374             case GLOBAL_QUICK_FILTER_SELECT:        /* quickly auto-select article */
  375             case GLOBAL_QUICK_FILTER_KILL:          /* quickly kill article */
  376                 if (grpmenu.curr < 0) {
  377                     info_message(_(txt_no_arts));
  378                     break;
  379                 }
  380                 if ((!TINRC_CONFIRM_ACTION) || prompt_yn((func == GLOBAL_QUICK_FILTER_KILL) ? _(txt_quick_filter_kill) : _(txt_quick_filter_select), TRUE) == 1) {
  381                     n = (int) base[grpmenu.curr]; /* should this depend on show_only_unread_arts? */
  382                     if (quick_filter(func, group, &arts[n])) {
  383                         old_artnum = arts[n].artnum;
  384                         unfilter_articles(group);
  385                         filter_articles(group);
  386                         make_threads(group, FALSE);
  387                         grpmenu.curr = find_new_pos(old_artnum, grpmenu.curr);
  388                         show_group_page();
  389                         info_message((func == GLOBAL_QUICK_FILTER_KILL) ? _(txt_info_add_kill) : _(txt_info_add_select));
  390                     }
  391                 }
  392                 break;
  393 
  394             case GLOBAL_REDRAW_SCREEN:
  395                 my_retouch();
  396                 set_xclick_off();
  397                 show_group_page();
  398                 break;
  399 
  400             case GLOBAL_LINE_DOWN:
  401                 move_down();
  402                 break;
  403 
  404             case GLOBAL_LINE_UP:
  405                 move_up();
  406                 break;
  407 
  408             case GLOBAL_PAGE_UP:
  409                 page_up();
  410                 break;
  411 
  412             case GLOBAL_SCROLL_DOWN:
  413                 scroll_down();
  414                 break;
  415 
  416             case GLOBAL_SCROLL_UP:
  417                 scroll_up();
  418                 break;
  419 
  420             case SPECIAL_CATCHUP_LEFT:
  421             case CATCHUP:
  422             case CATCHUP_NEXT_UNREAD:
  423                 ret_code = group_catchup(func);
  424                 break;
  425 
  426             case GROUP_TOGGLE_SUBJECT_DISPLAY:  /* toggle display of subject & subj/author */
  427                 if (++curr_group->attribute->show_author > SHOW_FROM_BOTH)
  428                     curr_group->attribute->show_author = SHOW_FROM_NONE;
  429                 show_group_page();
  430                 break;
  431 
  432             case GROUP_GOTO:    /* choose a new group by name */
  433                 n = choose_new_group();
  434                 if (n >= 0 && n != selmenu.curr) {
  435                     selmenu.curr = n;
  436                     ret_code = GRP_ENTER;
  437                 }
  438                 break;
  439 
  440             case GLOBAL_HELP:
  441                 show_help_page(GROUP_LEVEL, _(txt_index_page_com));
  442                 show_group_page();
  443                 break;
  444 
  445             case GLOBAL_TOGGLE_HELP_DISPLAY:        /* toggle mini help menu */
  446                 toggle_mini_help(GROUP_LEVEL);
  447                 show_group_page();
  448                 break;
  449 
  450             case GLOBAL_TOGGLE_INVERSE_VIDEO:       /* toggle inverse video */
  451                 toggle_inverse_video();
  452                 show_group_page();
  453                 show_inverse_video_status();
  454                 break;
  455 
  456 #   ifdef HAVE_COLOR
  457             case GLOBAL_TOGGLE_COLOR:
  458                 if (toggle_color()) {
  459                     show_group_page();
  460                     show_color_status();
  461                 }
  462                 break;
  463 #   endif /* HAVE_COLOR */
  464 
  465             case GROUP_MARK_THREAD_READ:            /* mark current thread/range/tagged threads as read */
  466             case MARK_THREAD_UNREAD:                /* or unread */
  467                 if (grpmenu.curr < 0)
  468                     info_message(_(txt_no_arts));
  469                 else {
  470                     t_function function, type;
  471 
  472                     function = func == GROUP_MARK_THREAD_READ ? (t_function) FEED_MARK_READ : (t_function) FEED_MARK_UNREAD;
  473                     type = range_active ? FEED_RANGE : (num_of_tagged_arts && !group->attribute->mark_ignore_tags) ? NOT_ASSIGNED : FEED_THREAD;
  474                     feed_articles(function, GROUP_LEVEL, type, group, (int) base[grpmenu.curr]);
  475                 }
  476                 break;
  477 
  478             case GROUP_LIST_THREAD:             /* list articles within current thread */
  479                 ret_code = enter_thread(0, NULL);   /* Enter thread at the top */
  480                 break;
  481 
  482             case GLOBAL_LOOKUP_MESSAGEID:
  483                 if ((i = prompt_msgid()) != ART_UNAVAILABLE)
  484                     ret_code = enter_pager(i, FALSE);
  485                 break;
  486 
  487             case GLOBAL_OPTION_MENU:            /* option menu */
  488                 old_artnum = grpmenu.max > 0 ? arts[(int) base[grpmenu.curr]].artnum : T_ARTNUM_CONST(-1);
  489                 config_page(group->name, signal_context);
  490                 grpmenu.curr = old_artnum >= T_ARTNUM_CONST(0) ? find_new_pos(old_artnum, grpmenu.curr) : grpmenu.max - 1;
  491                 show_group_page();
  492                 break;
  493 
  494             case GROUP_NEXT_GROUP:          /* goto next group */
  495                 clear_message();
  496                 if (selmenu.curr + 1 >= selmenu.max)
  497                     info_message(_(txt_no_more_groups));
  498                 else {
  499                     if (xflag && TINRC_CONFIRM_SELECT && (prompt_yn(_(txt_confirm_select_on_exit), FALSE) != 1)) {
  500                         undo_auto_select_arts();
  501                         xflag = FALSE;
  502                     }
  503                     selmenu.curr++;
  504                     ret_code = GRP_NEXTUNREAD;
  505                 }
  506                 break;
  507 
  508             case GROUP_NEXT_UNREAD_ARTICLE: /* goto next unread article */
  509                 if (grpmenu.curr < 0) {
  510                     info_message(_(txt_no_next_unread_art));
  511                     break;
  512                 }
  513                 if ((n = next_unread((int) base[grpmenu.curr])) == -1)
  514                     info_message(_(txt_no_next_unread_art));
  515                 else
  516                     ret_code = enter_pager(n, FALSE);
  517                 break;
  518 
  519             case GROUP_PREVIOUS_UNREAD_ARTICLE: /* go to previous unread article */
  520                 if (grpmenu.curr < 0) {
  521                     info_message(_(txt_no_prev_unread_art));
  522                     break;
  523                 }
  524 
  525                 if ((n = prev_unread(prev_response((int) base[grpmenu.curr]))) == -1)
  526                     info_message(_(txt_no_prev_unread_art));
  527                 else
  528                     ret_code = enter_pager(n, FALSE);
  529                 break;
  530 
  531             case GROUP_PREVIOUS_GROUP:  /* previous group */
  532                 clear_message();
  533                 for (i = selmenu.curr - 1; i >= 0; i--) {
  534                     if (UNREAD_GROUP(i))
  535                         break;
  536                 }
  537                 if (i < 0)
  538                     info_message(_(txt_no_prev_group));
  539                 else {
  540                     if (xflag && TINRC_CONFIRM_SELECT && (prompt_yn(_(txt_confirm_select_on_exit), FALSE) != 1)) {
  541                         undo_auto_select_arts();
  542                         xflag = FALSE;
  543                     }
  544                     selmenu.curr = i;
  545                     ret_code = GRP_NEXTUNREAD;
  546                 }
  547                 break;
  548 
  549             case GLOBAL_QUIT:   /* return to group selection page */
  550                 if (num_of_tagged_arts && prompt_yn(_(txt_quit_despite_tags), TRUE) != 1)
  551                     break;
  552                 if (xflag && TINRC_CONFIRM_SELECT && (prompt_yn(_(txt_confirm_select_on_exit), FALSE) != 1)) {
  553                     undo_auto_select_arts();
  554                     xflag = FALSE;
  555                 }
  556                 ret_code = GRP_EXIT;
  557                 break;
  558 
  559             case GLOBAL_QUIT_TIN:       /* quit */
  560                 if (num_of_tagged_arts && prompt_yn(_(txt_quit_despite_tags), TRUE) != 1)
  561                     break;
  562                 if (xflag && TINRC_CONFIRM_SELECT && (prompt_yn(_(txt_confirm_select_on_exit), FALSE) != 1)) {
  563                     undo_auto_select_arts();
  564                     xflag = FALSE;
  565                 }
  566                 ret_code = GRP_QUIT;
  567                 break;
  568 
  569             case GROUP_TOGGLE_READ_UNREAD:
  570                 toggle_read_unread(FALSE);
  571                 show_group_page();
  572                 break;
  573 
  574             case GROUP_TOGGLE_GET_ARTICLES_LIMIT:
  575                 if (prompt_getart_limit()) {
  576                     /*
  577                      * if getart limit was given via cmd-line
  578                      * make it inactive now in order to use
  579                      * tinrc.getart_limit
  580                      */
  581                     if (cmdline.args & CMDLINE_GETART_LIMIT)
  582                         cmdline.args &= ~CMDLINE_GETART_LIMIT;
  583                     ret_code = GRP_NEXTUNREAD;
  584                 }
  585                 break;
  586 
  587             case GLOBAL_BUGREPORT:
  588                 bug_report();
  589                 break;
  590 
  591             case GROUP_TAG_PARTS: /* tag all in order */
  592                 if (0 <= grpmenu.curr) {
  593                     if (tag_multipart(grpmenu.curr) != 0) {
  594                         /*
  595                          * on success, move the pointer to the next
  596                          * untagged article just for ease of use's sake
  597                          */
  598                         n = grpmenu.curr;
  599                         update_group_page();
  600                         do {
  601                             n++;
  602                             n %= grpmenu.max;
  603                             if (arts[base[n]].tagged == 0) {
  604                                 move_to_item(n);
  605                                 break;
  606                             }
  607                         } while (n != grpmenu.curr);
  608                         info_message(_(txt_info_all_parts_tagged));
  609                     }
  610                 }
  611                 break;
  612 
  613             case GROUP_TAG:     /* tag/untag threads for mailing/piping/printing/saving */
  614                 if (grpmenu.curr >= 0) {
  615                     t_bool tagged = TRUE;
  616 
  617                     n = (int) base[grpmenu.curr];
  618 
  619                     /*
  620                      * This loop looks for any article in the thread that
  621                      * isn't already tagged.
  622                      */
  623                     for (ii = n; ii != -1 && tagged; ii = arts[ii].thread) {
  624                         if (arts[ii].tagged == 0) {
  625                             tagged = FALSE;
  626                             break;
  627                         }
  628                     }
  629 
  630                     /*
  631                      * If the whole thread is tagged, untag it. Otherwise, tag
  632                      * any untagged articles
  633                      */
  634                     if (tagged) {
  635                         /*
  636                          * Here we repeat the tagged test in both blocks
  637                          * to leave the choice of tagged/untagged
  638                          * determination politic in the previous lines.
  639                          */
  640                         for (ii = n; ii != -1; ii = arts[ii].thread) {
  641                             if (arts[ii].tagged != 0) {
  642                                 tagged = TRUE;
  643                                 untag_article(ii);
  644                             }
  645                         }
  646                     } else {
  647                         for (ii = n; ii != -1; ii = arts[ii].thread) {
  648                             if (arts[ii].tagged == 0)
  649                                 arts[ii].tagged = ++num_of_tagged_arts;
  650                         }
  651                     }
  652                     if ((ii = line_is_tagged(n)))
  653                         mark_screen(grpmenu.curr, mark_offset - 2, tin_ltoa(ii, 3));
  654                     else {
  655                         char mark[] = { '\0', '\0' };
  656 
  657                         stat_thread(grpmenu.curr, &sbuf);
  658                         mark[0] = sbuf.art_mark;
  659                         mark_screen(grpmenu.curr, mark_offset - 2, "  "); /* clear space used by tag numbering */
  660                         mark_screen(grpmenu.curr, mark_offset, mark);
  661                     }
  662                     if (tagged)
  663                         show_tagged_lines();
  664 
  665                     if (grpmenu.curr + 1 < grpmenu.max)
  666                         move_down();
  667                     else
  668                         draw_subject_arrow();
  669 
  670                     info_message(tagged ? _(txt_prefix_untagged) : _(txt_prefix_tagged), txt_thread_singular);
  671 
  672                 }
  673                 break;
  674 
  675             case GROUP_TOGGLE_THREADING:        /* Cycle through the threading types */
  676                 group->attribute->thread_articles = (group->attribute->thread_articles + 1) % (THREAD_MAX + 1);
  677                 if (grpmenu.curr >= 0) {
  678                     i = base[grpmenu.curr];                             /* Save a copy of current thread */
  679                     make_threads(group, TRUE);
  680                     find_base(group);
  681                     if ((grpmenu.curr = which_thread(i)) < 0)           /* Restore current position in group */
  682                         grpmenu.curr = 0;
  683                 }
  684                 show_group_page();
  685                 break;
  686 
  687             case GROUP_UNTAG:   /* untag all articles */
  688                 if (grpmenu.curr >= 0) {
  689                     if (untag_all_articles())
  690                         update_group_page();
  691                 }
  692                 break;
  693 
  694             case GLOBAL_VERSION:
  695                 info_message(cvers);
  696                 break;
  697 
  698             case GLOBAL_POST:   /* post an article */
  699                 if (post_article(group->name))
  700                     show_group_page();
  701                 break;
  702 
  703             case GLOBAL_POSTPONED:  /* post postponed article */
  704                 if (can_post) {
  705                     if (pickup_postponed_articles(FALSE, FALSE))
  706                         show_group_page();
  707                 } else
  708                     info_message(_(txt_cannot_post));
  709                 break;
  710 
  711             case GLOBAL_DISPLAY_POST_HISTORY:   /* display messages posted by user */
  712                 if (user_posted_messages())
  713                     show_group_page();
  714                 break;
  715 
  716             case MARK_ARTICLE_UNREAD:       /* mark base article of thread unread */
  717                 if (grpmenu.curr < 0)
  718                     info_message(_(txt_no_arts));
  719                 else {
  720                     const char *ptr;
  721 
  722                     if (range_active) {
  723                         /*
  724                          * We are tied to following base[] here, not arts[], as we operate on
  725                          * the base articles by definition.
  726                          */
  727                         for (ii = 0; ii < grpmenu.max; ++ii) {
  728                             if (arts[base[ii]].inrange) {
  729                                 arts[base[ii]].inrange = FALSE;
  730                                 art_mark(group, &arts[base[ii]], ART_WILL_RETURN);
  731                                 for_each_art_in_thread(i, ii)
  732                                     arts[i].inrange = FALSE;
  733                             }
  734                         }
  735                         range_active = FALSE;
  736                         show_group_page();
  737                         ptr = _(txt_base_article_range);
  738                     } else {
  739                         art_mark(group, &arts[base[grpmenu.curr]], ART_WILL_RETURN);
  740                         ptr = _(txt_base_article);
  741                     }
  742 
  743                     show_group_title(TRUE);
  744                     build_sline(grpmenu.curr);
  745                     draw_subject_arrow();
  746                     info_message(_(txt_marked_as_unread), ptr);
  747                 }
  748                 break;
  749 
  750             case MARK_FEED_READ:    /* mark selected articles as read */
  751                 if (grpmenu.curr >= 0)
  752                     feed_articles(FEED_MARK_READ, GROUP_LEVEL, NOT_ASSIGNED, group, (int) base[grpmenu.curr]);
  753                 break;
  754 
  755             case MARK_FEED_UNREAD:  /* mark selected articles as unread */
  756                 if (grpmenu.curr >= 0)
  757                     feed_articles(FEED_MARK_UNREAD, GROUP_LEVEL, NOT_ASSIGNED, group, (int) base[grpmenu.curr]);
  758                 break;
  759 
  760             case GROUP_SELECT_THREAD:   /* mark thread as selected */
  761             case GROUP_TOGGLE_SELECT_THREAD:    /* toggle thread */
  762                 if (grpmenu.curr < 0) {
  763                     info_message(_(txt_no_arts));
  764                     break;
  765                 }
  766 
  767                 flag = TRUE;
  768                 if (func == GROUP_TOGGLE_SELECT_THREAD) {
  769                     stat_thread(grpmenu.curr, &sbuf);
  770                     if (sbuf.selected_unread == sbuf.unread)
  771                         flag = FALSE;
  772                 }
  773                 n = 0;
  774                 for_each_art_in_thread(i, grpmenu.curr) {
  775                     arts[i].selected = flag;
  776                     ++n;
  777                 }
  778                 assert(n > 0);
  779                 {
  780                     char mark[] = { '\0', '\0' };
  781 
  782                     stat_thread(grpmenu.curr, &sbuf);
  783                     mark[0] = sbuf.art_mark;
  784                     mark_screen(grpmenu.curr, mark_offset, mark);
  785                 }
  786 
  787                 show_group_title(TRUE);
  788 
  789                 if (grpmenu.curr + 1 < grpmenu.max)
  790                     move_down();
  791                 else
  792                     draw_subject_arrow();
  793 
  794                 info_message(flag ? _(txt_thread_marked_as_selected) : _(txt_thread_marked_as_deselected));
  795                 break;
  796 
  797             case GROUP_REVERSE_SELECTIONS:  /* reverse selections */
  798                 for_each_art(i)
  799                     arts[i].selected = bool_not(arts[i].selected);
  800                 update_group_page();
  801                 show_group_title(TRUE);
  802                 break;
  803 
  804             case GROUP_UNDO_SELECTIONS: /* undo selections */
  805                 undo_selections();
  806                 xflag = FALSE;
  807                 show_group_title(TRUE);
  808                 update_group_page();
  809                 break;
  810 
  811             case GROUP_SELECT_PATTERN:  /* select matching patterns */
  812                 if (grpmenu.curr >= 0) {
  813                     char pat[128];
  814                     char *prompt;
  815                     struct regex_cache cache = { NULL, NULL };
  816 
  817                     prompt = fmt_string(_(txt_select_pattern), tinrc.default_select_pattern);
  818                     if (!(prompt_string_default(prompt, tinrc.default_select_pattern, _(txt_info_no_previous_expression), HIST_SELECT_PATTERN))) {
  819                         free(prompt);
  820                         break;
  821                     }
  822                     free(prompt);
  823 
  824                     if (STRCMPEQ(tinrc.default_select_pattern, "*")) {  /* all */
  825                         if (tinrc.wildcard)
  826                             STRCPY(pat, ".*");
  827                         else
  828                             STRCPY(pat, tinrc.default_select_pattern);
  829                     } else
  830                         snprintf(pat, sizeof(pat), REGEX_FMT, tinrc.default_select_pattern);
  831 
  832                     if (tinrc.wildcard && !(compile_regex(pat, &cache, PCRE_CASELESS)))
  833                         break;
  834 
  835                     flag = FALSE;
  836                     for (n = 0; n < grpmenu.max; n++) {
  837                         if (!match_regex(arts[base[n]].subject, pat, &cache, TRUE))
  838                             continue;
  839 
  840                         for_each_art_in_thread(i, n)
  841                             arts[i].selected = TRUE;
  842 
  843                         flag = TRUE;
  844                     }
  845                     if (flag) {
  846                         show_group_title(TRUE);
  847                         update_group_page();
  848                     }
  849                     if (tinrc.wildcard) {
  850                         FreeIfNeeded(cache.re);
  851                         FreeIfNeeded(cache.extra);
  852                     }
  853                 }
  854                 break;
  855 
  856             case GROUP_SELECT_THREAD_IF_UNREAD_SELECTED:    /* select all unread arts in thread hot if 1 is hot */
  857                 for (n = 0; n < grpmenu.max; n++) {
  858                     stat_thread(n, &sbuf);
  859                     if (!sbuf.selected_unread || sbuf.selected_unread == sbuf.unread)
  860                         continue;
  861 
  862                     for_each_art_in_thread(i, n)
  863                         arts[i].selected = TRUE;
  864                 }
  865                 show_group_title(TRUE);
  866                 break;
  867 
  868             case GROUP_MARK_UNSELECTED_ARTICLES_READ:   /* mark read all unselected arts */
  869                 if (!xflag) {
  870                     do_auto_select_arts();
  871                     xflag = TRUE;
  872                 } else {
  873                     undo_auto_select_arts();
  874                     xflag = FALSE;
  875                 }
  876                 break;
  877 
  878             case GROUP_DO_AUTOSELECT:       /* perform auto-selection on group */
  879                 for (n = 0; n < grpmenu.max; n++) {
  880                     for_each_art_in_thread(i, n)
  881                         arts[i].selected = TRUE;
  882                 }
  883                 update_group_page();
  884                 show_group_title(TRUE);
  885                 break;
  886 
  887             case GLOBAL_TOGGLE_INFO_LAST_LINE:
  888                 tinrc.info_in_last_line = bool_not(tinrc.info_in_last_line);
  889                 show_group_page();
  890                 break;
  891 
  892             default:
  893                 info_message(_(txt_bad_command), printascii(key, func_to_key(GLOBAL_HELP, group_keys)));
  894                 break;
  895         } /* switch(ch) */
  896     } /* ret_code >= 0 */
  897 
  898     set_xclick_off();
  899 
  900     clear_note_area();
  901     grp_del_mail_arts(group);
  902 
  903     art_close(&pgart);              /* Close any open art */
  904 
  905     curr_group = NULL;
  906 
  907     return ret_code;
  908 }
  909 
  910 
  911 void
  912 show_group_page(
  913     void)
  914 {
  915     int i;
  916 
  917     signal_context = cGroup;
  918     currmenu = &grpmenu;
  919 
  920     ClearScreen();
  921     set_first_screen_item();
  922     parse_format_string(curr_group->attribute->group_format, &grp_fmt);
  923     mark_offset = 0;
  924     show_group_title(FALSE);
  925 
  926     for (i = grpmenu.first; i < grpmenu.first + NOTESLINES && i < grpmenu.max; ++i)
  927         build_sline(i);
  928 
  929     show_mini_help(GROUP_LEVEL);
  930 
  931     if (grpmenu.max <= 0) {
  932         info_message(_(txt_no_arts));
  933         return;
  934     }
  935 
  936     draw_subject_arrow();
  937 }
  938 
  939 
  940 static void
  941 update_group_page(
  942     void)
  943 {
  944     int i, j;
  945     char mark[] = { '\0', '\0' };
  946     struct t_art_stat sbuf;
  947 
  948     for (i = grpmenu.first; i < grpmenu.first + NOTESLINES && i < grpmenu.max; ++i) {
  949         if ((j = line_is_tagged(base[i])))
  950             mark_screen(i, mark_offset - 2, tin_ltoa(j, 3));
  951         else {
  952             stat_thread(i, &sbuf);
  953             mark[0] = sbuf.art_mark;
  954             mark_screen(i, mark_offset - 2, "  ");  /* clear space used by tag numbering */
  955             mark_screen(i, mark_offset, mark);
  956             if (sbuf.art_mark == tinrc.art_marked_selected)
  957                 draw_mark_selected(i);
  958         }
  959     }
  960 
  961     if (grpmenu.max <= 0)
  962         return;
  963 
  964     draw_subject_arrow();
  965 }
  966 
  967 
  968 static void
  969 draw_subject_arrow(
  970     void)
  971 {
  972     draw_arrow_mark(INDEX_TOP + grpmenu.curr - grpmenu.first);
  973 
  974     if (tinrc.info_in_last_line) {
  975         struct t_art_stat statbuf;
  976 
  977         stat_thread(grpmenu.curr, &statbuf);
  978         info_message("%s", arts[(statbuf.unread ? next_unread(base[grpmenu.curr]) : base[grpmenu.curr])].subject);
  979     } else if (grpmenu.curr == grpmenu.max - 1)
  980         info_message(_(txt_end_of_arts));
  981 }
  982 
  983 
  984 void
  985 clear_note_area(
  986     void)
  987 {
  988     MoveCursor(INDEX_TOP, 0);
  989     CleartoEOS();
  990 }
  991 
  992 
  993 /*
  994  * If in show_only_unread_arts mode or there are unread articles we know this
  995  * thread will exist after toggle. Otherwise we find the next closest to
  996  * return to. 'force' can be set to force tin to show all messages
  997  */
  998 static void
  999 toggle_read_unread(
 1000     t_bool force)
 1001 {
 1002     int n, i = -1;
 1003 
 1004     /*
 1005      * Clear art->keep_in_base if switching to !show_only_unread_arts
 1006      */
 1007     if (curr_group->attribute->show_only_unread_arts) {
 1008         for_each_art(n)
 1009             arts[n].keep_in_base = FALSE;
 1010     }
 1011 
 1012     /* force currently is always false */
 1013     if (force)
 1014         curr_group->attribute->show_only_unread_arts = TRUE;    /* Yes - really, we change it in a bit */
 1015 
 1016     wait_message(0, _(txt_reading_arts),
 1017         (curr_group->attribute->show_only_unread_arts) ? _(txt_all) : _(txt_unread));
 1018 
 1019     if (grpmenu.curr >= 0) {
 1020         if (curr_group->attribute->show_only_unread_arts || new_responses(grpmenu.curr))
 1021             i = base[grpmenu.curr];
 1022         else if ((n = prev_unread((int) base[grpmenu.curr])) >= 0)
 1023             i = n;
 1024         else if ((n = next_unread((int) base[grpmenu.curr])) >= 0)
 1025             i = n;
 1026     }
 1027 
 1028     if (!force)
 1029         curr_group->attribute->show_only_unread_arts = bool_not(curr_group->attribute->show_only_unread_arts);
 1030 
 1031     find_base(curr_group);
 1032     if (i >= 0 && (n = which_thread(i)) >= 0)
 1033         grpmenu.curr = n;
 1034     else if (grpmenu.max > 0)
 1035         grpmenu.curr = grpmenu.max - 1;
 1036     clear_message();
 1037 }
 1038 
 1039 
 1040 /*
 1041  * Find new index position after a kill or unkill. Because kill can work on
 1042  * author it is impossible to know which, if any, articles will be left
 1043  * afterwards. So we make a "best attempt" to find a new index point.
 1044  */
 1045 static int
 1046 find_new_pos(
 1047     long old_artnum,
 1048     int cur_pos)
 1049 {
 1050     int i, pos;
 1051 
 1052     if ((i = find_artnum(old_artnum)) >= 0 && (pos = which_thread(i)) >= 0)
 1053         return pos;
 1054 
 1055     return ((cur_pos < grpmenu.max) ? cur_pos : (grpmenu.max - 1));
 1056 }
 1057 
 1058 
 1059 /*
 1060  * Set grpmenu.curr to the first unread or the last thread depending on
 1061  * the value of pos_first_unread
 1062  */
 1063 void
 1064 pos_first_unread_thread(
 1065     void)
 1066 {
 1067     int i;
 1068 
 1069     if (curr_group->attribute->pos_first_unread) {
 1070         for (i = 0; i < grpmenu.max; i++) {
 1071             if (new_responses(i))
 1072                 break;
 1073         }
 1074         grpmenu.curr = ((i < grpmenu.max) ? i : (grpmenu.max - 1));
 1075     } else
 1076         grpmenu.curr = grpmenu.max - 1;
 1077 }
 1078 
 1079 
 1080 void
 1081 mark_screen(
 1082     int screen_row,
 1083     int screen_col,
 1084     const char *value)
 1085 {
 1086     if (tinrc.draw_arrow) {
 1087         MoveCursor(INDEX2LNUM(screen_row), screen_col);
 1088         my_fputs(value, stdout);
 1089         stow_cursor();
 1090         my_flush();
 1091     } else {
 1092 #ifdef USE_CURSES
 1093         int y, x;
 1094         getyx(stdscr, y, x);
 1095         mvaddstr(INDEX2LNUM(screen_row), screen_col, value);
 1096         MoveCursor(y, x);
 1097 #else
 1098         int i;
 1099         for (i = 0; value[i] != '\0'; i++)
 1100             screen[INDEX2SNUM(screen_row)].col[screen_col + i] = value[i];
 1101         MoveCursor(INDEX2LNUM(screen_row), screen_col);
 1102         my_fputs(value, stdout);
 1103 #endif /* USE_CURSES */
 1104         currmenu->draw_arrow();
 1105     }
 1106 }
 1107 
 1108 
 1109 /*
 1110  *  Builds the correct header for multipart messages when sorting via
 1111  *  THREAD_MULTI.
 1112  */
 1113 static void
 1114 build_multipart_header(
 1115     char *dest,
 1116     int maxlen,
 1117     const char *src,
 1118     int cmplen,
 1119     int have,
 1120     int total)
 1121 {
 1122     const char *mark = (have == total) ? "*" : "-";
 1123     char *ss;
 1124 
 1125     if (cmplen > maxlen)
 1126         strncpy(dest, src, maxlen);
 1127     else {
 1128         strncpy(dest, src, cmplen);
 1129         ss = dest + cmplen;
 1130         snprintf(ss, maxlen - cmplen, "(%s/%d)", mark, total);
 1131     }
 1132 }
 1133 
 1134 
 1135 /*
 1136  * Build subject line given an index into base[].
 1137  *
 1138  * WARNING: some other code expects to find the article mark (ART_MARK_READ,
 1139  * ART_MARK_SELECTED, etc) at mark_offset from beginning of the line.
 1140  * So, if you change the format used in this routine, be sure to check that
 1141  * the value of mark_offset is still correct.
 1142  * Yes, this is somewhat kludgy.
 1143  */
 1144 static void
 1145 build_sline(
 1146     int i)
 1147 {
 1148     char *fmt, *buf;
 1149     char *buffer;
 1150     char arts_sub[HEADER_LEN];
 1151     char tmp_buf[8];
 1152     char tmp[LEN];
 1153     int respnum;
 1154     int j, k, n;
 1155     size_t len;
 1156     struct t_art_stat sbuf;
 1157 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
 1158     wchar_t *wtmp, *wtmp2;
 1159 #else
 1160     int fill, gap;
 1161     size_t len_start;
 1162 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
 1163 
 1164 #ifdef USE_CURSES
 1165     /*
 1166      * Allocate line buffer
 1167      * make it the same size like in !USE_CURSES case to simplify the code
 1168      */
 1169 #   if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
 1170         buffer = my_malloc(cCOLS * MB_CUR_MAX + 2);
 1171 #   else
 1172         buffer = my_malloc(cCOLS + 2);
 1173 #   endif /* MULTIBYTE_ABLE && !NO_LOCALE */
 1174 #else
 1175     buffer = screen[INDEX2SNUM(i)].col;
 1176 #endif /* USE_CURSES */
 1177 
 1178     buffer[0] = '\0';
 1179 
 1180     respnum = (int) base[i];
 1181 
 1182     stat_thread(i, &sbuf);
 1183 
 1184     /*
 1185      * Find index of first unread in this thread
 1186      */
 1187     j = (sbuf.unread) ? next_unread(respnum) : respnum;
 1188 
 1189     fmt = grp_fmt.str;
 1190 
 1191     if (tinrc.draw_arrow)
 1192         strcat(buffer, "  ");
 1193 
 1194     for (; *fmt; fmt++) {
 1195         if (*fmt != '%') {
 1196             strncat(buffer, fmt, 1);
 1197             continue;
 1198         }
 1199         switch (*++fmt) {
 1200             case '\0':
 1201                 break;
 1202 
 1203             case '%':
 1204                 strncat(buffer, fmt, 1);
 1205                 break;
 1206 
 1207             case 'D':   /* date */
 1208                 buf = my_malloc(LEN);
 1209                 if (my_strftime(buf, LEN - 1, grp_fmt.date_str, localtime((const time_t *) &arts[j].date))) {
 1210 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
 1211                     if ((wtmp = char2wchar_t(buf)) != NULL) {
 1212                         wtmp2 = wcspart(wtmp, grp_fmt.len_date_max, TRUE);
 1213                         if (wcstombs(tmp, wtmp2, sizeof(tmp) - 1) != (size_t) -1)
 1214                             strcat(buffer, tmp);
 1215 
 1216                         free(wtmp);
 1217                         free(wtmp2);
 1218                     }
 1219 #else
 1220                     strncat(buffer, buf, grp_fmt.len_date_max);
 1221 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
 1222                 }
 1223                 free(buf);
 1224                 break;
 1225 
 1226             case 'F':   /* from */
 1227                 if (curr_group->attribute->show_author != SHOW_FROM_NONE) {
 1228 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
 1229                     get_author(FALSE, &arts[j], tmp, sizeof(tmp) - 1);
 1230 
 1231                     if ((wtmp = char2wchar_t(tmp)) != NULL) {
 1232                         wtmp2 = wcspart(wtmp, grp_fmt.len_from, TRUE);
 1233                         if (wcstombs(tmp, wtmp2, sizeof(tmp) - 1) != (size_t) -1)
 1234                             strcat(buffer, tmp);
 1235 
 1236                         free(wtmp);
 1237                         free(wtmp2);
 1238                     }
 1239 #else
 1240                     len_start = strwidth(buffer);
 1241                     get_author(FALSE, &arts[j], buffer + strlen(buffer), grp_fmt.len_from);
 1242                     fill = grp_fmt.len_from - (strwidth(buffer) - len_start);
 1243                     gap = strlen(buffer);
 1244                     for (k = 0; k < fill; k++)
 1245                         buffer[gap + k] = ' ';
 1246                     buffer[gap + fill] = '\0';
 1247 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
 1248                 }
 1249                 break;
 1250 
 1251             case 'I':   /* initials */
 1252                 len = MIN(grp_fmt.len_initials, sizeof(tmp) - 1);
 1253                 get_initials(&arts[j], tmp, len);
 1254                 strcat(buffer, tmp);
 1255                 if ((k = len - strwidth(tmp)) > 0) {
 1256                     buf = buffer + strlen(buffer);
 1257                     for (; k > 0; --k)
 1258                         *buf++ = ' ';
 1259                     *buf = '\0';
 1260                 }
 1261                 break;
 1262 
 1263             case 'L':   /* lines */
 1264                 if (arts[j].line_count != -1)
 1265                     strcat(buffer, tin_ltoa(arts[j].line_count, grp_fmt.len_linecnt));
 1266                 else {
 1267                     buf = buffer + strlen(buffer);
 1268                     for (k = grp_fmt.len_linecnt; k > 1; --k)
 1269                         *buf++ = ' ';
 1270                     *buf++ = '?';
 1271                     *buf = '\0';
 1272                 }
 1273                 break;
 1274 
 1275             case 'm':   /* article flags, tag number, or whatever */
 1276                 if (!grp_fmt.mark_offset)
 1277                     grp_fmt.mark_offset = mark_offset = strwidth(buffer) + 2;
 1278                 if ((k = line_is_tagged(respnum)))
 1279                     STRCPY(tmp_buf, tin_ltoa(k, 3));
 1280                 else
 1281                     snprintf(tmp_buf, sizeof(tmp_buf), "  %c", sbuf.art_mark);
 1282                 strcat(buffer, tmp_buf);
 1283                 break;
 1284 
 1285             case 'M':   /* message-id */
 1286                 len = MIN(grp_fmt.len_msgid, sizeof(tmp) - 1);
 1287                 strncpy(tmp, arts[j].refptr ? arts[j].refptr->txt : "", len);
 1288                 tmp[len] = '\0';
 1289                 strcat(buffer, tmp);
 1290                 if ((k = len - strwidth(tmp)) > 0) {
 1291                     buf = buffer + strlen(buffer);
 1292                     for (; k > 0; --k)
 1293                         *buf++ = ' ';
 1294                     *buf = '\0';
 1295                 }
 1296                 break;
 1297 
 1298             case 'n':
 1299                 strcat(buffer, tin_ltoa(i + 1, grp_fmt.len_linenumber));
 1300                 break;
 1301 
 1302             case 'R':
 1303                 n = ((curr_group->attribute->show_only_unread_arts) ? (sbuf.unread + sbuf.seen) : sbuf.total);
 1304                 if (n > 1)
 1305                     strcat(buffer, tin_ltoa(n, grp_fmt.len_respcnt));
 1306                 else {
 1307                     buf = buffer + strlen(buffer);
 1308                     for (k = grp_fmt.len_respcnt; k > 0; --k)
 1309                         *buf++ = ' ';
 1310                     *buf = '\0';
 1311                 }
 1312                 break;
 1313 
 1314             case 'S':   /* score */
 1315                 strcat(buffer, tin_ltoa(sbuf.score, grp_fmt.len_score));
 1316                 break;
 1317 
 1318             case 's':   /* thread/subject */
 1319                 len = curr_group->attribute->show_author != SHOW_FROM_NONE ? grp_fmt.len_subj : grp_fmt.len_subj + grp_fmt.len_from;
 1320 
 1321                 if (sbuf.multipart_have > 1) /* We have a multipart msg so lets built our new header info. */
 1322                     build_multipart_header(arts_sub, len, arts[j].subject, sbuf.multipart_compare_len, sbuf.multipart_have, sbuf.multipart_total);
 1323                 else
 1324                     STRCPY(arts_sub, arts[j].subject);
 1325 
 1326 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
 1327                 if ((wtmp = char2wchar_t(arts_sub)) != NULL) {
 1328                     wtmp2 = wcspart(wtmp, len, TRUE);
 1329                     if (wcstombs(tmp, wtmp2, sizeof(tmp) - 1) != (size_t) -1)
 1330                         strcat(buffer, tmp);
 1331 
 1332                     free(wtmp);
 1333                     free(wtmp2);
 1334                 }
 1335 #else
 1336                 len_start = strwidth(buffer);
 1337                 strncat(buffer, arts_sub, len);
 1338                 fill = len - (strwidth(buffer) - len_start);
 1339                 gap = strlen(buffer);
 1340                 for (k = 0; k < fill; k++)
 1341                     buffer[gap + k] = ' ';
 1342                 buffer[gap + fill] = '\0';
 1343 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
 1344                 break;
 1345 
 1346             default:
 1347                 break;
 1348         }
 1349     }
 1350     /* protect display from non-displayable characters (e.g., form-feed) */
 1351     convert_to_printable(buffer, FALSE);
 1352 
 1353 #ifndef USE_CURSES
 1354     if (tinrc.strip_blanks)
 1355         strcat(strip_line(buffer), cCRLF);
 1356 #endif /* !USE_CURSES */
 1357 
 1358     WriteLine(INDEX2LNUM(i), buffer);
 1359 
 1360 #ifdef USE_CURSES
 1361     free(buffer);
 1362 #endif /* USE_CURSES */
 1363     if (sbuf.art_mark == tinrc.art_marked_selected)
 1364         draw_mark_selected(i);
 1365 }
 1366 
 1367 
 1368 static void
 1369 show_group_title(
 1370     t_bool clear_title)
 1371 {
 1372     char buf[LEN], tmp[LEN], flag;
 1373     int i, art_cnt = 0, recent_art_cnt = 0, selected_art_cnt = 0, read_selected_art_cnt = 0, killed_art_cnt = 0;
 1374 
 1375     for_each_art(i) {
 1376         if (arts[i].thread == ART_EXPIRED)
 1377             continue;
 1378 
 1379         if (curr_group->attribute->show_only_unread_arts) {
 1380             if (arts[i].status != ART_READ) {
 1381                 art_cnt++;
 1382                 if (tinrc.recent_time && ((time((time_t *) 0) - arts[i].date) < (tinrc.recent_time * DAY)))
 1383                     recent_art_cnt++;
 1384             }
 1385             if (arts[i].killed == ART_KILLED_UNREAD)
 1386                 killed_art_cnt++;
 1387         } else {
 1388             art_cnt++;
 1389             if (tinrc.recent_time && ((time((time_t *) 0) - arts[i].date) < (tinrc.recent_time * DAY)))
 1390                 recent_art_cnt++;
 1391 
 1392             if (arts[i].killed)
 1393                 killed_art_cnt++;
 1394         }
 1395         if (arts[i].selected) {
 1396             if (arts[i].status != ART_READ)
 1397                 selected_art_cnt++;
 1398             else
 1399                 read_selected_art_cnt++;
 1400         }
 1401     }
 1402 
 1403     /*
 1404      * build the group title
 1405      */
 1406     /* group name and thread count */
 1407     snprintf(buf, sizeof(buf), "%s (%d%c",
 1408         curr_group->name, grpmenu.max,
 1409         *txt_threading[curr_group->attribute->thread_articles]);
 1410 
 1411     /* article count */
 1412     if ((cmdline.args & CMDLINE_GETART_LIMIT) ? cmdline.getart_limit : tinrc.getart_limit)
 1413         snprintf(tmp, sizeof(tmp), " %d/%d%c",
 1414             (cmdline.args & CMDLINE_GETART_LIMIT) ? cmdline.getart_limit : tinrc.getart_limit, art_cnt,
 1415             (curr_group->attribute->show_only_unread_arts ? tinrc.art_marked_unread : tinrc.art_marked_read));
 1416     else
 1417         snprintf(tmp, sizeof(tmp), " %d%c",
 1418             art_cnt,
 1419             (curr_group->attribute->show_only_unread_arts ? tinrc.art_marked_unread : tinrc.art_marked_read));
 1420     if (sizeof(buf) > strlen(buf) + strlen(tmp))
 1421         strcat(buf, tmp);
 1422 
 1423     /* selected articles */
 1424     if (curr_group->attribute->show_only_unread_arts)
 1425         snprintf(tmp, sizeof(tmp), " %d%c",
 1426             selected_art_cnt, tinrc.art_marked_selected);
 1427     else
 1428         snprintf(tmp, sizeof(tmp), " %d%c %d%c",
 1429             selected_art_cnt, tinrc.art_marked_selected,
 1430             read_selected_art_cnt, tinrc.art_marked_read_selected);
 1431     if (sizeof(buf) > strlen(buf) + strlen(tmp))
 1432         strcat(buf, tmp);
 1433 
 1434     /* recent articles */
 1435     if (tinrc.recent_time) {
 1436         snprintf(tmp, sizeof(tmp), " %d%c",
 1437             recent_art_cnt, tinrc.art_marked_recent);
 1438 
 1439         if (sizeof(buf) > strlen(buf) + strlen(tmp))
 1440             strcat(buf, tmp);
 1441     }
 1442 
 1443     /* killed articles */
 1444     snprintf(tmp, sizeof(tmp), " %d%c",
 1445         killed_art_cnt, tinrc.art_marked_killed);
 1446     if (sizeof(buf) > strlen(buf) + strlen(tmp))
 1447         strcat(buf, tmp);
 1448 
 1449     /* group flag */
 1450     if ((flag = group_flag(curr_group->moderated)) == ' ')
 1451         snprintf(tmp, sizeof(tmp), ")");
 1452     else
 1453         snprintf(tmp, sizeof(tmp), ") %c", flag);
 1454     if (sizeof(buf) > strlen(buf) + strlen(tmp))
 1455         strcat(buf, tmp);
 1456 
 1457     if (clear_title) {
 1458         MoveCursor(0, 0);
 1459         CleartoEOLN();
 1460     }
 1461     show_title(buf);
 1462 }
 1463 
 1464 
 1465 /*
 1466  * Search for type SUBJ/AUTH in direction (TRUE = forwards)
 1467  * Return 0 if all is done, or a >0 thread_depth to enter the thread menu
 1468  */
 1469 static int
 1470 do_search(
 1471     t_function func,
 1472     t_bool repeat)
 1473 {
 1474     int start, n;
 1475 
 1476     if (grpmenu.curr < 0)
 1477         return 0;
 1478 
 1479     /*
 1480      * Not intuitive to search current thread in fwd search
 1481      */
 1482     start = ((func == GLOBAL_SEARCH_SUBJECT_FORWARD || func == GLOBAL_SEARCH_AUTHOR_FORWARD)
 1483         && grpmenu.curr < grpmenu.max - 1) ? prev_response((int) base[grpmenu.curr + 1]) : (int) base[grpmenu.curr];
 1484 
 1485     if ((n = search(func, start, repeat)) != -1) {
 1486         grpmenu.curr = which_thread(n);
 1487 
 1488         /*
 1489          * If the search found something deeper in a thread(not the base art)
 1490          * then enter the thread
 1491          */
 1492         if ((n = which_response(n)) != 0)
 1493             return n;
 1494 
 1495         show_group_page();
 1496     }
 1497     return 0;
 1498 }
 1499 
 1500 
 1501 /*
 1502  * We don't directly invoke the pager, but pass through the thread menu
 1503  * to keep navigation sane.
 1504  * 'art' is the arts[art] we wish to read
 1505  * ignore_unavail should be set if we wish to 'keep going' after 'article unavailable'
 1506  * Return a -ve ret_code if we must exit the group menu on return
 1507  */
 1508 static int
 1509 enter_pager(
 1510     int art,
 1511     t_bool ignore_unavail)
 1512 {
 1513     t_pagerinfo page;
 1514 
 1515     page.art = art;
 1516     page.ignore_unavail = ignore_unavail;
 1517 
 1518     return enter_thread(0, &page);
 1519 }
 1520 
 1521 
 1522 /*
 1523  * Handle entry/exit with the thread menu
 1524  * Return -ve ret_code if we must exit the group menu on return
 1525  */
 1526 static int
 1527 enter_thread(
 1528     int depth,
 1529     t_pagerinfo *page)
 1530 {
 1531     int i, n;
 1532 
 1533     if (grpmenu.curr < 0) {
 1534         info_message(_(txt_no_arts));
 1535         return 0;
 1536     }
 1537 
 1538     forever {
 1539         switch (i = thread_page(curr_group, (int) base[grpmenu.curr], depth, page)) {
 1540             case GRP_QUIT:                      /* 'Q'uit */
 1541             case GRP_RETSELECT:                 /* Back to selection screen */
 1542                 return i;
 1543                 /* NOTREACHED */
 1544                 break;
 1545 
 1546             case GRP_NEXT:                      /* 'c'atchup */
 1547                 show_group_page();
 1548                 move_down();
 1549                 return 0;
 1550                 /* NOTREACHED */
 1551                 break;
 1552 
 1553             case GRP_NEXTUNREAD:                /* 'C'atchup */
 1554                 if ((n = next_unread((int) base[grpmenu.curr])) >= 0) {
 1555                     if (page)
 1556                         page->art = n;
 1557                     if ((n = which_thread(n)) >= 0) {
 1558                         grpmenu.curr = n;
 1559                         depth = 0;
 1560                         break;      /* Drop into next thread with unread */
 1561                     }
 1562                 }
 1563                 /* No more unread threads in this group, enter next group */
 1564                 grpmenu.curr = 0;
 1565                 return GRP_NEXTUNREAD;
 1566                 /* NOTREACHED */
 1567                 break;
 1568 
 1569             case GRP_KILLED:
 1570                 grpmenu.curr = 0;
 1571                 /* FALLTHROUGH */
 1572 
 1573             case GRP_EXIT:
 1574             /* case GRP_GOTOTHREAD will never make it up this far */
 1575             default:        /* ie >= 0 Shouldn't happen any more? */
 1576                 clear_note_area();
 1577                 show_group_page();
 1578                 return 0;
 1579                 /* NOTREACHED */
 1580                 break;
 1581         }
 1582     }
 1583     /* NOTREACHED */
 1584     return 0;
 1585 }
 1586 
 1587 
 1588 /*
 1589  * Return a ret_code
 1590  */
 1591 static int
 1592 tab_pressed(
 1593     void)
 1594 {
 1595     int n;
 1596 
 1597     if ((n = ((grpmenu.curr < 0) ? -1 : next_unread((int) base[grpmenu.curr]))) < 0)
 1598         return GRP_NEXTUNREAD;          /* => Enter next unread group */
 1599 
 1600     /* We still have unread arts in the current group ... */
 1601     return enter_pager(n, TRUE);
 1602 }
 1603 
 1604 
 1605 /*
 1606  * There are three ways this is called
 1607  * catchup & return to group menu
 1608  * catchup & go to next group with unread articles
 1609  * group exit via left arrow if auto-catchup is set
 1610  * Return a -ve ret_code if we're done with the group menu
 1611  */
 1612 static int
 1613 group_catchup(
 1614     t_function func)
 1615 {
 1616     char buf[LEN];
 1617     int pyn = 1;
 1618 
 1619     if (num_of_tagged_arts && prompt_yn(_(txt_catchup_despite_tags), TRUE) != 1)
 1620         return 0;
 1621 
 1622     snprintf(buf, sizeof(buf), _(txt_mark_arts_read), (func == CATCHUP_NEXT_UNREAD) ? _(txt_enter_next_unread_group) : "");
 1623 
 1624     if (!curr_group->newsrc.num_unread || (!TINRC_CONFIRM_ACTION) || (pyn = prompt_yn(buf, TRUE)) == 1)
 1625         grp_mark_read(curr_group, arts);
 1626 
 1627     switch (func) {
 1628         case CATCHUP:               /* 'c' */
 1629             if (pyn == 1)
 1630                 return GRP_NEXT;
 1631             break;
 1632 
 1633         case CATCHUP_NEXT_UNREAD:           /* 'C' */
 1634             if (pyn == 1)
 1635                 return GRP_NEXTUNREAD;
 1636             break;
 1637 
 1638         case SPECIAL_CATCHUP_LEFT:              /* <- group catchup on exit */
 1639             switch (pyn) {
 1640                 case -1:                    /* ESCAPE - do nothing */
 1641                     break;
 1642 
 1643                 case 1:                     /* We caught up - advance group */
 1644                     return GRP_NEXT;
 1645                     /* NOTREACHED */
 1646                     break;
 1647 
 1648                 default:                    /* Just leave the group */
 1649                     return GRP_EXIT;
 1650                     /* NOTREACHED */
 1651                     break;
 1652             }
 1653             /* FALLTHROUGH */
 1654         default:                            /* Should not be here */
 1655             break;
 1656     }
 1657     return 0;                               /* Stay in this menu by default */
 1658 }
 1659 
 1660 
 1661 static t_bool
 1662 prompt_getart_limit(
 1663     void)
 1664 {
 1665     char *p;
 1666     t_bool ret = FALSE;
 1667 
 1668     clear_message();
 1669     if ((p = tin_getline(_(txt_enter_getart_limit), 2, 0, 0, FALSE, HIST_OTHER)) != NULL) {
 1670         tinrc.getart_limit = atoi(p);
 1671         ret = TRUE;
 1672     }
 1673     clear_message();
 1674     return ret;
 1675 }
 1676 
 1677 
 1678 /*
 1679  * Redraw all necessary parts of the screen after FEED_MARK_(UN)READ
 1680  * Move cursor to next unread item if needed
 1681  *
 1682  * Returns TRUE when no next unread art, FALSE otherwise
 1683  */
 1684 t_bool
 1685 group_mark_postprocess(
 1686     int function,
 1687     t_function feed_type,
 1688     int respnum)
 1689 {
 1690     int n;
 1691 
 1692     show_group_title(TRUE);
 1693     switch (function) {
 1694         case (FEED_MARK_READ):
 1695             if (feed_type == FEED_THREAD || feed_type == FEED_ARTICLE)
 1696                 build_sline(grpmenu.curr);
 1697             else
 1698                 show_group_page();
 1699 
 1700             if ((n = next_unread(next_response(respnum))) == -1) {
 1701                 draw_subject_arrow();
 1702                 return TRUE;
 1703             }
 1704 
 1705             move_to_item(which_thread(n));
 1706             break;
 1707 
 1708         case (FEED_MARK_UNREAD):
 1709             if (feed_type == FEED_THREAD || feed_type == FEED_ARTICLE)
 1710                 build_sline(grpmenu.curr);
 1711             else
 1712                 show_group_page();
 1713 
 1714             draw_subject_arrow();
 1715             break;
 1716 
 1717         default:
 1718             break;
 1719     }
 1720     return FALSE;
 1721 }