"Fossies" - the Fresh Open Source Software Archive

Member "tin-2.6.2/src/group.c" (9 Dec 2022, 50187 Bytes) of package /linux/misc/tin-2.6.2.tar.xz:


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

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