"Fossies" - the Fresh Open Source Software Archive

Member "tin-2.6.1/src/group.c" (22 Dec 2021, 48640 Bytes) of package /linux/misc/tin-2.6.1.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.0_vs_2.6.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   : 2021-07-25
    7  *  Notes     :
    8  *
    9  * Copyright (c) 1991-2022 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_TOGGLE_HELP_DISPLAY:        /* toggle mini help menu */
  463                 toggle_mini_help(GROUP_LEVEL);
  464                 show_group_page();
  465                 break;
  466 
  467             case GLOBAL_TOGGLE_INVERSE_VIDEO:       /* toggle inverse video */
  468                 toggle_inverse_video();
  469                 show_group_page();
  470                 show_inverse_video_status();
  471                 break;
  472 
  473 #ifdef HAVE_COLOR
  474             case GLOBAL_TOGGLE_COLOR:
  475                 if (toggle_color()) {
  476                     show_group_page();
  477                     show_color_status();
  478                 }
  479                 break;
  480 #endif /* HAVE_COLOR */
  481 
  482             case GROUP_MARK_THREAD_READ:            /* mark current thread/range/tagged threads as read */
  483             case MARK_THREAD_UNREAD:                /* or unread */
  484                 if (grpmenu.curr < 0)
  485                     info_message(_(txt_no_arts));
  486                 else {
  487                     t_function function, type;
  488 
  489                     function = func == GROUP_MARK_THREAD_READ ? (t_function) FEED_MARK_READ : (t_function) FEED_MARK_UNREAD;
  490                     type = range_active ? FEED_RANGE : (num_of_tagged_arts && !group->attribute->mark_ignore_tags) ? NOT_ASSIGNED : FEED_THREAD;
  491                     feed_articles(function, GROUP_LEVEL, type, group, (int) base[grpmenu.curr]);
  492                 }
  493                 break;
  494 
  495             case GROUP_LIST_THREAD:             /* list articles within current thread */
  496                 ret_code = enter_thread(0, NULL);   /* Enter thread at the top */
  497                 break;
  498 
  499             case GLOBAL_LOOKUP_MESSAGEID:
  500                 if ((i = prompt_msgid()) != ART_UNAVAILABLE)
  501                     ret_code = enter_pager(i, FALSE);
  502                 break;
  503 
  504             case GLOBAL_OPTION_MENU:            /* option menu */
  505                 old_artnum = grpmenu.max > 0 ? arts[(int) base[grpmenu.curr]].artnum : T_ARTNUM_CONST(-1);
  506                 config_page(group->name, signal_context);
  507                 grpmenu.curr = old_artnum >= T_ARTNUM_CONST(0) ? find_new_pos(old_artnum, grpmenu.curr) : grpmenu.max - 1;
  508                 show_group_page();
  509                 break;
  510 
  511             case GROUP_NEXT_GROUP:          /* goto next group */
  512                 clear_message();
  513                 if (selmenu.curr + 1 >= selmenu.max)
  514                     info_message(_(txt_no_more_groups));
  515                 else {
  516                     if (xflag && TINRC_CONFIRM_SELECT && (prompt_yn(_(txt_confirm_select_on_exit), FALSE) != 1)) {
  517                         undo_auto_select_arts();
  518                         xflag = FALSE;
  519                     }
  520                     selmenu.curr++;
  521                     ret_code = GRP_NEXTUNREAD;
  522                 }
  523                 break;
  524 
  525             case GROUP_NEXT_UNREAD_ARTICLE: /* goto next unread article */
  526                 if (grpmenu.curr < 0) {
  527                     info_message(_(txt_no_next_unread_art));
  528                     break;
  529                 }
  530                 if ((n = next_unread((int) base[grpmenu.curr])) == -1)
  531                     info_message(_(txt_no_next_unread_art));
  532                 else
  533                     ret_code = enter_pager(n, FALSE);
  534                 break;
  535 
  536             case GROUP_PREVIOUS_UNREAD_ARTICLE: /* go to previous unread article */
  537                 if (grpmenu.curr < 0) {
  538                     info_message(_(txt_no_prev_unread_art));
  539                     break;
  540                 }
  541 
  542                 if ((n = prev_unread(prev_response((int) base[grpmenu.curr]))) == -1)
  543                     info_message(_(txt_no_prev_unread_art));
  544                 else
  545                     ret_code = enter_pager(n, FALSE);
  546                 break;
  547 
  548             case GROUP_PREVIOUS_GROUP:  /* previous group */
  549                 clear_message();
  550                 for (i = selmenu.curr - 1; i >= 0; i--) {
  551                     if (UNREAD_GROUP(i))
  552                         break;
  553                 }
  554                 if (i < 0)
  555                     info_message(_(txt_no_prev_group));
  556                 else {
  557                     if (xflag && TINRC_CONFIRM_SELECT && (prompt_yn(_(txt_confirm_select_on_exit), FALSE) != 1)) {
  558                         undo_auto_select_arts();
  559                         xflag = FALSE;
  560                     }
  561                     selmenu.curr = i;
  562                     ret_code = GRP_NEXTUNREAD;
  563                 }
  564                 break;
  565 
  566             case GLOBAL_QUIT:   /* return to group selection page */
  567                 if (num_of_tagged_arts && prompt_yn(_(txt_quit_despite_tags), TRUE) != 1)
  568                     break;
  569                 if (xflag && TINRC_CONFIRM_SELECT && (prompt_yn(_(txt_confirm_select_on_exit), FALSE) != 1)) {
  570                     undo_auto_select_arts();
  571                     xflag = FALSE;
  572                 }
  573                 ret_code = GRP_EXIT;
  574                 break;
  575 
  576             case GLOBAL_QUIT_TIN:       /* quit */
  577                 if (num_of_tagged_arts && prompt_yn(_(txt_quit_despite_tags), TRUE) != 1)
  578                     break;
  579                 if (xflag && TINRC_CONFIRM_SELECT && (prompt_yn(_(txt_confirm_select_on_exit), FALSE) != 1)) {
  580                     undo_auto_select_arts();
  581                     xflag = FALSE;
  582                 }
  583                 ret_code = GRP_QUIT;
  584                 break;
  585 
  586             case GROUP_TOGGLE_READ_UNREAD:
  587                 toggle_read_unread(FALSE);
  588                 show_group_page();
  589                 break;
  590 
  591             case GROUP_TOGGLE_GET_ARTICLES_LIMIT:
  592                 if (prompt_getart_limit()) {
  593                     /*
  594                      * if getart limit was given via cmd-line
  595                      * make it inactive now in order to use
  596                      * tinrc.getart_limit
  597                      */
  598                     if (cmdline.args & CMDLINE_GETART_LIMIT)
  599                         cmdline.args &= ~CMDLINE_GETART_LIMIT;
  600                     ret_code = GRP_NEXTUNREAD;
  601                 }
  602                 break;
  603 
  604             case GLOBAL_BUGREPORT:
  605                 bug_report();
  606                 break;
  607 
  608             case GROUP_TAG_PARTS: /* tag all in order */
  609                 if (0 <= grpmenu.curr) {
  610                     int old_num = num_of_tagged_arts;
  611 
  612                     if (tag_multipart((int) base[grpmenu.curr]) != 0) {
  613                         /*
  614                          * on success, move the pointer to the next
  615                          * untagged article just for ease of use's sake
  616                          */
  617                         n = grpmenu.curr;
  618                         update_group_page();
  619                         do {
  620                             n++;
  621                             n %= grpmenu.max;
  622                             if (arts[base[n]].tagged == 0) {
  623                                 move_to_item(n);
  624                                 break;
  625                             }
  626                         } while (n != grpmenu.curr);
  627                         if (old_num < num_of_tagged_arts)
  628                             info_message(_(txt_info_all_parts_tagged));
  629                         else
  630                             info_message(_(txt_info_all_parts_untagged));
  631                     }
  632                 }
  633                 break;
  634 
  635             case GROUP_TAG:     /* tag/untag threads for mailing/piping/printing/saving */
  636                 if (grpmenu.curr >= 0) {
  637                     t_bool tagged = TRUE;
  638 
  639                     n = (int) base[grpmenu.curr];
  640 
  641                     /*
  642                      * This loop looks for any article in the thread that
  643                      * isn't already tagged.
  644                      */
  645                     for (ii = n; ii != -1; ii = arts[ii].thread) {
  646                         if (arts[ii].tagged == 0) {
  647                             tagged = FALSE;
  648                             break;
  649                         }
  650                     }
  651 
  652                     /*
  653                      * If the whole thread is tagged, untag it. Otherwise, tag
  654                      * any untagged articles
  655                      */
  656                     if (tagged) {
  657                         /*
  658                          * Here we repeat the tagged test in both blocks
  659                          * to leave the choice of tagged/untagged
  660                          * determination politic in the previous lines.
  661                          */
  662                         for (ii = n; ii != -1; ii = arts[ii].thread) {
  663                             if (arts[ii].tagged != 0) {
  664                                 tagged = TRUE;
  665                                 untag_article(ii);
  666                             }
  667                         }
  668                     } else {
  669                         for (ii = n; ii != -1; ii = arts[ii].thread) {
  670                             if (arts[ii].tagged == 0)
  671                                 arts[ii].tagged = ++num_of_tagged_arts;
  672                         }
  673                     }
  674                     if ((ii = line_is_tagged(n))) {
  675 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
  676                         if ((wtmp = char2wchar_t(tin_ltoa(ii, 3)))) {
  677                             mark_screen(grpmenu.curr, mark_offset - (3 - art_mark_width), wtmp);
  678                             free(wtmp);
  679                         }
  680 #else
  681                         mark_screen(grpmenu.curr, mark_offset - 2, tin_ltoa(ii, 3));
  682 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
  683                     } else {
  684 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
  685                         wchar_t mark[] = { L'\0', L'\0' };
  686 #else
  687                         char mark[] = { '\0', '\0' };
  688 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
  689 
  690                         stat_thread(grpmenu.curr, &sbuf);
  691                         mark[0] = sbuf.art_mark;
  692 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
  693                         mark_screen(grpmenu.curr, mark_offset - (3 - art_mark_width), L"   "); /* clear space used by tag numbering */
  694                         mark_screen(grpmenu.curr, mark_offset + (art_mark_width - wcwidth(mark[0])), mark);
  695 #else
  696                         mark_screen(grpmenu.curr, mark_offset - 2, "  "); /* clear space used by tag numbering */
  697                         mark_screen(grpmenu.curr, mark_offset, mark);
  698 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
  699                     }
  700                     if (tagged)
  701                         show_tagged_lines();
  702 
  703                     if (grpmenu.curr + 1 < grpmenu.max)
  704                         move_down();
  705                     else
  706                         draw_subject_arrow();
  707 
  708                     info_message(tagged ? _(txt_prefix_untagged) : _(txt_prefix_tagged), txt_thread_singular);
  709 
  710                 }
  711                 break;
  712 
  713             case GROUP_TOGGLE_THREADING:        /* Cycle through the threading types */
  714                 group->attribute->thread_articles = CAST_BITS((group->attribute->thread_articles + 1) % (THREAD_MAX + 1), thread_articles);
  715                 if (grpmenu.curr >= 0) {
  716                     i = (int) base[grpmenu.curr];                               /* Save a copy of current thread */
  717                     make_threads(group, TRUE);
  718                     find_base(group);
  719                     if ((grpmenu.curr = which_thread(i)) < 0)           /* Restore current position in group */
  720                         grpmenu.curr = 0;
  721                 }
  722                 show_group_page();
  723                 break;
  724 
  725             case GROUP_UNTAG:   /* untag all articles */
  726                 if (grpmenu.curr >= 0) {
  727                     if (untag_all_articles())
  728                         update_group_page();
  729                 }
  730                 break;
  731 
  732             case GLOBAL_VERSION:
  733                 info_message(cvers);
  734                 break;
  735 
  736             case GLOBAL_POST:   /* post an article */
  737                 if (post_article(group->name))
  738                     show_group_page();
  739                 break;
  740 
  741             case GLOBAL_POSTPONED:  /* post postponed article */
  742                 if (can_post) {
  743                     if (pickup_postponed_articles(FALSE, FALSE))
  744                         show_group_page();
  745                 } else
  746                     info_message(_(txt_cannot_post));
  747                 break;
  748 
  749             case GLOBAL_DISPLAY_POST_HISTORY:   /* display messages posted by user */
  750                 if (post_hist_page()) {
  751                     if (grpmenu.curr == -1 && grpmenu.max > 0)
  752                         grpmenu.curr = 0;
  753                     show_group_page();
  754                 }
  755                 break;
  756 
  757             case MARK_ARTICLE_UNREAD:       /* mark base article of thread unread */
  758                 if (grpmenu.curr < 0)
  759                     info_message(_(txt_no_arts));
  760                 else {
  761                     const char *ptr;
  762 
  763                     if (range_active) {
  764                         /*
  765                          * We are tied to following base[] here, not arts[], as we operate on
  766                          * the base articles by definition.
  767                          */
  768                         for (ii = 0; ii < grpmenu.max; ++ii) {
  769                             if (arts[base[ii]].inrange) {
  770                                 arts[base[ii]].inrange = FALSE;
  771                                 art_mark(group, &arts[base[ii]], ART_WILL_RETURN);
  772                                 for_each_art_in_thread(i, ii)
  773                                     arts[i].inrange = FALSE;
  774                             }
  775                         }
  776                         range_active = FALSE;
  777                         show_group_page();
  778                         ptr = _(txt_base_article_range);
  779                     } else {
  780                         art_mark(group, &arts[base[grpmenu.curr]], ART_WILL_RETURN);
  781                         ptr = _(txt_base_article);
  782                     }
  783 
  784                     show_group_title(TRUE);
  785                     build_sline(grpmenu.curr);
  786                     draw_subject_arrow();
  787                     info_message(_(txt_marked_as_unread), ptr);
  788                 }
  789                 break;
  790 
  791             case MARK_FEED_READ:    /* mark selected articles as read */
  792                 if (grpmenu.curr >= 0)
  793                     feed_articles(FEED_MARK_READ, GROUP_LEVEL, NOT_ASSIGNED, group, (int) base[grpmenu.curr]);
  794                 break;
  795 
  796             case MARK_FEED_UNREAD:  /* mark selected articles as unread */
  797                 if (grpmenu.curr >= 0)
  798                     feed_articles(FEED_MARK_UNREAD, GROUP_LEVEL, NOT_ASSIGNED, group, (int) base[grpmenu.curr]);
  799                 break;
  800 
  801             case GROUP_SELECT_THREAD:   /* mark thread as selected */
  802             case GROUP_TOGGLE_SELECT_THREAD:    /* toggle thread */
  803                 if (grpmenu.curr < 0) {
  804                     info_message(_(txt_no_arts));
  805                     break;
  806                 }
  807 
  808                 flag = TRUE;
  809                 if (func == GROUP_TOGGLE_SELECT_THREAD) {
  810                     stat_thread(grpmenu.curr, &sbuf);
  811                     if (sbuf.selected_unread == sbuf.unread)
  812                         flag = FALSE;
  813                 }
  814                 n = 0;
  815                 for_each_art_in_thread(i, grpmenu.curr) {
  816                     arts[i].selected = CAST_BOOL(flag);
  817                     ++n;
  818                 }
  819                 assert(n > 0);
  820                 {
  821 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
  822                     wchar_t mark[] = { L'\0', L'\0' };
  823 #else
  824                     char mark[] = { '\0', '\0' };
  825 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
  826 
  827                     stat_thread(grpmenu.curr, &sbuf);
  828                     mark[0] = sbuf.art_mark;
  829 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
  830                     mark_screen(grpmenu.curr, mark_offset + (art_mark_width - wcwidth(mark[0])), mark);
  831 #else
  832                     mark_screen(grpmenu.curr, mark_offset, mark);
  833 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
  834                 }
  835 
  836                 show_group_title(TRUE);
  837 
  838                 if (grpmenu.curr + 1 < grpmenu.max)
  839                     move_down();
  840                 else
  841                     draw_subject_arrow();
  842 
  843                 info_message(flag ? _(txt_thread_marked_as_selected) : _(txt_thread_marked_as_deselected));
  844                 break;
  845 
  846             case GROUP_REVERSE_SELECTIONS:  /* reverse selections */
  847                 for_each_art(i)
  848                     arts[i].selected = bool_not(arts[i].selected);
  849                 update_group_page();
  850                 show_group_title(TRUE);
  851                 break;
  852 
  853             case GROUP_UNDO_SELECTIONS: /* undo selections */
  854                 undo_selections();
  855                 xflag = FALSE;
  856                 show_group_title(TRUE);
  857                 update_group_page();
  858                 break;
  859 
  860             case GROUP_SELECT_PATTERN:  /* select matching patterns */
  861                 if (grpmenu.curr >= 0) {
  862                     char pat[128];
  863                     char *prompt;
  864                     struct regex_cache cache = { NULL, NULL };
  865 
  866                     prompt = fmt_string(_(txt_select_pattern), tinrc.default_select_pattern);
  867                     if (!(prompt_string_default(prompt, tinrc.default_select_pattern, _(txt_info_no_previous_expression), HIST_SELECT_PATTERN))) {
  868                         free(prompt);
  869                         break;
  870                     }
  871                     free(prompt);
  872 
  873                     if (STRCMPEQ(tinrc.default_select_pattern, "*")) {  /* all */
  874                         if (tinrc.wildcard)
  875                             STRCPY(pat, ".*");
  876                         else
  877                             STRCPY(pat, tinrc.default_select_pattern);
  878                     } else
  879                         snprintf(pat, sizeof(pat), REGEX_FMT, tinrc.default_select_pattern);
  880 
  881                     if (tinrc.wildcard && !(compile_regex(pat, &cache, PCRE_CASELESS)))
  882                         break;
  883 
  884                     flag = FALSE;
  885                     for (n = 0; n < grpmenu.max; n++) {
  886                         if (!match_regex(arts[base[n]].subject, pat, &cache, TRUE))
  887                             continue;
  888 
  889                         for_each_art_in_thread(i, n)
  890                             arts[i].selected = TRUE;
  891 
  892                         flag = TRUE;
  893                     }
  894                     if (flag) {
  895                         show_group_title(TRUE);
  896                         update_group_page();
  897                     }
  898                     if (tinrc.wildcard) {
  899                         FreeIfNeeded(cache.re);
  900                         FreeIfNeeded(cache.extra);
  901                     }
  902                 }
  903                 break;
  904 
  905             case GROUP_SELECT_THREAD_IF_UNREAD_SELECTED:    /* select all unread arts in thread hot if 1 is hot */
  906                 for (n = 0; n < grpmenu.max; n++) {
  907                     stat_thread(n, &sbuf);
  908                     if (!sbuf.selected_unread || sbuf.selected_unread == sbuf.unread)
  909                         continue;
  910 
  911                     for_each_art_in_thread(i, n)
  912                         arts[i].selected = TRUE;
  913                 }
  914                 show_group_title(TRUE);
  915                 break;
  916 
  917             case GROUP_MARK_UNSELECTED_ARTICLES_READ:   /* mark read all unselected arts */
  918                 if (!xflag) {
  919                     do_auto_select_arts();
  920                     xflag = TRUE;
  921                 } else {
  922                     undo_auto_select_arts();
  923                     xflag = FALSE;
  924                 }
  925                 break;
  926 
  927             case GROUP_DO_AUTOSELECT:       /* perform auto-selection on group */
  928                 for (n = 0; n < grpmenu.max; n++) {
  929                     for_each_art_in_thread(i, n)
  930                         arts[i].selected = TRUE;
  931                 }
  932                 update_group_page();
  933                 show_group_title(TRUE);
  934                 break;
  935 
  936             case GLOBAL_TOGGLE_INFO_LAST_LINE:
  937                 tinrc.info_in_last_line = bool_not(tinrc.info_in_last_line);
  938                 show_group_page();
  939                 break;
  940 
  941             default:
  942                 info_message(_(txt_bad_command), PrintFuncKey(key, GLOBAL_HELP, group_keys));
  943                 break;
  944         } /* switch(ch) */
  945     } /* ret_code >= 0 */
  946 
  947     set_xclick_off();
  948 
  949     clear_note_area();
  950     grp_del_mail_arts(group);
  951 
  952     art_close(&pgart);              /* Close any open art */
  953 
  954     curr_group = NULL;
  955 
  956     return ret_code;
  957 }
  958 
  959 
  960 void
  961 show_group_page(
  962     void)
  963 {
  964     int i;
  965 
  966     signal_context = cGroup;
  967     currmenu = &grpmenu;
  968 
  969     ClearScreen();
  970     set_first_screen_item();
  971     parse_format_string(curr_group->attribute->group_format, &grp_fmt);
  972     mark_offset = 0;
  973     show_group_title(FALSE);
  974 
  975     for (i = grpmenu.first; i < grpmenu.first + NOTESLINES && i < grpmenu.max; ++i)
  976         build_sline(i);
  977 
  978     show_mini_help(GROUP_LEVEL);
  979 
  980     if (grpmenu.max <= 0) {
  981         info_message(_(txt_no_arts));
  982         return;
  983     }
  984 
  985     draw_subject_arrow();
  986 }
  987 
  988 
  989 static void
  990 update_group_page(
  991     void)
  992 {
  993     int i, j;
  994 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
  995     wchar_t mark[] = { L'\0', L'\0' };
  996     wchar_t *wtmp;
  997 #else
  998     char mark[] = { '\0', '\0' };
  999 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
 1000     struct t_art_stat sbuf;
 1001 
 1002     for (i = grpmenu.first; i < grpmenu.first + NOTESLINES && i < grpmenu.max; ++i) {
 1003         if ((j = line_is_tagged((int) base[i]))) {
 1004 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
 1005             if ((wtmp = char2wchar_t(tin_ltoa(j, 3)))) {
 1006                 mark_screen(i, mark_offset - (3 - art_mark_width), wtmp);
 1007                 free(wtmp);
 1008             }
 1009 #else
 1010             mark_screen(i, mark_offset - 2, tin_ltoa(j, 3));
 1011 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
 1012         } else {
 1013             stat_thread(i, &sbuf);
 1014             mark[0] = sbuf.art_mark;
 1015 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
 1016             mark_screen(i, mark_offset - (3 - art_mark_width), L"   "); /* clear space used by tag numbering */
 1017             mark_screen(i, mark_offset + (art_mark_width - wcwidth(mark[0])), mark);
 1018 #else
 1019             mark_screen(i, mark_offset - 2, "  ");  /* clear space used by tag numbering */
 1020             mark_screen(i, mark_offset, mark);
 1021 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
 1022             if (sbuf.art_mark == tinrc.art_marked_selected)
 1023                 draw_mark_selected(i);
 1024         }
 1025     }
 1026 
 1027     if (grpmenu.max <= 0)
 1028         return;
 1029 
 1030     draw_subject_arrow();
 1031 }
 1032 
 1033 
 1034 static void
 1035 draw_subject_arrow(
 1036     void)
 1037 {
 1038     draw_arrow_mark(INDEX_TOP + grpmenu.curr - grpmenu.first);
 1039 
 1040     if (tinrc.info_in_last_line) {
 1041         struct t_art_stat statbuf;
 1042 
 1043         stat_thread(grpmenu.curr, &statbuf);
 1044         info_message("%s", arts[(statbuf.unread ? next_unread((int) base[grpmenu.curr]) : base[grpmenu.curr])].subject);
 1045     } else if (grpmenu.curr == grpmenu.max - 1)
 1046         info_message(_(txt_end_of_arts));
 1047 }
 1048 
 1049 
 1050 void
 1051 clear_note_area(
 1052     void)
 1053 {
 1054     MoveCursor(INDEX_TOP, 0);
 1055     CleartoEOS();
 1056 }
 1057 
 1058 
 1059 /*
 1060  * If in show_only_unread_arts mode or there are unread articles we know this
 1061  * thread will exist after toggle. Otherwise we find the next closest to
 1062  * return to. 'force' can be set to force tin to show all messages
 1063  */
 1064 static void
 1065 toggle_read_unread(
 1066     t_bool force)
 1067 {
 1068     int n, i = -1;
 1069 
 1070     /*
 1071      * Clear art->keep_in_base if switching to !show_only_unread_arts
 1072      */
 1073     if (curr_group->attribute->show_only_unread_arts) {
 1074         for_each_art(n)
 1075             arts[n].keep_in_base = FALSE;
 1076     }
 1077 
 1078     /* force currently is always false */
 1079     if (force)
 1080         curr_group->attribute->show_only_unread_arts = TRUE;    /* Yes - really, we change it in a bit */
 1081 
 1082     wait_message(0, _(txt_reading_arts),
 1083         (curr_group->attribute->show_only_unread_arts) ? _(txt_all) : _(txt_unread));
 1084 
 1085     if (grpmenu.curr >= 0) {
 1086         if (curr_group->attribute->show_only_unread_arts || new_responses(grpmenu.curr))
 1087             i = (int) base[grpmenu.curr];
 1088         else if ((n = prev_unread((int) base[grpmenu.curr])) >= 0)
 1089             i = n;
 1090         else if ((n = next_unread((int) base[grpmenu.curr])) >= 0)
 1091             i = n;
 1092     }
 1093 
 1094     if (!force)
 1095         curr_group->attribute->show_only_unread_arts = bool_not(curr_group->attribute->show_only_unread_arts);
 1096 
 1097     find_base(curr_group);
 1098     if (i >= 0 && (n = which_thread(i)) >= 0)
 1099         grpmenu.curr = n;
 1100     else if (grpmenu.max > 0)
 1101         grpmenu.curr = grpmenu.max - 1;
 1102     clear_message();
 1103 }
 1104 
 1105 
 1106 /*
 1107  * Find new index position after a kill or unkill. Because kill can work on
 1108  * author it is impossible to know which, if any, articles will be left
 1109  * afterwards. So we make a "best attempt" to find a new index point.
 1110  */
 1111 static int
 1112 find_new_pos(
 1113     long old_artnum,
 1114     int cur_pos)
 1115 {
 1116     int i, pos;
 1117 
 1118     if ((i = find_artnum(old_artnum)) >= 0 && (pos = which_thread(i)) >= 0)
 1119         return pos;
 1120 
 1121     return ((cur_pos < grpmenu.max) ? cur_pos : (grpmenu.max - 1));
 1122 }
 1123 
 1124 
 1125 /*
 1126  * Set grpmenu.curr to the first unread or the last thread depending on
 1127  * the value of pos_first_unread
 1128  */
 1129 void
 1130 pos_first_unread_thread(
 1131     void)
 1132 {
 1133     int i;
 1134 
 1135     if (curr_group->attribute->pos_first_unread) {
 1136         for (i = 0; i < grpmenu.max; i++) {
 1137             if (new_responses(i))
 1138                 break;
 1139         }
 1140         grpmenu.curr = ((i < grpmenu.max) ? i : (grpmenu.max - 1));
 1141     } else
 1142         grpmenu.curr = grpmenu.max - 1;
 1143 }
 1144 
 1145 
 1146 void
 1147 mark_screen(
 1148     int screen_row,
 1149     int screen_col,
 1150 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
 1151     const wchar_t *value)
 1152 #else
 1153     const char *value)
 1154 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
 1155 {
 1156 
 1157     if (tinrc.draw_arrow) {
 1158         MoveCursor(INDEX2LNUM(screen_row), screen_col);
 1159 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
 1160         my_fputws(value, stdout);
 1161 #else
 1162         my_fputs(value, stdout);
 1163 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
 1164         stow_cursor();
 1165         my_flush();
 1166     } else {
 1167 #ifdef USE_CURSES
 1168 #   if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
 1169         char *tmp;
 1170 #   endif /* MULTIBYTE_ABLE && !NO_LOCALE */
 1171         int y, x;
 1172 
 1173         getyx(stdscr, y, x);
 1174 #   if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
 1175         if ((tmp = wchar_t2char(value))) {
 1176             mvaddstr(INDEX2LNUM(screen_row), screen_col, tmp);
 1177             free(tmp);
 1178         }
 1179 #   else
 1180         mvaddstr(INDEX2LNUM(screen_row), screen_col, value);
 1181 #   endif /* MULTIBYTE_ABLE && !NO_LOCALE */
 1182         MoveCursor(y, x);
 1183 #else
 1184         int i;
 1185 
 1186         for (i = 0; value[i] != '\0'; i++)
 1187             screen[INDEX2SNUM(screen_row)].col[screen_col + i] = value[i];
 1188 
 1189         MoveCursor(INDEX2LNUM(screen_row), screen_col);
 1190 #   if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
 1191         my_fputws(value, stdout);
 1192 #   else
 1193         my_fputs(value, stdout);
 1194 #   endif /* MULTIBYTE_ABLE && !NO_LOCALE */
 1195 #endif /* USE_CURSES */
 1196         currmenu->draw_arrow();
 1197     }
 1198 }
 1199 
 1200 
 1201 /*
 1202  *  Builds the correct header for multipart messages when sorting via
 1203  *  THREAD_MULTI.
 1204  */
 1205 static void
 1206 build_multipart_header(
 1207     char *dest,
 1208     int maxlen,
 1209     const char *src,
 1210     int cmplen,
 1211     int have,
 1212     int total)
 1213 {
 1214     const char *mark = (have == total) ? "*" : "-";
 1215     char *ss;
 1216 
 1217     if (cmplen > maxlen)
 1218         strncpy(dest, src, (size_t) maxlen);
 1219     else {
 1220         strncpy(dest, src, (size_t) cmplen);
 1221         ss = dest + cmplen;
 1222         snprintf(ss, (size_t) (maxlen - cmplen), "(%s/%d)", mark, total);
 1223     }
 1224 }
 1225 
 1226 
 1227 /*
 1228  * Build subject line given an index into base[].
 1229  *
 1230  * WARNING: some other code expects to find the article mark (ART_MARK_READ,
 1231  * ART_MARK_SELECTED, etc) at mark_offset from beginning of the line.
 1232  * So, if you change the format used in this routine, be sure to check that
 1233  * the value of mark_offset is still correct.
 1234  * Yes, this is somewhat kludgy.
 1235  */
 1236 static void
 1237 build_sline(
 1238     int i)
 1239 {
 1240     char *fmt, *buf;
 1241     char *buffer;
 1242     char arts_sub[HEADER_LEN];
 1243     char tmp_buf[8];
 1244     char tmp[LEN];
 1245     int respnum;
 1246     int j, k, n;
 1247     size_t len;
 1248     struct t_art_stat sbuf;
 1249     t_bool tagged = FALSE;
 1250 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
 1251     wchar_t *wtmp, *wtmp2;
 1252 #else
 1253     int fill, gap;
 1254     size_t len_start;
 1255 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
 1256 
 1257 #ifdef USE_CURSES
 1258     /*
 1259      * Allocate line buffer
 1260      * make it the same size like in !USE_CURSES case to simplify the code
 1261      */
 1262 #   if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
 1263         buffer = my_malloc(cCOLS * MB_CUR_MAX + 2);
 1264 #   else
 1265         buffer = my_malloc(cCOLS + 2);
 1266 #   endif /* MULTIBYTE_ABLE && !NO_LOCALE */
 1267 #else
 1268     buffer = screen[INDEX2SNUM(i)].col;
 1269 #endif /* USE_CURSES */
 1270 
 1271     buffer[0] = '\0';
 1272 
 1273     respnum = (int) base[i];
 1274 
 1275     stat_thread(i, &sbuf);
 1276 
 1277     /*
 1278      * Find index of first unread in this thread
 1279      */
 1280     j = (sbuf.unread) ? next_unread(respnum) : respnum;
 1281 
 1282     fmt = grp_fmt.str;
 1283 
 1284     if (tinrc.draw_arrow)
 1285         strcat(buffer, "  ");
 1286 
 1287     for (; *fmt; fmt++) {
 1288         if (*fmt != '%') {
 1289             strncat(buffer, fmt, 1);
 1290             continue;
 1291         }
 1292         switch (*++fmt) {
 1293             case '\0':
 1294                 break;
 1295 
 1296             case '%':
 1297                 strncat(buffer, fmt, 1);
 1298                 break;
 1299 
 1300             case 'D':   /* date */
 1301                 buf = my_malloc(LEN);
 1302                 if (my_strftime(buf, LEN - 1, grp_fmt.date_str, localtime((const time_t *) &arts[j].date))) {
 1303 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
 1304                     if ((wtmp = char2wchar_t(buf)) != NULL) {
 1305                         wtmp2 = wcspart(wtmp, (int) grp_fmt.len_date_max, TRUE);
 1306                         if (wcstombs(tmp, wtmp2, sizeof(tmp) - 1) != (size_t) -1)
 1307                             strcat(buffer, tmp);
 1308 
 1309                         free(wtmp);
 1310                         free(wtmp2);
 1311                     }
 1312 #else
 1313                     strncat(buffer, buf, grp_fmt.len_date_max);
 1314 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
 1315                 }
 1316                 free(buf);
 1317                 break;
 1318 
 1319             case 'F':   /* from */
 1320                 if (curr_group->attribute->show_author != SHOW_FROM_NONE) {
 1321 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
 1322                     get_author(FALSE, &arts[j], tmp, sizeof(tmp) - 1);
 1323 
 1324                     if ((wtmp = char2wchar_t(tmp)) != NULL) {
 1325                         wtmp2 = wcspart(wtmp, (int) grp_fmt.len_from, TRUE);
 1326                         if (wcstombs(tmp, wtmp2, sizeof(tmp) - 1) != (size_t) -1)
 1327                             strcat(buffer, tmp);
 1328 
 1329                         free(wtmp);
 1330                         free(wtmp2);
 1331                     }
 1332 #else
 1333                     len_start = strwidth(buffer);
 1334                     get_author(FALSE, &arts[j], buffer + strlen(buffer), grp_fmt.len_from);
 1335                     fill = grp_fmt.len_from - (strwidth(buffer) - len_start);
 1336                     gap = strlen(buffer);
 1337                     for (k = 0; k < fill; k++)
 1338                         buffer[gap + k] = ' ';
 1339                     buffer[gap + fill] = '\0';
 1340 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
 1341                 }
 1342                 break;
 1343 
 1344             case 'I':   /* initials */
 1345                 len = MIN(grp_fmt.len_initials, sizeof(tmp) - 1);
 1346                 get_initials(&arts[j], tmp, (int) len);
 1347                 strcat(buffer, tmp);
 1348                 if ((k = (int) (len - (size_t) strwidth(tmp))) > 0) {
 1349                     buf = buffer + strlen(buffer);
 1350                     for (; k > 0; --k)
 1351                         *buf++ = ' ';
 1352                     *buf = '\0';
 1353                 }
 1354                 break;
 1355 
 1356             case 'L':   /* lines */
 1357                 if (arts[j].line_count != -1)
 1358                     strcat(buffer, tin_ltoa(arts[j].line_count, (int) grp_fmt.len_linecnt));
 1359                 else {
 1360                     buf = buffer + strlen(buffer);
 1361                     for (k = (int) grp_fmt.len_linecnt; k > 1; --k)
 1362                         *buf++ = ' ';
 1363                     *buf++ = '?';
 1364                     *buf = '\0';
 1365                 }
 1366                 break;
 1367 
 1368             case 'm':   /* article flags, tag number, or whatever */
 1369                 if (!grp_fmt.mark_offset)
 1370                     grp_fmt.mark_offset = (size_t) (mark_offset = strwidth(buffer) + 2);
 1371                 if ((k = line_is_tagged(respnum))) {
 1372                     STRCPY(tmp_buf, tin_ltoa(k, 3));
 1373                     strcat(buffer, " ");
 1374                     tagged = TRUE;
 1375                 } else
 1376 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
 1377                     snprintf(tmp_buf, sizeof(tmp_buf), "%s%lc", art_mark_width > wcwidth(sbuf.art_mark) ? "   " : "  ", sbuf.art_mark);
 1378 #else
 1379                     snprintf(tmp_buf, sizeof(tmp_buf), "  %c", sbuf.art_mark);
 1380 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
 1381                 strcat(buffer, tmp_buf);
 1382                 break;
 1383 
 1384             case 'M':   /* message-id */
 1385                 len = MIN(grp_fmt.len_msgid, sizeof(tmp) - 1);
 1386                 strncpy(tmp, arts[j].refptr ? arts[j].refptr->txt : "", len);
 1387                 tmp[len] = '\0';
 1388                 strcat(buffer, tmp);
 1389                 if ((k = (int) (len - (size_t) strwidth(tmp))) > 0) {
 1390                     buf = buffer + strlen(buffer);
 1391                     for (; k > 0; --k)
 1392                         *buf++ = ' ';
 1393                     *buf = '\0';
 1394                 }
 1395                 break;
 1396 
 1397             case 'n':
 1398                 strcat(buffer, tin_ltoa(i + 1, (int) grp_fmt.len_linenumber));
 1399                 break;
 1400 
 1401             case 'R':
 1402                 n = ((curr_group->attribute->show_only_unread_arts) ? (sbuf.unread + sbuf.seen) : sbuf.total);
 1403                 if (n > 1)
 1404                     strcat(buffer, tin_ltoa(n, (int) grp_fmt.len_respcnt));
 1405                 else {
 1406                     buf = buffer + strlen(buffer);
 1407                     for (k = (int) grp_fmt.len_respcnt; k > 0; --k)
 1408                         *buf++ = ' ';
 1409                     *buf = '\0';
 1410                 }
 1411                 break;
 1412 
 1413             case 'S':   /* score */
 1414                 strcat(buffer, tin_ltoa(sbuf.score, (int) grp_fmt.len_score));
 1415                 break;
 1416 
 1417             case 's':   /* thread/subject */
 1418                 len = curr_group->attribute->show_author != SHOW_FROM_NONE ? grp_fmt.len_subj : grp_fmt.len_subj + grp_fmt.len_from;
 1419 
 1420                 if (sbuf.multipart_have > 1) /* We have a multipart msg so lets built our new header info. */
 1421                     build_multipart_header(arts_sub, (int) len, arts[j].subject, sbuf.multipart_compare_len, sbuf.multipart_have, sbuf.multipart_total);
 1422                 else
 1423                     STRCPY(arts_sub, arts[j].subject);
 1424 
 1425 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
 1426                 if ((wtmp = char2wchar_t(arts_sub)) != NULL) {
 1427                     wtmp2 = wcspart(wtmp, (int) len, TRUE);
 1428                     if (wcstombs(tmp, wtmp2, sizeof(tmp) - 1) != (size_t) -1)
 1429                         strcat(buffer, tmp);
 1430 
 1431                     free(wtmp);
 1432                     free(wtmp2);
 1433                 }
 1434 #else
 1435                 len_start = strwidth(buffer);
 1436                 strncat(buffer, arts_sub, len);
 1437                 fill = len - (strwidth(buffer) - len_start);
 1438                 gap = strlen(buffer);
 1439                 for (k = 0; k < fill; k++)
 1440                     buffer[gap + k] = ' ';
 1441                 buffer[gap + fill] = '\0';
 1442 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
 1443                 break;
 1444 
 1445             default:
 1446                 break;
 1447         }
 1448     }
 1449     /* protect display from non-displayable characters (e.g., form-feed) */
 1450     convert_to_printable(buffer, FALSE);
 1451 
 1452 #ifndef USE_CURSES
 1453     if (tinrc.strip_blanks)
 1454         strcat(strip_line(buffer), cCRLF);
 1455 #endif /* !USE_CURSES */
 1456 
 1457     WriteLine(INDEX2LNUM(i), buffer);
 1458 
 1459 #ifdef USE_CURSES
 1460     free(buffer);
 1461 #endif /* USE_CURSES */
 1462     if (!tagged && sbuf.art_mark == tinrc.art_marked_selected)
 1463         draw_mark_selected(i);
 1464 }
 1465 
 1466 
 1467 static void
 1468 show_group_title(
 1469     t_bool clear_title)
 1470 {
 1471     char buf[LEN], tmp[LEN], flag;
 1472     int i, art_cnt = 0, recent_art_cnt = 0, selected_art_cnt = 0, read_selected_art_cnt = 0, killed_art_cnt = 0;
 1473 
 1474     for_each_art(i) {
 1475         if (arts[i].thread == ART_EXPIRED)
 1476             continue;
 1477 
 1478         if (curr_group->attribute->show_only_unread_arts) {
 1479             if (arts[i].status != ART_READ) {
 1480                 art_cnt++;
 1481                 if (tinrc.recent_time && ((time((time_t *) 0) - arts[i].date) < (tinrc.recent_time * DAY)))
 1482                     recent_art_cnt++;
 1483             }
 1484             if (arts[i].killed == ART_KILLED_UNREAD)
 1485                 killed_art_cnt++;
 1486         } else {
 1487             art_cnt++;
 1488             if (tinrc.recent_time && ((time((time_t *) 0) - arts[i].date) < (tinrc.recent_time * DAY)))
 1489                 recent_art_cnt++;
 1490 
 1491             if (arts[i].killed)
 1492                 killed_art_cnt++;
 1493         }
 1494         if (arts[i].selected) {
 1495             if (arts[i].status != ART_READ)
 1496                 selected_art_cnt++;
 1497             else
 1498                 read_selected_art_cnt++;
 1499         }
 1500     }
 1501 
 1502     /*
 1503      * build the group title
 1504      */
 1505     /* group name and thread count */
 1506     snprintf(buf, sizeof(buf), "%s (%d%c",
 1507         curr_group->name, grpmenu.max,
 1508         *txt_threading[curr_group->attribute->thread_articles]);
 1509 
 1510     /* article count */
 1511     if ((cmdline.args & CMDLINE_GETART_LIMIT) ? cmdline.getart_limit : tinrc.getart_limit)
 1512         snprintf(tmp, sizeof(tmp), " %d/%d%"T_CHAR_FMT,
 1513             (cmdline.args & CMDLINE_GETART_LIMIT) ? cmdline.getart_limit : tinrc.getart_limit, art_cnt,
 1514             (curr_group->attribute->show_only_unread_arts ? tinrc.art_marked_unread : tinrc.art_marked_read));
 1515     else
 1516         snprintf(tmp, sizeof(tmp), " %d%"T_CHAR_FMT,
 1517             art_cnt,
 1518             (curr_group->attribute->show_only_unread_arts ? tinrc.art_marked_unread : tinrc.art_marked_read));
 1519     if (sizeof(buf) > strlen(buf) + strlen(tmp))
 1520         strcat(buf, tmp);
 1521 
 1522     /* selected articles */
 1523     if (curr_group->attribute->show_only_unread_arts)
 1524         snprintf(tmp, sizeof(tmp), " %d%"T_CHAR_FMT,
 1525             selected_art_cnt, tinrc.art_marked_selected);
 1526     else
 1527         snprintf(tmp, sizeof(tmp), " %d%"T_CHAR_FMT" %d%"T_CHAR_FMT,
 1528             selected_art_cnt, tinrc.art_marked_selected,
 1529             read_selected_art_cnt, tinrc.art_marked_read_selected);
 1530     if (sizeof(buf) > strlen(buf) + strlen(tmp))
 1531         strcat(buf, tmp);
 1532 
 1533     /* recent articles */
 1534     if (tinrc.recent_time) {
 1535         snprintf(tmp, sizeof(tmp), " %d%"T_CHAR_FMT,
 1536             recent_art_cnt, tinrc.art_marked_recent);
 1537 
 1538         if (sizeof(buf) > strlen(buf) + strlen(tmp))
 1539             strcat(buf, tmp);
 1540     }
 1541 
 1542     /* killed articles */
 1543     snprintf(tmp, sizeof(tmp), " %d%"T_CHAR_FMT,
 1544         killed_art_cnt, tinrc.art_marked_killed);
 1545 
 1546     if (sizeof(buf) > strlen(buf) + strlen(tmp))
 1547         strcat(buf, tmp);
 1548 
 1549     /* group flag */
 1550     if ((flag = group_flag(curr_group->moderated)) == ' ')
 1551         snprintf(tmp, sizeof(tmp), ")");
 1552     else
 1553         snprintf(tmp, sizeof(tmp), ") %c", flag);
 1554     if (sizeof(buf) > strlen(buf) + strlen(tmp))
 1555         strcat(buf, tmp);
 1556 
 1557     if (clear_title) {
 1558         MoveCursor(0, 0);
 1559         CleartoEOLN();
 1560     }
 1561     show_title(buf);
 1562 }
 1563 
 1564 
 1565 /*
 1566  * Search for type SUBJ/AUTH in direction (TRUE = forwards)
 1567  * Return 0 if all is done, or a >0 thread_depth to enter the thread menu
 1568  */
 1569 static int
 1570 do_search(
 1571     t_function func,
 1572     t_bool repeat)
 1573 {
 1574     int start, n;
 1575 
 1576     if (grpmenu.curr < 0)
 1577         return 0;
 1578 
 1579     /*
 1580      * Not intuitive to search current thread in fwd search
 1581      */
 1582     start = ((func == GLOBAL_SEARCH_SUBJECT_FORWARD || func == GLOBAL_SEARCH_AUTHOR_FORWARD)
 1583         && grpmenu.curr < grpmenu.max - 1) ? prev_response((int) base[grpmenu.curr + 1]) : (int) base[grpmenu.curr];
 1584 
 1585     if (start >= 0 && ((n = search(func, start, repeat)) != -1)) {
 1586         grpmenu.curr = which_thread(n);
 1587 
 1588         /*
 1589          * If the search found something deeper in a thread(not the base art)
 1590          * then enter the thread
 1591          */
 1592         if ((n = which_response(n)) != 0)
 1593             return n;
 1594 
 1595         show_group_page();
 1596     }
 1597     return 0;
 1598 }
 1599 
 1600 
 1601 /*
 1602  * We don't directly invoke the pager, but pass through the thread menu
 1603  * to keep navigation sane.
 1604  * 'art' is the arts[art] we wish to read
 1605  * ignore_unavail should be set if we wish to 'keep going' after 'article unavailable'
 1606  * Return a -ve ret_code if we must exit the group menu on return
 1607  */
 1608 static int
 1609 enter_pager(
 1610     int art,
 1611     t_bool ignore_unavail)
 1612 {
 1613     t_pagerinfo page;
 1614 
 1615     page.art = art;
 1616     page.ignore_unavail = CAST_BOOL(ignore_unavail);
 1617 
 1618     return enter_thread(0, &page);
 1619 }
 1620 
 1621 
 1622 /*
 1623  * Handle entry/exit with the thread menu
 1624  * Return -ve ret_code if we must exit the group menu on return
 1625  */
 1626 static int
 1627 enter_thread(
 1628     int depth,
 1629     t_pagerinfo *page)
 1630 {
 1631     int i, n;
 1632 
 1633     if (grpmenu.curr < 0) {
 1634         info_message(_(txt_no_arts));
 1635         return 0;
 1636     }
 1637 
 1638     forever {
 1639         switch (i = thread_page(curr_group, (int) base[grpmenu.curr], depth, page)) {
 1640             case GRP_QUIT:                      /* 'Q'uit */
 1641             case GRP_RETSELECT:                 /* Back to selection screen */
 1642                 return i;
 1643                 /* NOTREACHED */
 1644                 break;
 1645 
 1646             case GRP_NEXT:                      /* 'c'atchup */
 1647                 show_group_page();
 1648                 move_down();
 1649                 return 0;
 1650                 /* NOTREACHED */
 1651                 break;
 1652 
 1653             case GRP_NEXTUNREAD:                /* 'C'atchup */
 1654                 if ((n = next_unread((int) base[grpmenu.curr])) >= 0) {
 1655                     if (page)
 1656                         page->art = n;
 1657                     if ((n = which_thread(n)) >= 0) {
 1658                         grpmenu.curr = n;
 1659                         depth = 0;
 1660                         break;      /* Drop into next thread with unread */
 1661                     }
 1662                 }
 1663                 /* No more unread threads in this group, enter next group */
 1664                 grpmenu.curr = 0;
 1665                 return GRP_NEXTUNREAD;
 1666                 /* NOTREACHED */
 1667                 break;
 1668 
 1669             case GRP_KILLED:
 1670                 grpmenu.curr = 0;
 1671                 /* FALLTHROUGH */
 1672 
 1673             case GRP_EXIT:
 1674             /* case GRP_GOTOTHREAD will never make it up this far */
 1675             default:        /* ie >= 0 Shouldn't happen any more? */
 1676                 clear_note_area();
 1677                 show_group_page();
 1678                 return 0;
 1679                 /* NOTREACHED */
 1680                 break;
 1681         }
 1682     }
 1683     /* NOTREACHED */
 1684     return 0;
 1685 }
 1686 
 1687 
 1688 /*
 1689  * Return a ret_code
 1690  */
 1691 static int
 1692 tab_pressed(
 1693     void)
 1694 {
 1695     int n;
 1696 
 1697     if ((n = ((grpmenu.curr < 0) ? -1 : next_unread((int) base[grpmenu.curr]))) < 0)
 1698         return GRP_NEXTUNREAD;          /* => Enter next unread group */
 1699 
 1700     /* We still have unread arts in the current group ... */
 1701     return enter_pager(n, TRUE);
 1702 }
 1703 
 1704 
 1705 /*
 1706  * There are three ways this is called
 1707  * catchup & return to group menu
 1708  * catchup & go to next group with unread articles
 1709  * group exit via left arrow if auto-catchup is set
 1710  * Return a -ve ret_code if we're done with the group menu
 1711  */
 1712 static int
 1713 group_catchup(
 1714     t_function func)
 1715 {
 1716     char buf[LEN];
 1717     int pyn = 1;
 1718 
 1719     if (num_of_tagged_arts && prompt_yn(_(txt_catchup_despite_tags), TRUE) != 1)
 1720         return 0;
 1721 
 1722     snprintf(buf, sizeof(buf), _(txt_mark_arts_read), (func == CATCHUP_NEXT_UNREAD) ? _(txt_enter_next_unread_group) : "");
 1723 
 1724     if (!curr_group->newsrc.num_unread || (!TINRC_CONFIRM_ACTION) || (pyn = prompt_yn(buf, TRUE)) == 1)
 1725         grp_mark_read(curr_group, arts);
 1726 
 1727     switch (func) {
 1728         case CATCHUP:               /* 'c' */
 1729             if (pyn == 1)
 1730                 return GRP_NEXT;
 1731             break;
 1732 
 1733         case CATCHUP_NEXT_UNREAD:           /* 'C' */
 1734             if (pyn == 1)
 1735                 return GRP_NEXTUNREAD;
 1736             break;
 1737 
 1738         case SPECIAL_CATCHUP_LEFT:              /* <- group catchup on exit */
 1739             switch (pyn) {
 1740                 case -1:                    /* ESCAPE - do nothing */
 1741                     break;
 1742 
 1743                 case 1:                     /* We caught up - advance group */
 1744                     return GRP_NEXT;
 1745                     /* NOTREACHED */
 1746                     break;
 1747 
 1748                 default:                    /* Just leave the group */
 1749                     return GRP_EXIT;
 1750                     /* NOTREACHED */
 1751                     break;
 1752             }
 1753             /* FALLTHROUGH */
 1754         default:                            /* Should not be here */
 1755             break;
 1756     }
 1757     return 0;                               /* Stay in this menu by default */
 1758 }
 1759 
 1760 
 1761 static t_bool
 1762 prompt_getart_limit(
 1763     void)
 1764 {
 1765     char *p;
 1766     t_bool ret = FALSE;
 1767 
 1768     clear_message();
 1769     if ((p = tin_getline(_(txt_enter_getart_limit), 2, NULL, 0, FALSE, HIST_OTHER)) != NULL) {
 1770         tinrc.getart_limit = atoi(p);
 1771         ret = TRUE;
 1772     }
 1773     clear_message();
 1774     return ret;
 1775 }
 1776 
 1777 
 1778 /*
 1779  * Redraw all necessary parts of the screen after FEED_MARK_(UN)READ
 1780  * Move cursor to next unread item if needed
 1781  *
 1782  * Returns TRUE when no next unread art, FALSE otherwise
 1783  */
 1784 t_bool
 1785 group_mark_postprocess(
 1786     int function,
 1787     t_function feed_type,
 1788     int respnum)
 1789 {
 1790     int n;
 1791 
 1792     show_group_title(TRUE);
 1793     switch (function) {
 1794         case (FEED_MARK_READ):
 1795             if (feed_type == FEED_THREAD || feed_type == FEED_ARTICLE)
 1796                 build_sline(grpmenu.curr);
 1797             else
 1798                 show_group_page();
 1799 
 1800             if ((n = next_unread(next_response(respnum))) == -1) {
 1801                 draw_subject_arrow();
 1802                 return TRUE;
 1803             }
 1804 
 1805             move_to_item(which_thread(n));
 1806             break;
 1807 
 1808         case (FEED_MARK_UNREAD):
 1809             if (feed_type == FEED_THREAD || feed_type == FEED_ARTICLE)
 1810                 build_sline(grpmenu.curr);
 1811             else
 1812                 show_group_page();
 1813 
 1814             draw_subject_arrow();
 1815             break;
 1816 
 1817         default:
 1818             break;
 1819     }
 1820     return FALSE;
 1821 }