"Fossies" - the Fresh Open Source Software Archive

Member "tin-2.6.1/src/select.c" (22 Dec 2021, 43186 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 "select.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    : select.c
    4  *  Author    : I. Lea & R. Skrenta
    5  *  Created   : 1991-04-01
    6  *  Updated   : 2021-08-28
    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 static struct t_fmt sel_fmt;
   50 
   51 /*
   52  * Local prototypes
   53  */
   54 static t_function select_left(void);
   55 static t_function select_right(void);
   56 static int active_comp(t_comptype p1, t_comptype p2);
   57 static int reposition_group(struct t_group *group, int default_num);
   58 static int save_restore_curr_group(t_bool saving);
   59 static t_bool pos_next_unread_group(t_bool redraw);
   60 static t_bool yanked_out = TRUE;
   61 static void build_gline(int i);
   62 static void catchup_group(struct t_group *group, t_bool goto_next_unread_group);
   63 static void draw_group_arrow(void);
   64 static void read_groups(void);
   65 static void select_done(void);
   66 static void select_quit(void);
   67 static void select_read_group(void);
   68 static void sort_active_file(void);
   69 static void subscribe_pattern(const char *prompt, const char *message, const char *result, t_bool state);
   70 static void sync_active_file(void);
   71 static void yank_active_file(void);
   72 #ifdef NNTP_ABLE
   73     static char *lookup_msgid(char *msgid);
   74     static struct t_group *get_group_from_list(char *newsgroups);
   75 #endif /* NNTP_ABLE */
   76 
   77 
   78 /*
   79  * selmenu.curr = index (start at 0) of cursor position on menu,
   80  *                or -1 when no groups visible on screen
   81  * selmenu.max = Total # of groups in my_group[]
   82  * selmenu.first is static here
   83  */
   84 t_menu selmenu = { 1, 0, 0, show_selection_page, draw_group_arrow, build_gline };
   85 
   86 static int groupname_len;   /* max. group name length */
   87 static int flags_offset;
   88 static int ucnt_offset;
   89 
   90 
   91 static t_function
   92 select_left(
   93     void)
   94 {
   95     return GLOBAL_QUIT;
   96 }
   97 
   98 
   99 static t_function
  100 select_right(
  101     void)
  102 {
  103     return SELECT_ENTER_GROUP;
  104 }
  105 
  106 
  107 void
  108 selection_page(
  109     int start_groupnum,
  110     int num_cmd_line_groups)
  111 {
  112     char buf[LEN];
  113     char key[MAXKEYLEN];
  114     int i, n;
  115     t_function func;
  116 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
  117     wchar_t *wtmp;
  118 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
  119 
  120     selmenu.curr = start_groupnum;
  121 
  122 #ifdef READ_CHAR_HACK
  123     setbuf(stdin, 0);
  124 #endif /* READ_CHAR_HACK */
  125 
  126     Raw(TRUE);
  127     ClearScreen();
  128 
  129     /*
  130      * If user specified only 1 cmd line groupname (eg. tin -r alt.sources)
  131      * then go there immediately.
  132      */
  133     if (num_cmd_line_groups == 1)
  134         select_read_group();
  135 
  136     cursoroff();
  137     show_selection_page();  /* display group selection page */
  138 
  139     forever {
  140         if (!resync_active_file()) {
  141             if (reread_active_after_posting()) /* reread active file if necessary */
  142                 show_selection_page();
  143         } else {
  144             if (!yanked_out)
  145                 yanked_out = bool_not(yanked_out); /* yank out if yanked in */
  146         }
  147 
  148         set_xclick_on();
  149 
  150         switch ((func = handle_keypad(select_left, select_right, global_mouse_action, select_keys))) {
  151             case GLOBAL_ABORT:      /* Abort */
  152                 break;
  153 
  154             case DIGIT_1:
  155             case DIGIT_2:
  156             case DIGIT_3:
  157             case DIGIT_4:
  158             case DIGIT_5:
  159             case DIGIT_6:
  160             case DIGIT_7:
  161             case DIGIT_8:
  162             case DIGIT_9:
  163                 if (selmenu.max)
  164                     prompt_item_num(func_to_key(func, select_keys), _(txt_select_group));
  165                 else
  166                     info_message(_(txt_no_groups));
  167                 break;
  168 
  169 #ifndef NO_SHELL_ESCAPE
  170             case GLOBAL_SHELL_ESCAPE:
  171                 do_shell_escape();
  172                 break;
  173 #endif /* !NO_SHELL_ESCAPE */
  174 
  175             case GLOBAL_FIRST_PAGE:     /* show first page of groups */
  176                 top_of_list();
  177                 break;
  178 
  179             case GLOBAL_LAST_PAGE:      /* show last page of groups */
  180                 end_of_list();
  181                 break;
  182 
  183             case GLOBAL_PAGE_UP:
  184                 page_up();
  185                 break;
  186 
  187             case GLOBAL_PAGE_DOWN:
  188                 page_down();
  189                 break;
  190 
  191             case GLOBAL_LINE_UP:
  192                 move_up();
  193                 break;
  194 
  195             case GLOBAL_LINE_DOWN:
  196                 move_down();
  197                 break;
  198 
  199             case GLOBAL_SCROLL_DOWN:
  200                 scroll_down();
  201                 break;
  202 
  203             case GLOBAL_SCROLL_UP:
  204                 scroll_up();
  205                 break;
  206 
  207             case SELECT_SORT_ACTIVE:    /* sort active groups */
  208                 sort_active_file();
  209                 break;
  210 
  211             case GLOBAL_SET_RANGE:
  212                 if (selmenu.max) {
  213                     if (set_range(SELECT_LEVEL, 1, selmenu.max, selmenu.curr + 1))
  214                         show_selection_page();
  215                 } else
  216                     info_message(_(txt_no_groups));
  217                 break;
  218 
  219             case GLOBAL_SEARCH_SUBJECT_FORWARD:
  220             case GLOBAL_SEARCH_SUBJECT_BACKWARD:
  221             case GLOBAL_SEARCH_REPEAT:
  222                 if (func == GLOBAL_SEARCH_REPEAT && last_search != GLOBAL_SEARCH_SUBJECT_FORWARD && last_search != GLOBAL_SEARCH_SUBJECT_BACKWARD)
  223                     info_message(_(txt_no_prev_search));
  224                 else {
  225                     if ((i = search_active((func == GLOBAL_SEARCH_SUBJECT_FORWARD), (func == GLOBAL_SEARCH_REPEAT))) != -1) {
  226                         move_to_item(i);
  227                         clear_message();
  228                     }
  229                 }
  230                 break;
  231 
  232             case SELECT_ENTER_GROUP:        /* go into group */
  233                 select_read_group();
  234                 break;
  235 
  236             case SELECT_ENTER_NEXT_UNREAD_GROUP:    /* enter next group containing unread articles */
  237                 if (pos_next_unread_group(FALSE))
  238                     read_groups();
  239                 break;                          /* Nothing more to do at the moment */
  240 
  241             case GLOBAL_REDRAW_SCREEN:
  242                 my_retouch();                   /* TODO: not done elsewhere, maybe should be in show_selection_page */
  243                 set_xclick_off();
  244                 show_selection_page();
  245                 break;
  246 
  247             case SELECT_RESET_NEWSRC:       /* reset .newsrc */
  248                 if (no_write) {
  249                     info_message(_(txt_info_no_write));
  250                     break;
  251                 }
  252                 if (prompt_yn(_(txt_reset_newsrc), FALSE) == 1) {
  253                     reset_newsrc();
  254                     sync_active_file();
  255                     selmenu.curr = 0;
  256                     show_selection_page();
  257                 }
  258                 break;
  259 
  260             case CATCHUP:           /* catchup - mark all articles as read */
  261             case CATCHUP_NEXT_UNREAD:   /* and goto next unread group */
  262                 if (selmenu.max)
  263                     catchup_group(&CURR_GROUP, (func == CATCHUP_NEXT_UNREAD));
  264                 else
  265                     info_message(_(txt_no_groups));
  266                 break;
  267 
  268             case GLOBAL_EDIT_FILTER:
  269                 if (invoke_editor(filter_file, filter_file_offset, NULL))
  270                     (void) read_filter_file(filter_file);
  271                 show_selection_page();
  272                 break;
  273 
  274             case SELECT_TOGGLE_DESCRIPTIONS:    /* toggle newsgroup descriptions */
  275                 if (sel_fmt.show_grpdesc) {
  276                     show_description = bool_not(show_description);
  277                     if (show_description)
  278                         read_descriptions(TRUE);
  279                     show_selection_page();
  280                 } else
  281                     info_message(_(txt_grpdesc_disabled));
  282                 break;
  283 
  284             case SELECT_GOTO:           /* prompt for a new group name */
  285                 {
  286                     int oldmax = selmenu.max;
  287 
  288                     if ((n = choose_new_group()) >= 0) {
  289                         /*
  290                          * If a new group was added and it is on the actual screen
  291                          * draw it. If it is off screen the redraw will handle it.
  292                          */
  293                         if (oldmax != selmenu.max && n >= selmenu.first && n < selmenu.first + NOTESLINES)
  294                             build_gline(n);
  295                         move_to_item(n);
  296                     }
  297                 }
  298                 break;
  299 
  300             case GLOBAL_HELP:
  301                 show_help_page(SELECT_LEVEL, _(txt_group_select_com));
  302                 show_selection_page();
  303                 break;
  304 
  305 #ifdef NNTP_ABLE
  306             case GLOBAL_LOOKUP_MESSAGEID:
  307                 switch (show_article_by_msgid(NULL)) {
  308                     case LOOKUP_OK:
  309                         show_selection_page();
  310                         break;
  311 
  312                     case LOOKUP_UNAVAIL:
  313                         info_message("%s %s", _(txt_lookup_func_not_available), _(txt_lookup_func_not_nntp));
  314                         break;
  315 
  316                     case LOOKUP_QUIT:
  317                         select_quit();
  318                         break;
  319 
  320                     default:
  321                         break;
  322                 }
  323                 break;
  324 #endif /* NNTP_ABLE */
  325 
  326             case GLOBAL_TOGGLE_HELP_DISPLAY:    /* toggle mini help menu */
  327                 toggle_mini_help(SELECT_LEVEL);
  328                 show_selection_page();
  329                 break;
  330 
  331             case GLOBAL_TOGGLE_INVERSE_VIDEO:
  332                 toggle_inverse_video();
  333                 show_selection_page();
  334                 show_inverse_video_status();
  335                 break;
  336 
  337 #ifdef HAVE_COLOR
  338             case GLOBAL_TOGGLE_COLOR:
  339                 if (toggle_color()) {
  340                     show_selection_page();
  341                     show_color_status();
  342                 }
  343                 break;
  344 #endif /* HAVE_COLOR */
  345 
  346             case GLOBAL_TOGGLE_INFO_LAST_LINE:  /* display group description */
  347                 tinrc.info_in_last_line = bool_not(tinrc.info_in_last_line);
  348                 show_selection_page();
  349                 break;
  350 
  351             case SELECT_MOVE_GROUP:         /* reposition group within group list */
  352                 /* TODO: move all this to reposition_group() */
  353                 if (!selmenu.max) {
  354                     info_message(_(txt_no_groups));
  355                     break;
  356                 }
  357 
  358                 if (!CURR_GROUP.subscribed) {
  359                     info_message(_(txt_info_not_subscribed));
  360                     break;
  361                 }
  362 
  363                 if (no_write) {
  364                     info_message(_(txt_info_no_write));
  365                     break;
  366                 }
  367 
  368                 n = selmenu.curr;
  369                 selmenu.curr = reposition_group(&active[my_group[n]], n);
  370                 HpGlitch(erase_arrow());
  371                 if (selmenu.curr < selmenu.first || selmenu.curr >= selmenu.first + NOTESLINES - 1 || selmenu.curr != n)
  372                     show_selection_page();
  373                 else {
  374                     i = selmenu.curr;
  375                     selmenu.curr = n;
  376                     erase_arrow();
  377                     selmenu.curr = i;
  378                     clear_message();
  379                     draw_group_arrow();
  380                 }
  381                 break;
  382 
  383             case GLOBAL_OPTION_MENU:
  384                 config_page(selmenu.max ? CURR_GROUP.name : NULL, signal_context);
  385                 show_selection_page();
  386                 break;
  387 
  388             case SELECT_NEXT_UNREAD_GROUP:      /* goto next unread group */
  389                 pos_next_unread_group(TRUE);
  390                 break;
  391 
  392             case GLOBAL_QUIT:           /* quit */
  393                 select_done();
  394                 break;
  395 
  396             case GLOBAL_QUIT_TIN:           /* quit, no ask */
  397                 select_quit();
  398                 break;
  399 
  400             case SELECT_QUIT_NO_WRITE:      /* quit, but don't save configuration */
  401                 if (prompt_yn(_(txt_quit_no_write), TRUE) == 1)
  402                     tin_done(EXIT_SUCCESS, NULL);
  403                 show_selection_page();
  404                 break;
  405 
  406             case SELECT_TOGGLE_READ_DISPLAY:
  407                 /*
  408                  * If in tinrc.show_only_unread_groups mode toggle all
  409                  * subscribed to groups and only groups that contain unread
  410                  * articles
  411                  */
  412                 tinrc.show_only_unread_groups = bool_not(tinrc.show_only_unread_groups);
  413                 /*
  414                  * as we effectively do a yank out on each change, set yanked_out accordingly
  415                  */
  416                 yanked_out = TRUE;
  417                 wait_message(0, _(txt_reading_groups), (tinrc.show_only_unread_groups) ? _("unread") : _("all"));
  418 
  419                 toggle_my_groups(NULL);
  420                 show_selection_page();
  421                 if (tinrc.show_only_unread_groups)
  422                     info_message(_(txt_show_unread));
  423                 else
  424                     clear_message();
  425                 break;
  426 
  427             case GLOBAL_BUGREPORT:
  428                 bug_report();
  429                 break;
  430 
  431             case SELECT_SUBSCRIBE:          /* subscribe to current group */
  432                 if (!selmenu.max) {
  433                     info_message(_(txt_no_groups));
  434                     break;
  435                 }
  436                 if (no_write) {
  437                     info_message(_(txt_info_no_write));
  438                     break;
  439                 }
  440                 if (!CURR_GROUP.subscribed && !CURR_GROUP.bogus) {
  441                     subscribe(&CURR_GROUP, SUBSCRIBED, TRUE);
  442                     show_selection_page();
  443                     info_message(_(txt_subscribed_to), CURR_GROUP.name);
  444                     move_down();
  445                 }
  446                 break;
  447 
  448             case SELECT_SUBSCRIBE_PATTERN:      /* subscribe to groups matching pattern */
  449                 if (no_write) {
  450                     info_message(_(txt_info_no_write));
  451                     break;
  452                 }
  453                 subscribe_pattern(_(txt_subscribe_pattern), _(txt_subscribing), _(txt_subscribed_num_groups), TRUE);
  454                 break;
  455 
  456             case SELECT_UNSUBSCRIBE:        /* unsubscribe to current group */
  457                 if (!selmenu.max) {
  458                     info_message(_(txt_no_groups));
  459                     break;
  460                 }
  461                 if (no_write) {
  462                     info_message(_(txt_info_no_write));
  463                     break;
  464                 }
  465                 if (CURR_GROUP.subscribed) {
  466 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
  467                     mark_screen(selmenu.curr, flags_offset, CURR_GROUP.newgroup ? L"N" : L"u");
  468 #else
  469                     mark_screen(selmenu.curr, flags_offset, CURR_GROUP.newgroup ? "N" : "u");
  470 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
  471                     subscribe(&CURR_GROUP, UNSUBSCRIBED, TRUE);
  472                     info_message(_(txt_unsubscribed_to), CURR_GROUP.name);
  473                     move_down();
  474                 } else if (CURR_GROUP.bogus && tinrc.strip_bogus == BOGUS_SHOW) {
  475                     /* Bogus groups aren't subscribed to avoid confusion */
  476                     /* Note that there is no way to remove the group from active[] */
  477                     snprintf(buf, sizeof(buf), _(txt_remove_bogus), CURR_GROUP.name);
  478                     write_newsrc();                 /* save current newsrc */
  479                     delete_group(CURR_GROUP.name);      /* remove bogus group */
  480                     read_newsrc(newsrc, TRUE);          /* reload newsrc */
  481                     toggle_my_groups(NULL);         /* keep current display-state */
  482                     show_selection_page();              /* redraw screen */
  483                     info_message(buf);
  484                 }
  485                 break;
  486 
  487             case SELECT_UNSUBSCRIBE_PATTERN:    /* unsubscribe to groups matching pattern */
  488                 if (no_write) {
  489                     info_message(_(txt_info_no_write));
  490                     break;
  491                 }
  492                 subscribe_pattern(_(txt_unsubscribe_pattern),
  493                                 _(txt_unsubscribing), _(txt_unsubscribed_num_groups), FALSE);
  494                 break;
  495 
  496             case GLOBAL_VERSION:            /* show tin version */
  497                 info_message(cvers);
  498                 break;
  499 
  500             case GLOBAL_POST:           /* post a basenote */
  501                 if (!selmenu.max) {
  502                     if (!can_post) {
  503                         info_message(_(txt_cannot_post));
  504                         break;
  505                     }
  506                     snprintf(buf, sizeof(buf), _(txt_post_newsgroups), tinrc.default_post_newsgroups);
  507                     if (!prompt_string_default(buf, tinrc.default_post_newsgroups, _(txt_no_newsgroups), HIST_POST_NEWSGROUPS))
  508                         break;
  509                     if (group_find(tinrc.default_post_newsgroups, FALSE) == NULL) {
  510                         error_message(2, _(txt_not_in_active_file), tinrc.default_post_newsgroups);
  511                         break;
  512                     } else {
  513                         strcpy(buf, tinrc.default_post_newsgroups);
  514 #if 1 /* TODO: fix the rest of the code so we don't need this anymore */
  515                         /*
  516                          * this is a gross hack to avoid a crash in the
  517                          * CHARSET_CONVERSION conversion case in new_part()
  518                          * which currently relies on CURR_GROUP
  519                          */
  520                         selmenu.curr = my_group_add(buf, FALSE);
  521                         /*
  522                          * and the next hack to avoid a grabbled selection
  523                          * screen after the posting
  524                          */
  525                         toggle_my_groups(NULL);
  526                         toggle_my_groups(NULL);
  527 #endif /* 1 */
  528                     }
  529                 } else
  530                     STRCPY(buf, CURR_GROUP.name);
  531                 if (!can_post && !CURR_GROUP.bogus && !CURR_GROUP.attribute->mailing_list) {
  532                     info_message(_(txt_cannot_post));
  533                     break;
  534                 }
  535                 if (post_article(buf))
  536                     show_selection_page();
  537                 break;
  538 
  539             case GLOBAL_POSTPONED:          /* post postponed article */
  540                 if (can_post) {
  541                     if (pickup_postponed_articles(FALSE, FALSE))
  542                         show_selection_page();
  543                 } else
  544                     info_message(_(txt_cannot_post));
  545                 break;
  546 
  547             case GLOBAL_DISPLAY_POST_HISTORY:   /* display messages posted by user */
  548                 if (post_hist_page())
  549                     show_selection_page();
  550                 break;
  551 
  552             case SELECT_YANK_ACTIVE:        /* yank in/out rest of groups from active */
  553                 yank_active_file();
  554                 break;
  555 
  556             case SELECT_SYNC_WITH_ACTIVE:       /* Re-read active file to see if any new news */
  557                 sync_active_file();
  558                 if (!yanked_out)
  559                     yank_active_file();         /* yank out if yanked in */
  560                 break;
  561 
  562             case SELECT_MARK_GROUP_UNREAD:
  563                 if (!selmenu.max) {
  564                     info_message(_(txt_no_groups));
  565                     break;
  566                 }
  567                 grp_mark_unread(&CURR_GROUP);
  568                 if (CURR_GROUP.newsrc.num_unread)
  569                     STRCPY(buf, tin_ltoa(CURR_GROUP.newsrc.num_unread, (int) sel_fmt.len_ucnt));
  570                 else {
  571                     size_t j = 0;
  572 
  573                     while (j < sel_fmt.len_ucnt)
  574                         buf[j++] = ' ';
  575                     buf[j] = '\0';
  576                 }
  577 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
  578                 if ((wtmp = char2wchar_t(buf))) {
  579                     mark_screen(selmenu.curr, ucnt_offset, wtmp);
  580                     free(wtmp);
  581                 }
  582 #else
  583                 mark_screen(selmenu.curr, ucnt_offset, buf);
  584 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
  585                 break;
  586 
  587             default:
  588                 info_message(_(txt_bad_command), PrintFuncKey(key, GLOBAL_HELP, select_keys));
  589         }
  590     }
  591 }
  592 
  593 
  594 void
  595 show_selection_page(
  596     void)
  597 {
  598     char buf[LEN];
  599     int i;
  600     size_t len;
  601 
  602     signal_context = cSelect;
  603     currmenu = &selmenu;
  604     parse_format_string(tinrc.select_format, &sel_fmt);
  605     groupname_len = 0;
  606     flags_offset = 0;
  607     mark_offset = 0;
  608     ucnt_offset = 0;
  609 
  610     if (read_news_via_nntp)
  611         snprintf(buf, sizeof(buf), "%s (%s  %d%s)", _(txt_group_selection), nntp_server, selmenu.max, (tinrc.show_only_unread_groups ? _(" R") : ""));
  612     else
  613         snprintf(buf, sizeof(buf), "%s (%d%s)", _(txt_group_selection), selmenu.max, (tinrc.show_only_unread_groups ? _(" R") : ""));
  614 
  615     if (selmenu.curr < 0)
  616         selmenu.curr = 0;
  617 
  618     ClearScreen();
  619     set_first_screen_item();
  620     show_title(buf);
  621 
  622     if (sel_fmt.len_grpname_max && !sel_fmt.len_grpname) {
  623         /*
  624          * calculate max length of groupname field
  625          * if yanked in (yanked_out == FALSE) check all groups in active file
  626          * otherwise just subscribed to groups
  627          */
  628         if (yanked_out) {
  629             for (i = 0; i < selmenu.max; i++) {
  630                 if ((len = (size_t) strwidth(active[my_group[i]].name)) > sel_fmt.len_grpname)
  631                     sel_fmt.len_grpname = len;
  632             }
  633         } else {
  634             for_each_group(i) {
  635                 if ((len = (size_t) strwidth(active[i].name)) > sel_fmt.len_grpname)
  636                     sel_fmt.len_grpname = len;
  637             }
  638         }
  639     }
  640 
  641     groupname_len = (sel_fmt.show_grpdesc && show_description) ? (int) sel_fmt.len_grpname_dsc : (int) sel_fmt.len_grpname;
  642 
  643     if (groupname_len > (int) sel_fmt.len_grpname_max)
  644         groupname_len = (int) sel_fmt.len_grpname_max;
  645     if (groupname_len < 0)
  646         groupname_len = 0;
  647 
  648     if (!sel_fmt.len_grpdesc)
  649         sel_fmt.len_grpdesc = (sel_fmt.len_grpname_max - (size_t) groupname_len);
  650     else {
  651         if (sel_fmt.len_grpdesc > (sel_fmt.len_grpname_max - (size_t) groupname_len))
  652             sel_fmt.len_grpdesc = (sel_fmt.len_grpname_max - (size_t) groupname_len);
  653     }
  654 
  655     flags_offset = (int) (sel_fmt.flags_offset + (size_t) (sel_fmt.g_before_f ? groupname_len : 0) + (sel_fmt.d_before_f ? sel_fmt.len_grpdesc : 0));
  656     ucnt_offset = (int) (sel_fmt.ucnt_offset + (size_t) (sel_fmt.g_before_u ? groupname_len : 0) + (sel_fmt.d_before_u ? sel_fmt.len_grpdesc : 0));
  657 
  658     for (i = selmenu.first; i < selmenu.first + NOTESLINES && i < selmenu.max; i++)
  659         build_gline(i);
  660 
  661     show_mini_help(SELECT_LEVEL);
  662 
  663 #ifdef NNTP_ABLE
  664     did_reconnect = FALSE;
  665 #endif /* NNTP_ABLE */
  666 
  667     if (selmenu.max <= 0) {
  668         info_message(_(txt_no_groups));
  669         return;
  670     }
  671 
  672     draw_group_arrow();
  673 }
  674 
  675 
  676 static void
  677 build_gline(
  678     int i)
  679 {
  680     char *sptr, *fmt, *buf;
  681     char subs;
  682     int n;
  683     size_t j;
  684 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
  685     char *name_buf = NULL;
  686     char *desc_buf = NULL;
  687     wchar_t *active_name, *active_name2, *active_desc, *active_desc2;
  688 #else
  689     char *active_name, *active_name2;
  690     size_t fill, len_start;
  691 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
  692 
  693 #ifdef USE_CURSES
  694     /*
  695      * Allocate line buffer
  696      * make it the same size like in !USE_CURSES case to simplify the code
  697      */
  698 #   if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
  699         sptr = my_malloc(cCOLS * MB_CUR_MAX + 2);
  700 #   else
  701         sptr = my_malloc(cCOLS + 2);
  702 #   endif /* MULTIBYTE_ABLE && !NO_LOCALE */
  703 #else
  704     sptr = screen[INDEX2SNUM(i)].col;
  705 #endif /* USE_CURSES */
  706 
  707     sptr[0] = '\0';
  708     fmt = sel_fmt.str;
  709     n = my_group[i];
  710 
  711     if (tinrc.draw_arrow)
  712         strcat(sptr, "  ");
  713 
  714     for (; *fmt; fmt++) {
  715         if (*fmt != '%') {
  716             strncat(sptr, fmt, 1);
  717             continue;
  718         }
  719         switch (*++fmt) {
  720             case '\0':
  721                 break;
  722 
  723             case '%':
  724                 strncat(sptr, fmt, 1);
  725                 break;
  726 
  727             case 'd':
  728 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
  729                 if (show_description && active[n].description) {
  730                     active_desc = char2wchar_t(active[n].description);
  731                     if (active_desc) {
  732                         if ((active_desc2 = wcspart(active_desc, (int) sel_fmt.len_grpdesc, TRUE)) != NULL) {
  733                             desc_buf = wchar_t2char(active_desc2);
  734                             free(active_desc);
  735                             free(active_desc2);
  736                         }
  737                     }
  738                     if (desc_buf) {
  739                         strcat(sptr, desc_buf);
  740                         FreeAndNull(desc_buf);
  741                     }
  742                 } else {
  743                     buf = sptr + strlen(sptr);
  744                     for (j = 0; j < sel_fmt.len_grpdesc; ++j)
  745                         *buf++ = ' ';
  746                     *buf = '\0';
  747                 }
  748 #else
  749                 if (show_description && active[n].description) {
  750                     len_start = strwidth(sptr);
  751                     strncat(sptr, active[n].description, sel_fmt.len_grpdesc);
  752                     fill = sel_fmt.len_grpdesc - (strwidth(sptr) - len_start);
  753                 } else
  754                     fill = sel_fmt.len_grpdesc;
  755                 buf = sptr + strlen(sptr);
  756                 for (j = 0; j < fill; ++j)
  757                     *buf++ = ' ';
  758                 *buf = '\0';
  759 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
  760                 break;
  761 
  762             case 'f':
  763                 /*
  764                  * Display a flag for this group if needed
  765                  * . Bogus groups are dumped immediately 'D'
  766                  * . Normal subscribed groups may be
  767                  *   ' ' normal, 'X' not postable, 'M' moderated, '=' renamed
  768                  * . Newgroups are 'N'
  769                  * . Unsubscribed groups are 'u'
  770                  *
  771                  * TODO: make flags configurable via tinrc?
  772                  */
  773                 if (active[n].bogus)                    /* Group is not in active list */
  774                     subs = 'D';
  775                 else if (active[n].subscribed)          /* Important that this precedes Newgroup */
  776                     subs = group_flag(active[n].moderated);
  777                 else
  778                     subs = ((active[n].newgroup) ? 'N' : 'u'); /* New (but unsubscribed) group or unsubscribed group */
  779                 buf = sptr + strlen(sptr);
  780                 *buf++ = subs;
  781                 *buf = '\0';
  782                 break;
  783 
  784             case 'G':
  785 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
  786                 if ((active_name = char2wchar_t(active[n].name)) == NULL) /* If char2wchar_t() fails try again after replacing unprintable characters */
  787                     active_name = char2wchar_t(convert_to_printable(active[n].name, FALSE));
  788 
  789                 if (active_name && tinrc.abbreviate_groupname) {
  790                     active_name2 = abbr_wcsgroupname(active_name, groupname_len);
  791                     free(active_name);
  792                 } else
  793                     active_name2 = active_name;
  794 
  795                 if (active_name2 && (active_name = wcspart(active_name2, groupname_len, TRUE)) != NULL) {
  796                     free(active_name2);
  797                     name_buf = wchar_t2char(active_name);
  798                     free(active_name);
  799                 }
  800                 if (name_buf) {
  801                     strcat(sptr, name_buf);
  802                     FreeAndNull(name_buf);
  803                 }
  804 #else
  805                 if (tinrc.abbreviate_groupname)
  806                     active_name = abbr_groupname(active[n].name, groupname_len);
  807                 else
  808                     active_name = my_strdup(active[n].name);
  809 
  810                 active_name2 = my_malloc(groupname_len + 1);
  811                 snprintf(active_name2, groupname_len + 1, "%-*s", groupname_len, active_name);
  812                 strcat(sptr, active_name2);
  813                 free(active_name);
  814                 free(active_name2);
  815 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
  816                 break;
  817 
  818             case 'n':
  819                 strcat(sptr, tin_ltoa(i + 1, (int) sel_fmt.len_linenumber));
  820                 break;
  821 
  822             case 'U':
  823                 if (active[my_group[i]].inrange) {
  824 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
  825                     char tmp_buf[10];
  826 
  827                     buf = sptr + strlen(sptr);
  828                     for (j = 1; j <= sel_fmt.len_ucnt - (art_mark_width - (art_mark_width - wcwidth(tinrc.art_marked_inrange))); ++j)
  829                         *buf++ = ' ';
  830                     snprintf(tmp_buf, sizeof(tmp_buf), "%"T_CHAR_FMT, tinrc.art_marked_inrange);
  831                     *buf-- = '\0';
  832                     strcat(buf, tmp_buf);
  833 #else
  834                     buf = sptr + strlen(sptr);
  835                     for (j = 1; j < sel_fmt.len_ucnt; ++j)
  836                         *buf++ = ' ';
  837                     *buf++ = tinrc.art_marked_inrange;
  838                     *buf = '\0';
  839 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
  840                 } else if (active[my_group[i]].newsrc.num_unread) {
  841                     int getart_limit;
  842                     t_artnum num_unread;
  843 
  844                     getart_limit = (cmdline.args & CMDLINE_GETART_LIMIT) ? cmdline.getart_limit : tinrc.getart_limit;
  845                     num_unread = active[my_group[i]].newsrc.num_unread;
  846                     if (getart_limit > 0 && getart_limit < num_unread)
  847                         num_unread = getart_limit;
  848                     strcat(sptr, tin_ltoa(num_unread, (int) sel_fmt.len_ucnt));
  849                 } else {
  850                     buf = sptr + strlen(sptr);
  851                     for (j = 0; j < sel_fmt.len_ucnt; ++j)
  852                         *buf++ = ' ';
  853                     *buf = '\0';
  854                 }
  855                 break;
  856 
  857             default:
  858                 break;
  859         }
  860     }
  861 #ifndef USE_CURSES
  862     if (tinrc.strip_blanks)
  863         strcat(strip_line(sptr), cCRLF);
  864 #endif /* !USE_CURSES */
  865 
  866     WriteLine(INDEX2LNUM(i), sptr);
  867 
  868 #ifdef USE_CURSES
  869     free(sptr);
  870 #endif /* USE_CURSES */
  871 }
  872 
  873 
  874 static void
  875 draw_group_arrow(
  876     void)
  877 {
  878     if (!selmenu.max)
  879         info_message(_(txt_no_groups));
  880     else {
  881         draw_arrow_mark(INDEX_TOP + selmenu.curr - selmenu.first);
  882         if (CURR_GROUP.aliasedto)
  883             info_message(_(txt_group_aliased), CURR_GROUP.aliasedto);
  884         else if (tinrc.info_in_last_line)
  885             info_message("%s", CURR_GROUP.description ? CURR_GROUP.description : _(txt_no_description));
  886         else if (selmenu.curr == selmenu.max - 1)
  887             info_message(_(txt_end_of_groups));
  888     }
  889 }
  890 
  891 
  892 static void
  893 sync_active_file(
  894     void)
  895 {
  896     wait_message(0, _(txt_reading_news_newsrc_file));
  897     force_reread_active_file = TRUE;
  898     resync_active_file();
  899 }
  900 
  901 
  902 static void
  903 yank_active_file(
  904     void)
  905 {
  906     if (yanked_out && selmenu.max == num_active) {          /* All groups currently present? */
  907         info_message(_(txt_yanked_none));
  908         return;
  909     }
  910 
  911     if (yanked_out) {                       /* Yank in */
  912         int i;
  913         int prevmax = selmenu.max;
  914 
  915         save_restore_curr_group(TRUE);              /* Save group position */
  916 
  917         /*
  918          * Reset counter and load all the groups in active[] into my_group[]
  919          */
  920         selmenu.max = 0;
  921         for_each_group(i)
  922             my_group[selmenu.max++] = i;
  923 
  924         selmenu.curr = save_restore_curr_group(FALSE);  /* Restore previous group position */
  925         yanked_out = bool_not(yanked_out);
  926         show_selection_page();
  927         info_message(_(txt_yanked_groups), selmenu.max - prevmax, PLURAL(selmenu.max - prevmax, txt_group));
  928     } else {                            /* Yank out */
  929         toggle_my_groups(NULL);
  930         HpGlitch(erase_arrow());
  931         yanked_out = bool_not(yanked_out);
  932         show_selection_page();
  933         info_message(_(txt_yanked_sub_groups));
  934     }
  935 }
  936 
  937 
  938 /*
  939  * Sort active[] and associated tin_sort() helper function
  940  */
  941 static int
  942 active_comp(
  943     t_comptype p1,
  944     t_comptype p2)
  945 {
  946     const struct t_group *s1 = (const struct t_group *) p1;
  947     const struct t_group *s2 = (const struct t_group *) p2;
  948 
  949     return strcasecmp(s1->name, s2->name);
  950 }
  951 
  952 
  953 /*
  954  * Call with TRUE to file away the current cursor position
  955  * Call again with FALSE to return a suggested value to restore the
  956  * current cursor (selmenu.curr) position
  957  */
  958 static int
  959 save_restore_curr_group(
  960     t_bool saving)
  961 {
  962     static char *oldgroup;
  963     static int oldmax = 0;
  964     int ret;
  965 
  966     /*
  967      * Take a copy of the current groupname, if present
  968      */
  969     if (saving) {
  970         oldmax = selmenu.max;
  971         if (oldmax)
  972             oldgroup = my_strdup(CURR_GROUP.name);
  973         return 0;
  974     }
  975 
  976     /*
  977      * Find & return the new screen position of the group
  978      */
  979     ret = -1;
  980 
  981     if (oldmax) {
  982         ret = my_group_find(oldgroup);
  983         FreeAndNull(oldgroup);
  984     }
  985 
  986     if (ret == -1) {        /* Group not present, return something semi-useful */
  987         if (selmenu.max > 0)
  988             ret = selmenu.max - 1;
  989         else
  990             ret = 0;
  991     }
  992     return ret;
  993 }
  994 
  995 
  996 static void
  997 sort_active_file(
  998     void)
  999 {
 1000     save_restore_curr_group(TRUE);
 1001 
 1002     tin_sort(active, (size_t) num_active, sizeof(struct t_group), active_comp);
 1003     group_rehash(yanked_out);
 1004 
 1005     selmenu.curr = save_restore_curr_group(FALSE);
 1006 
 1007     show_selection_page();
 1008 }
 1009 
 1010 
 1011 int
 1012 choose_new_group(
 1013     void)
 1014 {
 1015     char *prompt;
 1016     int idx;
 1017 
 1018     prompt = fmt_string(_(txt_newsgroup), tinrc.default_goto_group);
 1019 
 1020     if (!(prompt_string_default(prompt, tinrc.default_goto_group, "", HIST_GOTO_GROUP))) {
 1021         free(prompt);
 1022         return -1;
 1023     }
 1024     free(prompt);
 1025 
 1026     str_trim(tinrc.default_goto_group);
 1027 
 1028     if (tinrc.default_goto_group[0] == '\0')
 1029         return -1;
 1030 
 1031     clear_message();
 1032 
 1033     if ((idx = my_group_add(tinrc.default_goto_group, TRUE)) == -1)
 1034         info_message(_(txt_not_in_active_file), tinrc.default_goto_group);
 1035 
 1036     return idx;
 1037 }
 1038 
 1039 
 1040 /*
 1041  * Return new value for selmenu.max, skipping any new newsgroups that have been
 1042  * found
 1043  */
 1044 int
 1045 skip_newgroups(
 1046     void)
 1047 {
 1048     int i = 0;
 1049 
 1050     if (selmenu.max) {
 1051         while (i < selmenu.max && active[my_group[i]].newgroup)
 1052             i++;
 1053     }
 1054 
 1055     return i;
 1056 }
 1057 
 1058 
 1059 /*
 1060  * Find a group in the users selection list, my_group[].
 1061  * If 'add' is TRUE, then add the supplied group return the index into
 1062  * my_group[] if group is added or was already there. Return -1 if group
 1063  * is not in active[]
 1064  *
 1065  * NOTE: can't be static due to my_group_add() macro
 1066  */
 1067 int
 1068 add_my_group(
 1069     const char *group,
 1070     t_bool add,
 1071     t_bool ignore_case)
 1072 {
 1073     int i, j;
 1074 
 1075     if ((i = find_group_index(group, ignore_case)) < 0)
 1076         return -1;
 1077 
 1078     for (j = 0; j < selmenu.max; j++) {
 1079         if (my_group[j] == i)
 1080             return j;
 1081     }
 1082 
 1083     if (add) {
 1084         my_group[selmenu.max++] = i;
 1085         return (selmenu.max - 1);
 1086     }
 1087 
 1088     return -1;
 1089 }
 1090 
 1091 
 1092 static int
 1093 reposition_group(
 1094     struct t_group *group,
 1095     int default_num)
 1096 {
 1097     char buf[LEN];
 1098     char pos[LEN];
 1099     int pos_num, newgroups;
 1100 
 1101     /* Have already trapped no_write at this point */
 1102 
 1103     snprintf(buf, sizeof(buf), _(txt_newsgroup_position), group->name,
 1104         (tinrc.default_move_group ? tinrc.default_move_group : default_num + 1));
 1105 
 1106     if (!prompt_string(buf, pos, HIST_MOVE_GROUP))
 1107         return default_num;
 1108 
 1109     if (strlen(pos))
 1110         pos_num = ((pos[0] == '$') ? selmenu.max : atoi(pos));
 1111     else {
 1112         if (tinrc.default_move_group)
 1113             pos_num = tinrc.default_move_group;
 1114         else
 1115             return default_num;
 1116     }
 1117 
 1118     if (pos_num > selmenu.max)
 1119         pos_num = selmenu.max;
 1120     else if (pos_num <= 0)
 1121         pos_num = 1;
 1122 
 1123     newgroups = skip_newgroups();
 1124 
 1125     /*
 1126      * Can't move into newgroups, they aren't in .newsrc
 1127      */
 1128     if (pos_num <= newgroups) {
 1129         error_message(2, _(txt_skipping_newgroups));
 1130         return default_num;
 1131     }
 1132 
 1133     wait_message(0, _(txt_moving), group->name);
 1134 
 1135     /*
 1136      * seems to have the side effect of rearranging
 1137      * my_groups, so tinrc.show_only_unread_groups has to be updated
 1138      */
 1139     tinrc.show_only_unread_groups = FALSE;
 1140 
 1141     /*
 1142      * New newgroups aren't in .newsrc so we need to offset to
 1143      * get the right position
 1144      */
 1145     if (pos_group_in_newsrc(group, pos_num - newgroups)) {
 1146         read_newsrc(newsrc, TRUE);
 1147         tinrc.default_move_group = pos_num;
 1148         return (pos_num - 1);
 1149     } else {
 1150         tinrc.default_move_group = default_num + 1;
 1151         return default_num;
 1152     }
 1153 }
 1154 
 1155 
 1156 static void
 1157 catchup_group(
 1158     struct t_group *group,
 1159     t_bool goto_next_unread_group)
 1160 {
 1161     char *smsg = NULL;
 1162 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
 1163     wchar_t *wtmp;
 1164 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
 1165 
 1166     if ((!TINRC_CONFIRM_ACTION) || prompt_yn(sized_message(&smsg, _(txt_mark_group_read), group->name), TRUE) == 1) {
 1167         grp_mark_read(group, NULL);
 1168         {
 1169             char buf[LEN];
 1170             size_t i = 0;
 1171 
 1172             while (i < sel_fmt.len_ucnt)
 1173                 buf[i++] = ' ';
 1174             buf[i] = '\0';
 1175 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
 1176             if ((wtmp = char2wchar_t(buf))) {
 1177                 mark_screen(selmenu.curr, ucnt_offset, wtmp);
 1178                 free(wtmp);
 1179             }
 1180 #else
 1181             mark_screen(selmenu.curr, ucnt_offset, buf);
 1182 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
 1183         }
 1184 
 1185         if (goto_next_unread_group)
 1186             pos_next_unread_group(TRUE);
 1187         else
 1188             move_down();
 1189     }
 1190     FreeIfNeeded(smsg);
 1191 }
 1192 
 1193 
 1194 /*
 1195  * Set selmenu.curr to next group with unread arts
 1196  * If the current group has unread arts, it will be found first !
 1197  * If redraw is set, update the selection menu appropriately
 1198  * Return FALSE if no groups left to read
 1199  *        TRUE  at all other times
 1200  */
 1201 static t_bool
 1202 pos_next_unread_group(
 1203     t_bool redraw)
 1204 {
 1205     int i;
 1206     t_bool all_groups_read = TRUE;
 1207 
 1208     if (!selmenu.max)
 1209         return FALSE;
 1210 
 1211     for (i = selmenu.curr; i < selmenu.max; i++) {
 1212         if (UNREAD_GROUP(i)) {
 1213             all_groups_read = FALSE;
 1214             break;
 1215         }
 1216     }
 1217 
 1218     if (all_groups_read) {
 1219         for (i = 0; i < selmenu.curr; i++) {
 1220             if (UNREAD_GROUP(i)) {
 1221                 all_groups_read = FALSE;
 1222                 break;
 1223             }
 1224         }
 1225     }
 1226 
 1227     if (all_groups_read) {
 1228         info_message(_(txt_no_groups_to_read));
 1229         return FALSE;
 1230     }
 1231 
 1232     if (redraw)
 1233         move_to_item(i);
 1234     else
 1235         selmenu.curr = i;
 1236 
 1237     return TRUE;
 1238 }
 1239 
 1240 
 1241 /*
 1242  * This is the main loop that cycles through, reading groups.
 1243  * We keep going until we return to the selection screen or exit tin
 1244  */
 1245 static void
 1246 read_groups(
 1247     void)
 1248 {
 1249     t_bool done = FALSE;
 1250 
 1251     clear_message();
 1252 
 1253     while (!done) {     /* if xmin > xmax the newsservers active is broken */
 1254         switch (group_page(&CURR_GROUP)) {
 1255             case GRP_QUIT:
 1256                 select_quit();
 1257                 break;
 1258 
 1259             case GRP_NEXT:
 1260                 if (selmenu.curr + 1 < selmenu.max)
 1261                     selmenu.curr++;
 1262                 done = TRUE;
 1263                 break;
 1264 
 1265             case GRP_NEXTUNREAD:
 1266                 if (!pos_next_unread_group(FALSE))
 1267                     done = TRUE;
 1268                 break;
 1269 
 1270             case GRP_ENTER:     /* group_page() has already set selmenu.curr */
 1271                 break;
 1272 
 1273             case GRP_RETSELECT:
 1274             case GRP_EXIT:
 1275             default:
 1276                 done = TRUE;
 1277                 break;
 1278         }
 1279     }
 1280 
 1281     if (!need_reread_active_file())
 1282         show_selection_page();
 1283 }
 1284 
 1285 
 1286 /*
 1287  * Toggle my_group[] between all groups / only unread groups
 1288  * We make a special case for Newgroups (always appear, at the top)
 1289  * and Bogus groups if tinrc.strip_bogus = BOGUS_SHOW
 1290  */
 1291 void
 1292 toggle_my_groups(
 1293     const char *group)
 1294 {
 1295 #if 1
 1296     FILE *fp;
 1297     char buf[NEWSRC_LINE];
 1298     char *ptr;
 1299 #endif /* 1 */
 1300     int i;
 1301 
 1302     /*
 1303      * Save current or next group with unread arts for later use
 1304      */
 1305     if (selmenu.max) {
 1306         int old_curr_group_idx = 0;
 1307 
 1308         if (group != NULL) {
 1309             if ((i = my_group_find(group)) >= 0)
 1310                 old_curr_group_idx = i;
 1311         } else
 1312             old_curr_group_idx = (selmenu.curr == -1) ? 0 : selmenu.curr;
 1313 
 1314         if (tinrc.show_only_unread_groups) {
 1315             for (i = old_curr_group_idx; i < selmenu.max; i++) {
 1316                 if (UNREAD_GROUP(i) || active[my_group[i]].newgroup) {
 1317                     old_curr_group_idx = i;
 1318                     break;
 1319                 }
 1320             }
 1321         }
 1322         selmenu.curr = old_curr_group_idx;  /* Set current group to save */
 1323     } else
 1324         selmenu.curr = 0;
 1325 
 1326     save_restore_curr_group(TRUE);
 1327 
 1328     selmenu.max = skip_newgroups();         /* Reposition after any newgroups */
 1329 
 1330     /* TODO: why re-read .newsrc here, instead of something like this... */
 1331 #if 0
 1332     for_each_group(i) {
 1333         if (active[i].subscribed) {
 1334             if (tinrc.show_only_unread_groups) {
 1335                 if (active[i].newsrc.num_unread > 0 || (active[i].bogus && tinrc.strip_bogus == BOGUS_SHOW))
 1336                     my_group[selmenu.max++] = i;
 1337             } else
 1338                 my_group[selmenu.max++] = i;
 1339         }
 1340     }
 1341 #else
 1342     /* preserve group ordering based on newsrc */
 1343     if ((fp = fopen(newsrc, "r")) == NULL)
 1344         return;
 1345 
 1346     while (fgets(buf, (int) sizeof(buf), fp) != NULL) {
 1347         if ((ptr = strchr(buf, SUBSCRIBED)) != NULL) {
 1348             *ptr = '\0';
 1349 
 1350             if ((i = find_group_index(buf, FALSE)) < 0)
 1351                 continue;
 1352 
 1353             if (tinrc.show_only_unread_groups) {
 1354                 if (active[i].newsrc.num_unread || (active[i].bogus && tinrc.strip_bogus == BOGUS_SHOW))
 1355                     my_group_add(buf, FALSE);
 1356             } else
 1357                 my_group_add(buf, FALSE);
 1358         }
 1359     }
 1360     fclose(fp);
 1361 #endif /* 0 */
 1362     selmenu.curr = save_restore_curr_group(FALSE);          /* Restore saved group position */
 1363 }
 1364 
 1365 
 1366 /*
 1367  * Subscribe or unsubscribe from a list of groups. List can be full list as
 1368  * supported by match_group_list()
 1369  */
 1370 static void
 1371 subscribe_pattern(
 1372     const char *prompt,
 1373     const char *message,
 1374     const char *result,
 1375     t_bool state)
 1376 {
 1377     char buf[LEN];
 1378     int i, subscribe_num = 0;
 1379 
 1380     if (!num_active || no_write)
 1381         return;
 1382 
 1383     if (!prompt_string(prompt, buf, HIST_OTHER) || !*buf) {
 1384         clear_message();
 1385         return;
 1386     }
 1387 
 1388     wait_message(0, "%s", message);
 1389 
 1390     for_each_group(i) {
 1391         if (match_group_list(active[i].name, buf)) {
 1392             if (active[i].subscribed != (state != FALSE)) {
 1393                 spin_cursor();
 1394                 /* If found and group is not subscribed add it to end of my_group[]. */
 1395                 subscribe(&active[i], SUB_CHAR(state), TRUE);
 1396                 if (state) {
 1397                     my_group_add(active[i].name, FALSE);
 1398                     grp_mark_unread(&active[i]);
 1399                 }
 1400                 subscribe_num++;
 1401             }
 1402         }
 1403     }
 1404 
 1405     if (subscribe_num) {
 1406         toggle_my_groups(NULL);
 1407         show_selection_page();
 1408         info_message(result, subscribe_num);
 1409     } else
 1410         info_message(_(txt_no_match));
 1411 }
 1412 
 1413 
 1414 /*
 1415  * Does NOT return
 1416  */
 1417 static void
 1418 select_quit(
 1419     void)
 1420 {
 1421     write_config_file(local_config_file);
 1422     ClearScreen();
 1423     tin_done(EXIT_SUCCESS, NULL);   /* Tin END */
 1424 }
 1425 
 1426 
 1427 static void
 1428 select_done(
 1429     void)
 1430 {
 1431     if (!TINRC_CONFIRM_TO_QUIT || prompt_yn(_(txt_quit), TRUE) == 1)
 1432         select_quit();
 1433     if (!no_write && prompt_yn(_(txt_save_config), TRUE) == 1) {
 1434         write_config_file(local_config_file);
 1435         write_newsrc();
 1436     }
 1437     show_selection_page();
 1438 }
 1439 
 1440 
 1441 static void
 1442 select_read_group(
 1443     void)
 1444 {
 1445     struct t_group *currgrp;
 1446 
 1447     if (!selmenu.max || selmenu.curr == -1) {
 1448         info_message(_(txt_no_groups));
 1449         return;
 1450     }
 1451 
 1452     currgrp = &CURR_GROUP;
 1453 
 1454     if (currgrp->bogus) {
 1455         info_message(_(txt_not_exist));
 1456         return;
 1457     }
 1458 
 1459     if (currgrp->xmax > 0 && (currgrp->xmin <= currgrp->xmax))
 1460         read_groups();
 1461     else
 1462         info_message(_(txt_no_arts));
 1463 }
 1464 
 1465 
 1466 #ifdef NNTP_ABLE
 1467 /*
 1468  * Try to fetch articles Nesgroups-header via [X]HDR or XPAT.
 1469  */
 1470 static char *
 1471 lookup_msgid(
 1472     char *msgid)
 1473 {
 1474     if (read_news_via_nntp && !read_saved_news) {
 1475         if (!nntp_caps.hdr_cmd && !nntp_caps.xpat) {
 1476             info_message(_(txt_lookup_func_not_available));
 1477             return NULL;
 1478         }
 1479         if (msgid) {
 1480             char *ptr, *r = NULL;
 1481             static char *x;
 1482             char buf[NNTP_STRLEN];
 1483             int ret;
 1484 
 1485             if (nntp_caps.hdr_cmd) {
 1486                 snprintf(buf, sizeof(buf), "%s Newsgroups %s", nntp_caps.hdr_cmd, msgid);
 1487                 ret = new_nntp_command(buf, (nntp_caps.type == CAPABILITIES) ? OK_HDR : OK_HEAD, NULL, 0);
 1488 
 1489                 switch (ret) {
 1490                     case OK_HEAD:
 1491                     case OK_HDR:
 1492                         x = NULL;
 1493                         while ((ptr = tin_fgets(FAKE_NNTP_FP, FALSE)) != NULL) {
 1494 #       ifdef DEBUG
 1495                             if (debug & DEBUG_NNTP)
 1496                                 debug_print_file("NNTP", "<<<%s%s", logtime(), ptr);
 1497 #       endif /* DEBUG */
 1498 
 1499                             if (ret == OK_HEAD) { /* RFC 2980 ("%s %s", id, grp) */
 1500                                 if (!strncmp(ptr, msgid, strlen(msgid))) { /* INN, MPNews, Leafnode, Cnews nntpd */
 1501                                     r = ptr + strlen(msgid) + 1;
 1502                                 } else { /* DNEWS ("%d %s", num, grp) */
 1503                                     r = ptr;
 1504                                     while (*r && *r != ' ' && *r != '\t')
 1505                                         r++;
 1506                                     while (*r && (*r == ' ' || *r == '\t'))
 1507                                         r++;
 1508                                 }
 1509                             }
 1510 
 1511                             if (ret == OK_HDR) { /* RFC 3977 ("0 %s", grp) */
 1512                                 if (*ptr == '0' && (*(ptr + 1) == ' ' || *(ptr + 1) == '\t'))
 1513                                     r = ptr + 2;
 1514                             }
 1515 
 1516                             if (r) {
 1517                                 FreeIfNeeded(x);    /* only required on bogus multi responses, just to be safe */
 1518                                 x = my_strdup(r);
 1519                             }
 1520                         }
 1521 
 1522                         if (x)
 1523                             return x;
 1524 
 1525                         if (!r) {
 1526 #       ifdef DEBUG
 1527                                 if ((debug & DEBUG_NNTP) && verbose > 1)
 1528                                     debug_print_file("NNTP", "lookup_msgid(%s) response empty or not recognized", buf);
 1529 #       endif /* DEBUG */
 1530                                 if (!nntp_caps.xpat)
 1531                                     info_message(_(txt_lookup_func_not_available));
 1532                         }
 1533                         if (r || !nntp_caps.xpat)
 1534                             return NULL;
 1535                         break;
 1536 
 1537                     case ERR_NOART:
 1538                         info_message(_(txt_art_unavailable));
 1539                         return NULL;
 1540 
 1541                     default:
 1542                         if (!nntp_caps.xpat) { /* try only once */
 1543                             info_message(_(txt_lookup_func_not_available));
 1544                             return NULL;
 1545                         }
 1546                         break;
 1547                 }
 1548             }
 1549 
 1550             if (nntp_caps.xpat) {
 1551                 snprintf(buf, sizeof(buf), "XPAT Newsgroups %s *", msgid);
 1552                 ret = new_nntp_command(buf, OK_HEAD, NULL, 0);
 1553                 x = NULL;
 1554                 switch (ret) {
 1555                     case OK_HEAD:
 1556                         while ((ptr = tin_fgets(FAKE_NNTP_FP, FALSE)) != NULL) {
 1557 #       ifdef DEBUG
 1558                             if (debug & DEBUG_NNTP)
 1559                                 debug_print_file("NNTP", "<<<%s%s", logtime(), ptr);
 1560 #       endif /* DEBUG */
 1561                             if (!strncmp(ptr, msgid, strlen(msgid)))
 1562                                 r = ptr + strlen(msgid) + 1;
 1563 
 1564                             if (r) {
 1565                                 FreeIfNeeded(x); /* only required on bogus multi responses, just to be safe */
 1566                                 x = my_strdup(r);
 1567                             }
 1568                         }
 1569 
 1570                         if (x)
 1571                             return x;
 1572 
 1573                         if (!r) {
 1574 #       ifdef DEBUG
 1575                                 if ((debug & DEBUG_NNTP) && verbose > 1)
 1576                                     debug_print_file("NNTP", "lookup_msgid(%s) response empty or not recognized", buf);
 1577 #       endif /* DEBUG */
 1578                                 info_message(_(txt_lookup_func_not_available));
 1579                                 /* nntp_caps.xpat = FALSE; */ /* ? */
 1580                         }
 1581                         return NULL;
 1582 
 1583                     case ERR_NOART:
 1584                         info_message(_(txt_art_unavailable));
 1585                         return NULL;
 1586 
 1587                     default:
 1588                         nntp_caps.xpat = FALSE;
 1589                         break;
 1590                 }
 1591             }
 1592             info_message(_(txt_lookup_func_not_available));
 1593         }
 1594     } else
 1595         info_message("%s %s", _(txt_lookup_func_not_available), _(txt_lookup_func_not_nntp));
 1596 
 1597     return NULL;
 1598 }
 1599 
 1600 
 1601 /*
 1602  * Get a message ID for the 'L' command. Add <> if needed.
 1603  * Try to enter an appropriate group and display the referenced article.
 1604  * If no group from the Newsgroups:-header is available, display the
 1605  * contents of the header.
 1606  */
 1607 int
 1608 show_article_by_msgid(
 1609     char *messageid)
 1610 {
 1611     char id[NNTP_STRLEN];   /* still way too big; RFC 3977 3.6 & RFC 5536 3.1.3 limit Message-ID to max 250 octets */
 1612     char *idptr = NULL;
 1613     char *newsgroups = NULL;
 1614     int i, ret = 0;
 1615     struct t_article *art;
 1616     struct t_group *group = NULL;
 1617     struct t_group *tmp_group = NULL;
 1618     struct t_msgid *msgid = NULL;
 1619     t_bool tmp_cache_overview_files;
 1620     t_bool tmp_show_only_unread_arts;
 1621 
 1622     if (!(read_news_via_nntp && !read_saved_news)) {
 1623         return LOOKUP_UNAVAIL;
 1624     }
 1625 
 1626     if (messageid) {
 1627         idptr = messageid;
 1628         newsgroups = lookup_msgid(idptr);
 1629     } else {
 1630         if (prompt_string(_(txt_enter_message_id), id + 1, HIST_MESSAGE_ID) && id[1]) {
 1631             idptr = str_trim(id + 1);
 1632             if (id[1] != '<') {
 1633                 id[0] = '<';
 1634                 strcat(id, ">");
 1635                 idptr = id;
 1636             }
 1637             newsgroups = lookup_msgid(idptr);
 1638         }
 1639     }
 1640 
 1641     if (!newsgroups)
 1642         return LOOKUP_ART_UNAVAIL;
 1643 
 1644     if ((group = get_group_from_list(newsgroups)) == NULL) {
 1645         info_message(strchr(newsgroups, ',') ? _(txt_lookup_show_groups) : _(txt_lookup_show_group), newsgroups);
 1646         free(newsgroups);
 1647         return LOOKUP_FAILED;
 1648     }
 1649 
 1650     if (curr_group)
 1651         tmp_group = curr_group;
 1652     curr_group = group;
 1653     num_of_tagged_arts = 0;
 1654     range_active = FALSE;
 1655     last_resp = -1;
 1656     this_resp = -1;
 1657     tmp_cache_overview_files = tinrc.cache_overview_files;
 1658     tinrc.cache_overview_files = FALSE;
 1659     tmp_show_only_unread_arts = curr_group->attribute->show_only_unread_arts;
 1660     curr_group->attribute->show_only_unread_arts = FALSE;
 1661 
 1662     if (!index_group(group)) {
 1663         for_each_art(i) {
 1664             art = &arts[i];
 1665             FreeAndNull(art->refs);
 1666             FreeAndNull(art->msgid);
 1667         }
 1668         tin_errno = 0;
 1669         ret = LOOKUP_FAILED;
 1670     }
 1671 
 1672     if (!ret) {
 1673         grpmenu.first = 0;
 1674 
 1675         if (*idptr == '\0')
 1676             ret = LOOKUP_ART_UNAVAIL;
 1677 
 1678         if ((msgid = find_msgid(idptr)) == NULL)
 1679             ret = LOOKUP_ART_UNAVAIL;
 1680 
 1681         if (!ret && msgid->article == ART_UNAVAILABLE)
 1682             ret = LOOKUP_ART_UNAVAIL;
 1683 
 1684         if (!ret && which_thread(msgid->article) == -1)
 1685             ret = LOOKUP_NO_LAST;
 1686     }
 1687 
 1688     if (!ret) {
 1689         switch (show_page(group, msgid->article, NULL)) {
 1690             case GRP_QUIT:
 1691                 ret = LOOKUP_QUIT;
 1692                 break;
 1693 
 1694             default:
 1695                 break;
 1696         }
 1697     }
 1698 
 1699     free(newsgroups);
 1700     art_close(&pgart);
 1701     tinrc.cache_overview_files = tmp_cache_overview_files;
 1702     curr_group->attribute->show_only_unread_arts = CAST_BOOL(tmp_show_only_unread_arts);
 1703     if (tmp_group) {
 1704         curr_group = tmp_group;
 1705         if (!index_group(curr_group)) {
 1706             for_each_art(i) {
 1707                 art = &arts[i];
 1708                 FreeAndNull(art->refs);
 1709                 FreeAndNull(art->msgid);
 1710             }
 1711             curr_group = NULL;
 1712             tin_errno = 0;
 1713             ret = LOOKUP_FAILED;
 1714         }
 1715     } else
 1716         curr_group = NULL;
 1717 
 1718     this_resp = last_resp = -1;
 1719 
 1720     return ret;
 1721 }
 1722 
 1723 
 1724 /*
 1725  * Takes a list of newsgroups and determines if one of them is available.
 1726  */
 1727 static struct t_group *
 1728 get_group_from_list(
 1729     char *newsgroups)
 1730 {
 1731     char *ptr, *tr;
 1732     t_bool found = FALSE;
 1733     struct t_group *group = NULL;
 1734 
 1735     if (!newsgroups || (ptr = strtok(newsgroups, ",")) == NULL)
 1736         return NULL;
 1737 
 1738     /* find first available group of type news */
 1739     do {
 1740         tr = str_trim(ptr);
 1741         group = group_find(tr, TRUE);
 1742         if (group && group->type == GROUP_TYPE_NEWS)
 1743             found = TRUE;
 1744     } while (!found && (ptr = strtok(NULL, ",")) != NULL);
 1745 
 1746     return found ? group : NULL;
 1747 }
 1748 #endif /* NNTP_ABLE */