"Fossies" - the Fresh Open Source Software Archive

Member "tin-2.4.4/src/group.c" (20 Nov 2019, 45016 Bytes) of package /linux/misc/tin-2.4.4.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.4.3_vs_2.4.4.

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