"Fossies" - the Fresh Open Source Software Archive

Member "tin-2.4.4/src/select.c" (20 Nov 2019, 41125 Bytes) of package /linux/misc/tin-2.4.4.tar.xz:


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

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