"Fossies" - the Fresh Open Source Software Archive

Member "tin-2.4.1/src/select.c" (12 Oct 2016, 40909 Bytes) of archive /linux/misc/tin-2.4.1.tar.gz:


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

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