"Fossies" - the Fresh Open Source Software Archive

Member "tin-2.6.2/src/select.c" (9 Dec 2022, 44140 Bytes) of package /linux/misc/tin-2.6.2.tar.xz:


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

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