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