"Fossies" - the Fresh Open Source Software Archive 
Member "tin-2.6.1/src/group.c" (22 Dec 2021, 48640 Bytes) of package /linux/misc/tin-2.6.1.tar.xz:
As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style:
standard) with prefixed line numbers and
code folding option.
Alternatively you can here
view or
download the uninterpreted source code file.
For more information about "group.c" see the
Fossies "Dox" file reference documentation and the latest
Fossies "Diffs" side-by-side code changes report:
2.6.0_vs_2.6.1.
1 /*
2 * Project : tin - a Usenet reader
3 * Module : group.c
4 * Author : I. Lea & R. Skrenta
5 * Created : 1991-04-01
6 * Updated : 2021-07-25
7 * Notes :
8 *
9 * Copyright (c) 1991-2022 Iain Lea <iain@bricbrac.de>, Rich Skrenta <skrenta@pbm.com>
10 * All rights reserved.
11 *
12 * Redistribution and use in source and binary forms, with or without
13 * modification, are permitted provided that the following conditions
14 * are met:
15 *
16 * 1. Redistributions of source code must retain the above copyright notice,
17 * this list of conditions and the following disclaimer.
18 *
19 * 2. Redistributions in binary form must reproduce the above copyright
20 * notice, this list of conditions and the following disclaimer in the
21 * documentation and/or other materials provided with the distribution.
22 *
23 * 3. Neither the name of the copyright holder nor the names of its
24 * contributors may be used to endorse or promote products derived from
25 * this software without specific prior written permission.
26 *
27 * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
28 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
29 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
30 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
31 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
32 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
33 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
34 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
35 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
36 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
37 * POSSIBILITY OF SUCH DAMAGE.
38 */
39
40
41 #ifndef TIN_H
42 # include "tin.h"
43 #endif /* !TIN_H */
44 #ifndef TCURSES_H
45 # include "tcurses.h"
46 #endif /* !TCURSES_H */
47
48
49 /*
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_TOGGLE_HELP_DISPLAY: /* toggle mini help menu */
463 toggle_mini_help(GROUP_LEVEL);
464 show_group_page();
465 break;
466
467 case GLOBAL_TOGGLE_INVERSE_VIDEO: /* toggle inverse video */
468 toggle_inverse_video();
469 show_group_page();
470 show_inverse_video_status();
471 break;
472
473 #ifdef HAVE_COLOR
474 case GLOBAL_TOGGLE_COLOR:
475 if (toggle_color()) {
476 show_group_page();
477 show_color_status();
478 }
479 break;
480 #endif /* HAVE_COLOR */
481
482 case GROUP_MARK_THREAD_READ: /* mark current thread/range/tagged threads as read */
483 case MARK_THREAD_UNREAD: /* or unread */
484 if (grpmenu.curr < 0)
485 info_message(_(txt_no_arts));
486 else {
487 t_function function, type;
488
489 function = func == GROUP_MARK_THREAD_READ ? (t_function) FEED_MARK_READ : (t_function) FEED_MARK_UNREAD;
490 type = range_active ? FEED_RANGE : (num_of_tagged_arts && !group->attribute->mark_ignore_tags) ? NOT_ASSIGNED : FEED_THREAD;
491 feed_articles(function, GROUP_LEVEL, type, group, (int) base[grpmenu.curr]);
492 }
493 break;
494
495 case GROUP_LIST_THREAD: /* list articles within current thread */
496 ret_code = enter_thread(0, NULL); /* Enter thread at the top */
497 break;
498
499 case GLOBAL_LOOKUP_MESSAGEID:
500 if ((i = prompt_msgid()) != ART_UNAVAILABLE)
501 ret_code = enter_pager(i, FALSE);
502 break;
503
504 case GLOBAL_OPTION_MENU: /* option menu */
505 old_artnum = grpmenu.max > 0 ? arts[(int) base[grpmenu.curr]].artnum : T_ARTNUM_CONST(-1);
506 config_page(group->name, signal_context);
507 grpmenu.curr = old_artnum >= T_ARTNUM_CONST(0) ? find_new_pos(old_artnum, grpmenu.curr) : grpmenu.max - 1;
508 show_group_page();
509 break;
510
511 case GROUP_NEXT_GROUP: /* goto next group */
512 clear_message();
513 if (selmenu.curr + 1 >= selmenu.max)
514 info_message(_(txt_no_more_groups));
515 else {
516 if (xflag && TINRC_CONFIRM_SELECT && (prompt_yn(_(txt_confirm_select_on_exit), FALSE) != 1)) {
517 undo_auto_select_arts();
518 xflag = FALSE;
519 }
520 selmenu.curr++;
521 ret_code = GRP_NEXTUNREAD;
522 }
523 break;
524
525 case GROUP_NEXT_UNREAD_ARTICLE: /* goto next unread article */
526 if (grpmenu.curr < 0) {
527 info_message(_(txt_no_next_unread_art));
528 break;
529 }
530 if ((n = next_unread((int) base[grpmenu.curr])) == -1)
531 info_message(_(txt_no_next_unread_art));
532 else
533 ret_code = enter_pager(n, FALSE);
534 break;
535
536 case GROUP_PREVIOUS_UNREAD_ARTICLE: /* go to previous unread article */
537 if (grpmenu.curr < 0) {
538 info_message(_(txt_no_prev_unread_art));
539 break;
540 }
541
542 if ((n = prev_unread(prev_response((int) base[grpmenu.curr]))) == -1)
543 info_message(_(txt_no_prev_unread_art));
544 else
545 ret_code = enter_pager(n, FALSE);
546 break;
547
548 case GROUP_PREVIOUS_GROUP: /* previous group */
549 clear_message();
550 for (i = selmenu.curr - 1; i >= 0; i--) {
551 if (UNREAD_GROUP(i))
552 break;
553 }
554 if (i < 0)
555 info_message(_(txt_no_prev_group));
556 else {
557 if (xflag && TINRC_CONFIRM_SELECT && (prompt_yn(_(txt_confirm_select_on_exit), FALSE) != 1)) {
558 undo_auto_select_arts();
559 xflag = FALSE;
560 }
561 selmenu.curr = i;
562 ret_code = GRP_NEXTUNREAD;
563 }
564 break;
565
566 case GLOBAL_QUIT: /* return to group selection page */
567 if (num_of_tagged_arts && prompt_yn(_(txt_quit_despite_tags), TRUE) != 1)
568 break;
569 if (xflag && TINRC_CONFIRM_SELECT && (prompt_yn(_(txt_confirm_select_on_exit), FALSE) != 1)) {
570 undo_auto_select_arts();
571 xflag = FALSE;
572 }
573 ret_code = GRP_EXIT;
574 break;
575
576 case GLOBAL_QUIT_TIN: /* quit */
577 if (num_of_tagged_arts && prompt_yn(_(txt_quit_despite_tags), TRUE) != 1)
578 break;
579 if (xflag && TINRC_CONFIRM_SELECT && (prompt_yn(_(txt_confirm_select_on_exit), FALSE) != 1)) {
580 undo_auto_select_arts();
581 xflag = FALSE;
582 }
583 ret_code = GRP_QUIT;
584 break;
585
586 case GROUP_TOGGLE_READ_UNREAD:
587 toggle_read_unread(FALSE);
588 show_group_page();
589 break;
590
591 case GROUP_TOGGLE_GET_ARTICLES_LIMIT:
592 if (prompt_getart_limit()) {
593 /*
594 * if getart limit was given via cmd-line
595 * make it inactive now in order to use
596 * tinrc.getart_limit
597 */
598 if (cmdline.args & CMDLINE_GETART_LIMIT)
599 cmdline.args &= ~CMDLINE_GETART_LIMIT;
600 ret_code = GRP_NEXTUNREAD;
601 }
602 break;
603
604 case GLOBAL_BUGREPORT:
605 bug_report();
606 break;
607
608 case GROUP_TAG_PARTS: /* tag all in order */
609 if (0 <= grpmenu.curr) {
610 int old_num = num_of_tagged_arts;
611
612 if (tag_multipart((int) base[grpmenu.curr]) != 0) {
613 /*
614 * on success, move the pointer to the next
615 * untagged article just for ease of use's sake
616 */
617 n = grpmenu.curr;
618 update_group_page();
619 do {
620 n++;
621 n %= grpmenu.max;
622 if (arts[base[n]].tagged == 0) {
623 move_to_item(n);
624 break;
625 }
626 } while (n != grpmenu.curr);
627 if (old_num < num_of_tagged_arts)
628 info_message(_(txt_info_all_parts_tagged));
629 else
630 info_message(_(txt_info_all_parts_untagged));
631 }
632 }
633 break;
634
635 case GROUP_TAG: /* tag/untag threads for mailing/piping/printing/saving */
636 if (grpmenu.curr >= 0) {
637 t_bool tagged = TRUE;
638
639 n = (int) base[grpmenu.curr];
640
641 /*
642 * This loop looks for any article in the thread that
643 * isn't already tagged.
644 */
645 for (ii = n; ii != -1; ii = arts[ii].thread) {
646 if (arts[ii].tagged == 0) {
647 tagged = FALSE;
648 break;
649 }
650 }
651
652 /*
653 * If the whole thread is tagged, untag it. Otherwise, tag
654 * any untagged articles
655 */
656 if (tagged) {
657 /*
658 * Here we repeat the tagged test in both blocks
659 * to leave the choice of tagged/untagged
660 * determination politic in the previous lines.
661 */
662 for (ii = n; ii != -1; ii = arts[ii].thread) {
663 if (arts[ii].tagged != 0) {
664 tagged = TRUE;
665 untag_article(ii);
666 }
667 }
668 } else {
669 for (ii = n; ii != -1; ii = arts[ii].thread) {
670 if (arts[ii].tagged == 0)
671 arts[ii].tagged = ++num_of_tagged_arts;
672 }
673 }
674 if ((ii = line_is_tagged(n))) {
675 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
676 if ((wtmp = char2wchar_t(tin_ltoa(ii, 3)))) {
677 mark_screen(grpmenu.curr, mark_offset - (3 - art_mark_width), wtmp);
678 free(wtmp);
679 }
680 #else
681 mark_screen(grpmenu.curr, mark_offset - 2, tin_ltoa(ii, 3));
682 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
683 } else {
684 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
685 wchar_t mark[] = { L'\0', L'\0' };
686 #else
687 char mark[] = { '\0', '\0' };
688 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
689
690 stat_thread(grpmenu.curr, &sbuf);
691 mark[0] = sbuf.art_mark;
692 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
693 mark_screen(grpmenu.curr, mark_offset - (3 - art_mark_width), L" "); /* clear space used by tag numbering */
694 mark_screen(grpmenu.curr, mark_offset + (art_mark_width - wcwidth(mark[0])), mark);
695 #else
696 mark_screen(grpmenu.curr, mark_offset - 2, " "); /* clear space used by tag numbering */
697 mark_screen(grpmenu.curr, mark_offset, mark);
698 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
699 }
700 if (tagged)
701 show_tagged_lines();
702
703 if (grpmenu.curr + 1 < grpmenu.max)
704 move_down();
705 else
706 draw_subject_arrow();
707
708 info_message(tagged ? _(txt_prefix_untagged) : _(txt_prefix_tagged), txt_thread_singular);
709
710 }
711 break;
712
713 case GROUP_TOGGLE_THREADING: /* Cycle through the threading types */
714 group->attribute->thread_articles = CAST_BITS((group->attribute->thread_articles + 1) % (THREAD_MAX + 1), thread_articles);
715 if (grpmenu.curr >= 0) {
716 i = (int) base[grpmenu.curr]; /* Save a copy of current thread */
717 make_threads(group, TRUE);
718 find_base(group);
719 if ((grpmenu.curr = which_thread(i)) < 0) /* Restore current position in group */
720 grpmenu.curr = 0;
721 }
722 show_group_page();
723 break;
724
725 case GROUP_UNTAG: /* untag all articles */
726 if (grpmenu.curr >= 0) {
727 if (untag_all_articles())
728 update_group_page();
729 }
730 break;
731
732 case GLOBAL_VERSION:
733 info_message(cvers);
734 break;
735
736 case GLOBAL_POST: /* post an article */
737 if (post_article(group->name))
738 show_group_page();
739 break;
740
741 case GLOBAL_POSTPONED: /* post postponed article */
742 if (can_post) {
743 if (pickup_postponed_articles(FALSE, FALSE))
744 show_group_page();
745 } else
746 info_message(_(txt_cannot_post));
747 break;
748
749 case GLOBAL_DISPLAY_POST_HISTORY: /* display messages posted by user */
750 if (post_hist_page()) {
751 if (grpmenu.curr == -1 && grpmenu.max > 0)
752 grpmenu.curr = 0;
753 show_group_page();
754 }
755 break;
756
757 case MARK_ARTICLE_UNREAD: /* mark base article of thread unread */
758 if (grpmenu.curr < 0)
759 info_message(_(txt_no_arts));
760 else {
761 const char *ptr;
762
763 if (range_active) {
764 /*
765 * We are tied to following base[] here, not arts[], as we operate on
766 * the base articles by definition.
767 */
768 for (ii = 0; ii < grpmenu.max; ++ii) {
769 if (arts[base[ii]].inrange) {
770 arts[base[ii]].inrange = FALSE;
771 art_mark(group, &arts[base[ii]], ART_WILL_RETURN);
772 for_each_art_in_thread(i, ii)
773 arts[i].inrange = FALSE;
774 }
775 }
776 range_active = FALSE;
777 show_group_page();
778 ptr = _(txt_base_article_range);
779 } else {
780 art_mark(group, &arts[base[grpmenu.curr]], ART_WILL_RETURN);
781 ptr = _(txt_base_article);
782 }
783
784 show_group_title(TRUE);
785 build_sline(grpmenu.curr);
786 draw_subject_arrow();
787 info_message(_(txt_marked_as_unread), ptr);
788 }
789 break;
790
791 case MARK_FEED_READ: /* mark selected articles as read */
792 if (grpmenu.curr >= 0)
793 feed_articles(FEED_MARK_READ, GROUP_LEVEL, NOT_ASSIGNED, group, (int) base[grpmenu.curr]);
794 break;
795
796 case MARK_FEED_UNREAD: /* mark selected articles as unread */
797 if (grpmenu.curr >= 0)
798 feed_articles(FEED_MARK_UNREAD, GROUP_LEVEL, NOT_ASSIGNED, group, (int) base[grpmenu.curr]);
799 break;
800
801 case GROUP_SELECT_THREAD: /* mark thread as selected */
802 case GROUP_TOGGLE_SELECT_THREAD: /* toggle thread */
803 if (grpmenu.curr < 0) {
804 info_message(_(txt_no_arts));
805 break;
806 }
807
808 flag = TRUE;
809 if (func == GROUP_TOGGLE_SELECT_THREAD) {
810 stat_thread(grpmenu.curr, &sbuf);
811 if (sbuf.selected_unread == sbuf.unread)
812 flag = FALSE;
813 }
814 n = 0;
815 for_each_art_in_thread(i, grpmenu.curr) {
816 arts[i].selected = CAST_BOOL(flag);
817 ++n;
818 }
819 assert(n > 0);
820 {
821 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
822 wchar_t mark[] = { L'\0', L'\0' };
823 #else
824 char mark[] = { '\0', '\0' };
825 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
826
827 stat_thread(grpmenu.curr, &sbuf);
828 mark[0] = sbuf.art_mark;
829 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
830 mark_screen(grpmenu.curr, mark_offset + (art_mark_width - wcwidth(mark[0])), mark);
831 #else
832 mark_screen(grpmenu.curr, mark_offset, mark);
833 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
834 }
835
836 show_group_title(TRUE);
837
838 if (grpmenu.curr + 1 < grpmenu.max)
839 move_down();
840 else
841 draw_subject_arrow();
842
843 info_message(flag ? _(txt_thread_marked_as_selected) : _(txt_thread_marked_as_deselected));
844 break;
845
846 case GROUP_REVERSE_SELECTIONS: /* reverse selections */
847 for_each_art(i)
848 arts[i].selected = bool_not(arts[i].selected);
849 update_group_page();
850 show_group_title(TRUE);
851 break;
852
853 case GROUP_UNDO_SELECTIONS: /* undo selections */
854 undo_selections();
855 xflag = FALSE;
856 show_group_title(TRUE);
857 update_group_page();
858 break;
859
860 case GROUP_SELECT_PATTERN: /* select matching patterns */
861 if (grpmenu.curr >= 0) {
862 char pat[128];
863 char *prompt;
864 struct regex_cache cache = { NULL, NULL };
865
866 prompt = fmt_string(_(txt_select_pattern), tinrc.default_select_pattern);
867 if (!(prompt_string_default(prompt, tinrc.default_select_pattern, _(txt_info_no_previous_expression), HIST_SELECT_PATTERN))) {
868 free(prompt);
869 break;
870 }
871 free(prompt);
872
873 if (STRCMPEQ(tinrc.default_select_pattern, "*")) { /* all */
874 if (tinrc.wildcard)
875 STRCPY(pat, ".*");
876 else
877 STRCPY(pat, tinrc.default_select_pattern);
878 } else
879 snprintf(pat, sizeof(pat), REGEX_FMT, tinrc.default_select_pattern);
880
881 if (tinrc.wildcard && !(compile_regex(pat, &cache, PCRE_CASELESS)))
882 break;
883
884 flag = FALSE;
885 for (n = 0; n < grpmenu.max; n++) {
886 if (!match_regex(arts[base[n]].subject, pat, &cache, TRUE))
887 continue;
888
889 for_each_art_in_thread(i, n)
890 arts[i].selected = TRUE;
891
892 flag = TRUE;
893 }
894 if (flag) {
895 show_group_title(TRUE);
896 update_group_page();
897 }
898 if (tinrc.wildcard) {
899 FreeIfNeeded(cache.re);
900 FreeIfNeeded(cache.extra);
901 }
902 }
903 break;
904
905 case GROUP_SELECT_THREAD_IF_UNREAD_SELECTED: /* select all unread arts in thread hot if 1 is hot */
906 for (n = 0; n < grpmenu.max; n++) {
907 stat_thread(n, &sbuf);
908 if (!sbuf.selected_unread || sbuf.selected_unread == sbuf.unread)
909 continue;
910
911 for_each_art_in_thread(i, n)
912 arts[i].selected = TRUE;
913 }
914 show_group_title(TRUE);
915 break;
916
917 case GROUP_MARK_UNSELECTED_ARTICLES_READ: /* mark read all unselected arts */
918 if (!xflag) {
919 do_auto_select_arts();
920 xflag = TRUE;
921 } else {
922 undo_auto_select_arts();
923 xflag = FALSE;
924 }
925 break;
926
927 case GROUP_DO_AUTOSELECT: /* perform auto-selection on group */
928 for (n = 0; n < grpmenu.max; n++) {
929 for_each_art_in_thread(i, n)
930 arts[i].selected = TRUE;
931 }
932 update_group_page();
933 show_group_title(TRUE);
934 break;
935
936 case GLOBAL_TOGGLE_INFO_LAST_LINE:
937 tinrc.info_in_last_line = bool_not(tinrc.info_in_last_line);
938 show_group_page();
939 break;
940
941 default:
942 info_message(_(txt_bad_command), PrintFuncKey(key, GLOBAL_HELP, group_keys));
943 break;
944 } /* switch(ch) */
945 } /* ret_code >= 0 */
946
947 set_xclick_off();
948
949 clear_note_area();
950 grp_del_mail_arts(group);
951
952 art_close(&pgart); /* Close any open art */
953
954 curr_group = NULL;
955
956 return ret_code;
957 }
958
959
960 void
961 show_group_page(
962 void)
963 {
964 int i;
965
966 signal_context = cGroup;
967 currmenu = &grpmenu;
968
969 ClearScreen();
970 set_first_screen_item();
971 parse_format_string(curr_group->attribute->group_format, &grp_fmt);
972 mark_offset = 0;
973 show_group_title(FALSE);
974
975 for (i = grpmenu.first; i < grpmenu.first + NOTESLINES && i < grpmenu.max; ++i)
976 build_sline(i);
977
978 show_mini_help(GROUP_LEVEL);
979
980 if (grpmenu.max <= 0) {
981 info_message(_(txt_no_arts));
982 return;
983 }
984
985 draw_subject_arrow();
986 }
987
988
989 static void
990 update_group_page(
991 void)
992 {
993 int i, j;
994 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
995 wchar_t mark[] = { L'\0', L'\0' };
996 wchar_t *wtmp;
997 #else
998 char mark[] = { '\0', '\0' };
999 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
1000 struct t_art_stat sbuf;
1001
1002 for (i = grpmenu.first; i < grpmenu.first + NOTESLINES && i < grpmenu.max; ++i) {
1003 if ((j = line_is_tagged((int) base[i]))) {
1004 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
1005 if ((wtmp = char2wchar_t(tin_ltoa(j, 3)))) {
1006 mark_screen(i, mark_offset - (3 - art_mark_width), wtmp);
1007 free(wtmp);
1008 }
1009 #else
1010 mark_screen(i, mark_offset - 2, tin_ltoa(j, 3));
1011 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
1012 } else {
1013 stat_thread(i, &sbuf);
1014 mark[0] = sbuf.art_mark;
1015 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
1016 mark_screen(i, mark_offset - (3 - art_mark_width), L" "); /* clear space used by tag numbering */
1017 mark_screen(i, mark_offset + (art_mark_width - wcwidth(mark[0])), mark);
1018 #else
1019 mark_screen(i, mark_offset - 2, " "); /* clear space used by tag numbering */
1020 mark_screen(i, mark_offset, mark);
1021 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
1022 if (sbuf.art_mark == tinrc.art_marked_selected)
1023 draw_mark_selected(i);
1024 }
1025 }
1026
1027 if (grpmenu.max <= 0)
1028 return;
1029
1030 draw_subject_arrow();
1031 }
1032
1033
1034 static void
1035 draw_subject_arrow(
1036 void)
1037 {
1038 draw_arrow_mark(INDEX_TOP + grpmenu.curr - grpmenu.first);
1039
1040 if (tinrc.info_in_last_line) {
1041 struct t_art_stat statbuf;
1042
1043 stat_thread(grpmenu.curr, &statbuf);
1044 info_message("%s", arts[(statbuf.unread ? next_unread((int) base[grpmenu.curr]) : base[grpmenu.curr])].subject);
1045 } else if (grpmenu.curr == grpmenu.max - 1)
1046 info_message(_(txt_end_of_arts));
1047 }
1048
1049
1050 void
1051 clear_note_area(
1052 void)
1053 {
1054 MoveCursor(INDEX_TOP, 0);
1055 CleartoEOS();
1056 }
1057
1058
1059 /*
1060 * If in show_only_unread_arts mode or there are unread articles we know this
1061 * thread will exist after toggle. Otherwise we find the next closest to
1062 * return to. 'force' can be set to force tin to show all messages
1063 */
1064 static void
1065 toggle_read_unread(
1066 t_bool force)
1067 {
1068 int n, i = -1;
1069
1070 /*
1071 * Clear art->keep_in_base if switching to !show_only_unread_arts
1072 */
1073 if (curr_group->attribute->show_only_unread_arts) {
1074 for_each_art(n)
1075 arts[n].keep_in_base = FALSE;
1076 }
1077
1078 /* force currently is always false */
1079 if (force)
1080 curr_group->attribute->show_only_unread_arts = TRUE; /* Yes - really, we change it in a bit */
1081
1082 wait_message(0, _(txt_reading_arts),
1083 (curr_group->attribute->show_only_unread_arts) ? _(txt_all) : _(txt_unread));
1084
1085 if (grpmenu.curr >= 0) {
1086 if (curr_group->attribute->show_only_unread_arts || new_responses(grpmenu.curr))
1087 i = (int) base[grpmenu.curr];
1088 else if ((n = prev_unread((int) base[grpmenu.curr])) >= 0)
1089 i = n;
1090 else if ((n = next_unread((int) base[grpmenu.curr])) >= 0)
1091 i = n;
1092 }
1093
1094 if (!force)
1095 curr_group->attribute->show_only_unread_arts = bool_not(curr_group->attribute->show_only_unread_arts);
1096
1097 find_base(curr_group);
1098 if (i >= 0 && (n = which_thread(i)) >= 0)
1099 grpmenu.curr = n;
1100 else if (grpmenu.max > 0)
1101 grpmenu.curr = grpmenu.max - 1;
1102 clear_message();
1103 }
1104
1105
1106 /*
1107 * Find new index position after a kill or unkill. Because kill can work on
1108 * author it is impossible to know which, if any, articles will be left
1109 * afterwards. So we make a "best attempt" to find a new index point.
1110 */
1111 static int
1112 find_new_pos(
1113 long old_artnum,
1114 int cur_pos)
1115 {
1116 int i, pos;
1117
1118 if ((i = find_artnum(old_artnum)) >= 0 && (pos = which_thread(i)) >= 0)
1119 return pos;
1120
1121 return ((cur_pos < grpmenu.max) ? cur_pos : (grpmenu.max - 1));
1122 }
1123
1124
1125 /*
1126 * Set grpmenu.curr to the first unread or the last thread depending on
1127 * the value of pos_first_unread
1128 */
1129 void
1130 pos_first_unread_thread(
1131 void)
1132 {
1133 int i;
1134
1135 if (curr_group->attribute->pos_first_unread) {
1136 for (i = 0; i < grpmenu.max; i++) {
1137 if (new_responses(i))
1138 break;
1139 }
1140 grpmenu.curr = ((i < grpmenu.max) ? i : (grpmenu.max - 1));
1141 } else
1142 grpmenu.curr = grpmenu.max - 1;
1143 }
1144
1145
1146 void
1147 mark_screen(
1148 int screen_row,
1149 int screen_col,
1150 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
1151 const wchar_t *value)
1152 #else
1153 const char *value)
1154 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
1155 {
1156
1157 if (tinrc.draw_arrow) {
1158 MoveCursor(INDEX2LNUM(screen_row), screen_col);
1159 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
1160 my_fputws(value, stdout);
1161 #else
1162 my_fputs(value, stdout);
1163 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
1164 stow_cursor();
1165 my_flush();
1166 } else {
1167 #ifdef USE_CURSES
1168 # if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
1169 char *tmp;
1170 # endif /* MULTIBYTE_ABLE && !NO_LOCALE */
1171 int y, x;
1172
1173 getyx(stdscr, y, x);
1174 # if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
1175 if ((tmp = wchar_t2char(value))) {
1176 mvaddstr(INDEX2LNUM(screen_row), screen_col, tmp);
1177 free(tmp);
1178 }
1179 # else
1180 mvaddstr(INDEX2LNUM(screen_row), screen_col, value);
1181 # endif /* MULTIBYTE_ABLE && !NO_LOCALE */
1182 MoveCursor(y, x);
1183 #else
1184 int i;
1185
1186 for (i = 0; value[i] != '\0'; i++)
1187 screen[INDEX2SNUM(screen_row)].col[screen_col + i] = value[i];
1188
1189 MoveCursor(INDEX2LNUM(screen_row), screen_col);
1190 # if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
1191 my_fputws(value, stdout);
1192 # else
1193 my_fputs(value, stdout);
1194 # endif /* MULTIBYTE_ABLE && !NO_LOCALE */
1195 #endif /* USE_CURSES */
1196 currmenu->draw_arrow();
1197 }
1198 }
1199
1200
1201 /*
1202 * Builds the correct header for multipart messages when sorting via
1203 * THREAD_MULTI.
1204 */
1205 static void
1206 build_multipart_header(
1207 char *dest,
1208 int maxlen,
1209 const char *src,
1210 int cmplen,
1211 int have,
1212 int total)
1213 {
1214 const char *mark = (have == total) ? "*" : "-";
1215 char *ss;
1216
1217 if (cmplen > maxlen)
1218 strncpy(dest, src, (size_t) maxlen);
1219 else {
1220 strncpy(dest, src, (size_t) cmplen);
1221 ss = dest + cmplen;
1222 snprintf(ss, (size_t) (maxlen - cmplen), "(%s/%d)", mark, total);
1223 }
1224 }
1225
1226
1227 /*
1228 * Build subject line given an index into base[].
1229 *
1230 * WARNING: some other code expects to find the article mark (ART_MARK_READ,
1231 * ART_MARK_SELECTED, etc) at mark_offset from beginning of the line.
1232 * So, if you change the format used in this routine, be sure to check that
1233 * the value of mark_offset is still correct.
1234 * Yes, this is somewhat kludgy.
1235 */
1236 static void
1237 build_sline(
1238 int i)
1239 {
1240 char *fmt, *buf;
1241 char *buffer;
1242 char arts_sub[HEADER_LEN];
1243 char tmp_buf[8];
1244 char tmp[LEN];
1245 int respnum;
1246 int j, k, n;
1247 size_t len;
1248 struct t_art_stat sbuf;
1249 t_bool tagged = FALSE;
1250 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
1251 wchar_t *wtmp, *wtmp2;
1252 #else
1253 int fill, gap;
1254 size_t len_start;
1255 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
1256
1257 #ifdef USE_CURSES
1258 /*
1259 * Allocate line buffer
1260 * make it the same size like in !USE_CURSES case to simplify the code
1261 */
1262 # if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
1263 buffer = my_malloc(cCOLS * MB_CUR_MAX + 2);
1264 # else
1265 buffer = my_malloc(cCOLS + 2);
1266 # endif /* MULTIBYTE_ABLE && !NO_LOCALE */
1267 #else
1268 buffer = screen[INDEX2SNUM(i)].col;
1269 #endif /* USE_CURSES */
1270
1271 buffer[0] = '\0';
1272
1273 respnum = (int) base[i];
1274
1275 stat_thread(i, &sbuf);
1276
1277 /*
1278 * Find index of first unread in this thread
1279 */
1280 j = (sbuf.unread) ? next_unread(respnum) : respnum;
1281
1282 fmt = grp_fmt.str;
1283
1284 if (tinrc.draw_arrow)
1285 strcat(buffer, " ");
1286
1287 for (; *fmt; fmt++) {
1288 if (*fmt != '%') {
1289 strncat(buffer, fmt, 1);
1290 continue;
1291 }
1292 switch (*++fmt) {
1293 case '\0':
1294 break;
1295
1296 case '%':
1297 strncat(buffer, fmt, 1);
1298 break;
1299
1300 case 'D': /* date */
1301 buf = my_malloc(LEN);
1302 if (my_strftime(buf, LEN - 1, grp_fmt.date_str, localtime((const time_t *) &arts[j].date))) {
1303 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
1304 if ((wtmp = char2wchar_t(buf)) != NULL) {
1305 wtmp2 = wcspart(wtmp, (int) grp_fmt.len_date_max, TRUE);
1306 if (wcstombs(tmp, wtmp2, sizeof(tmp) - 1) != (size_t) -1)
1307 strcat(buffer, tmp);
1308
1309 free(wtmp);
1310 free(wtmp2);
1311 }
1312 #else
1313 strncat(buffer, buf, grp_fmt.len_date_max);
1314 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
1315 }
1316 free(buf);
1317 break;
1318
1319 case 'F': /* from */
1320 if (curr_group->attribute->show_author != SHOW_FROM_NONE) {
1321 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
1322 get_author(FALSE, &arts[j], tmp, sizeof(tmp) - 1);
1323
1324 if ((wtmp = char2wchar_t(tmp)) != NULL) {
1325 wtmp2 = wcspart(wtmp, (int) grp_fmt.len_from, TRUE);
1326 if (wcstombs(tmp, wtmp2, sizeof(tmp) - 1) != (size_t) -1)
1327 strcat(buffer, tmp);
1328
1329 free(wtmp);
1330 free(wtmp2);
1331 }
1332 #else
1333 len_start = strwidth(buffer);
1334 get_author(FALSE, &arts[j], buffer + strlen(buffer), grp_fmt.len_from);
1335 fill = grp_fmt.len_from - (strwidth(buffer) - len_start);
1336 gap = strlen(buffer);
1337 for (k = 0; k < fill; k++)
1338 buffer[gap + k] = ' ';
1339 buffer[gap + fill] = '\0';
1340 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
1341 }
1342 break;
1343
1344 case 'I': /* initials */
1345 len = MIN(grp_fmt.len_initials, sizeof(tmp) - 1);
1346 get_initials(&arts[j], tmp, (int) len);
1347 strcat(buffer, tmp);
1348 if ((k = (int) (len - (size_t) strwidth(tmp))) > 0) {
1349 buf = buffer + strlen(buffer);
1350 for (; k > 0; --k)
1351 *buf++ = ' ';
1352 *buf = '\0';
1353 }
1354 break;
1355
1356 case 'L': /* lines */
1357 if (arts[j].line_count != -1)
1358 strcat(buffer, tin_ltoa(arts[j].line_count, (int) grp_fmt.len_linecnt));
1359 else {
1360 buf = buffer + strlen(buffer);
1361 for (k = (int) grp_fmt.len_linecnt; k > 1; --k)
1362 *buf++ = ' ';
1363 *buf++ = '?';
1364 *buf = '\0';
1365 }
1366 break;
1367
1368 case 'm': /* article flags, tag number, or whatever */
1369 if (!grp_fmt.mark_offset)
1370 grp_fmt.mark_offset = (size_t) (mark_offset = strwidth(buffer) + 2);
1371 if ((k = line_is_tagged(respnum))) {
1372 STRCPY(tmp_buf, tin_ltoa(k, 3));
1373 strcat(buffer, " ");
1374 tagged = TRUE;
1375 } else
1376 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
1377 snprintf(tmp_buf, sizeof(tmp_buf), "%s%lc", art_mark_width > wcwidth(sbuf.art_mark) ? " " : " ", sbuf.art_mark);
1378 #else
1379 snprintf(tmp_buf, sizeof(tmp_buf), " %c", sbuf.art_mark);
1380 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
1381 strcat(buffer, tmp_buf);
1382 break;
1383
1384 case 'M': /* message-id */
1385 len = MIN(grp_fmt.len_msgid, sizeof(tmp) - 1);
1386 strncpy(tmp, arts[j].refptr ? arts[j].refptr->txt : "", len);
1387 tmp[len] = '\0';
1388 strcat(buffer, tmp);
1389 if ((k = (int) (len - (size_t) strwidth(tmp))) > 0) {
1390 buf = buffer + strlen(buffer);
1391 for (; k > 0; --k)
1392 *buf++ = ' ';
1393 *buf = '\0';
1394 }
1395 break;
1396
1397 case 'n':
1398 strcat(buffer, tin_ltoa(i + 1, (int) grp_fmt.len_linenumber));
1399 break;
1400
1401 case 'R':
1402 n = ((curr_group->attribute->show_only_unread_arts) ? (sbuf.unread + sbuf.seen) : sbuf.total);
1403 if (n > 1)
1404 strcat(buffer, tin_ltoa(n, (int) grp_fmt.len_respcnt));
1405 else {
1406 buf = buffer + strlen(buffer);
1407 for (k = (int) grp_fmt.len_respcnt; k > 0; --k)
1408 *buf++ = ' ';
1409 *buf = '\0';
1410 }
1411 break;
1412
1413 case 'S': /* score */
1414 strcat(buffer, tin_ltoa(sbuf.score, (int) grp_fmt.len_score));
1415 break;
1416
1417 case 's': /* thread/subject */
1418 len = curr_group->attribute->show_author != SHOW_FROM_NONE ? grp_fmt.len_subj : grp_fmt.len_subj + grp_fmt.len_from;
1419
1420 if (sbuf.multipart_have > 1) /* We have a multipart msg so lets built our new header info. */
1421 build_multipart_header(arts_sub, (int) len, arts[j].subject, sbuf.multipart_compare_len, sbuf.multipart_have, sbuf.multipart_total);
1422 else
1423 STRCPY(arts_sub, arts[j].subject);
1424
1425 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
1426 if ((wtmp = char2wchar_t(arts_sub)) != NULL) {
1427 wtmp2 = wcspart(wtmp, (int) len, TRUE);
1428 if (wcstombs(tmp, wtmp2, sizeof(tmp) - 1) != (size_t) -1)
1429 strcat(buffer, tmp);
1430
1431 free(wtmp);
1432 free(wtmp2);
1433 }
1434 #else
1435 len_start = strwidth(buffer);
1436 strncat(buffer, arts_sub, len);
1437 fill = len - (strwidth(buffer) - len_start);
1438 gap = strlen(buffer);
1439 for (k = 0; k < fill; k++)
1440 buffer[gap + k] = ' ';
1441 buffer[gap + fill] = '\0';
1442 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
1443 break;
1444
1445 default:
1446 break;
1447 }
1448 }
1449 /* protect display from non-displayable characters (e.g., form-feed) */
1450 convert_to_printable(buffer, FALSE);
1451
1452 #ifndef USE_CURSES
1453 if (tinrc.strip_blanks)
1454 strcat(strip_line(buffer), cCRLF);
1455 #endif /* !USE_CURSES */
1456
1457 WriteLine(INDEX2LNUM(i), buffer);
1458
1459 #ifdef USE_CURSES
1460 free(buffer);
1461 #endif /* USE_CURSES */
1462 if (!tagged && sbuf.art_mark == tinrc.art_marked_selected)
1463 draw_mark_selected(i);
1464 }
1465
1466
1467 static void
1468 show_group_title(
1469 t_bool clear_title)
1470 {
1471 char buf[LEN], tmp[LEN], flag;
1472 int i, art_cnt = 0, recent_art_cnt = 0, selected_art_cnt = 0, read_selected_art_cnt = 0, killed_art_cnt = 0;
1473
1474 for_each_art(i) {
1475 if (arts[i].thread == ART_EXPIRED)
1476 continue;
1477
1478 if (curr_group->attribute->show_only_unread_arts) {
1479 if (arts[i].status != ART_READ) {
1480 art_cnt++;
1481 if (tinrc.recent_time && ((time((time_t *) 0) - arts[i].date) < (tinrc.recent_time * DAY)))
1482 recent_art_cnt++;
1483 }
1484 if (arts[i].killed == ART_KILLED_UNREAD)
1485 killed_art_cnt++;
1486 } else {
1487 art_cnt++;
1488 if (tinrc.recent_time && ((time((time_t *) 0) - arts[i].date) < (tinrc.recent_time * DAY)))
1489 recent_art_cnt++;
1490
1491 if (arts[i].killed)
1492 killed_art_cnt++;
1493 }
1494 if (arts[i].selected) {
1495 if (arts[i].status != ART_READ)
1496 selected_art_cnt++;
1497 else
1498 read_selected_art_cnt++;
1499 }
1500 }
1501
1502 /*
1503 * build the group title
1504 */
1505 /* group name and thread count */
1506 snprintf(buf, sizeof(buf), "%s (%d%c",
1507 curr_group->name, grpmenu.max,
1508 *txt_threading[curr_group->attribute->thread_articles]);
1509
1510 /* article count */
1511 if ((cmdline.args & CMDLINE_GETART_LIMIT) ? cmdline.getart_limit : tinrc.getart_limit)
1512 snprintf(tmp, sizeof(tmp), " %d/%d%"T_CHAR_FMT,
1513 (cmdline.args & CMDLINE_GETART_LIMIT) ? cmdline.getart_limit : tinrc.getart_limit, art_cnt,
1514 (curr_group->attribute->show_only_unread_arts ? tinrc.art_marked_unread : tinrc.art_marked_read));
1515 else
1516 snprintf(tmp, sizeof(tmp), " %d%"T_CHAR_FMT,
1517 art_cnt,
1518 (curr_group->attribute->show_only_unread_arts ? tinrc.art_marked_unread : tinrc.art_marked_read));
1519 if (sizeof(buf) > strlen(buf) + strlen(tmp))
1520 strcat(buf, tmp);
1521
1522 /* selected articles */
1523 if (curr_group->attribute->show_only_unread_arts)
1524 snprintf(tmp, sizeof(tmp), " %d%"T_CHAR_FMT,
1525 selected_art_cnt, tinrc.art_marked_selected);
1526 else
1527 snprintf(tmp, sizeof(tmp), " %d%"T_CHAR_FMT" %d%"T_CHAR_FMT,
1528 selected_art_cnt, tinrc.art_marked_selected,
1529 read_selected_art_cnt, tinrc.art_marked_read_selected);
1530 if (sizeof(buf) > strlen(buf) + strlen(tmp))
1531 strcat(buf, tmp);
1532
1533 /* recent articles */
1534 if (tinrc.recent_time) {
1535 snprintf(tmp, sizeof(tmp), " %d%"T_CHAR_FMT,
1536 recent_art_cnt, tinrc.art_marked_recent);
1537
1538 if (sizeof(buf) > strlen(buf) + strlen(tmp))
1539 strcat(buf, tmp);
1540 }
1541
1542 /* killed articles */
1543 snprintf(tmp, sizeof(tmp), " %d%"T_CHAR_FMT,
1544 killed_art_cnt, tinrc.art_marked_killed);
1545
1546 if (sizeof(buf) > strlen(buf) + strlen(tmp))
1547 strcat(buf, tmp);
1548
1549 /* group flag */
1550 if ((flag = group_flag(curr_group->moderated)) == ' ')
1551 snprintf(tmp, sizeof(tmp), ")");
1552 else
1553 snprintf(tmp, sizeof(tmp), ") %c", flag);
1554 if (sizeof(buf) > strlen(buf) + strlen(tmp))
1555 strcat(buf, tmp);
1556
1557 if (clear_title) {
1558 MoveCursor(0, 0);
1559 CleartoEOLN();
1560 }
1561 show_title(buf);
1562 }
1563
1564
1565 /*
1566 * Search for type SUBJ/AUTH in direction (TRUE = forwards)
1567 * Return 0 if all is done, or a >0 thread_depth to enter the thread menu
1568 */
1569 static int
1570 do_search(
1571 t_function func,
1572 t_bool repeat)
1573 {
1574 int start, n;
1575
1576 if (grpmenu.curr < 0)
1577 return 0;
1578
1579 /*
1580 * Not intuitive to search current thread in fwd search
1581 */
1582 start = ((func == GLOBAL_SEARCH_SUBJECT_FORWARD || func == GLOBAL_SEARCH_AUTHOR_FORWARD)
1583 && grpmenu.curr < grpmenu.max - 1) ? prev_response((int) base[grpmenu.curr + 1]) : (int) base[grpmenu.curr];
1584
1585 if (start >= 0 && ((n = search(func, start, repeat)) != -1)) {
1586 grpmenu.curr = which_thread(n);
1587
1588 /*
1589 * If the search found something deeper in a thread(not the base art)
1590 * then enter the thread
1591 */
1592 if ((n = which_response(n)) != 0)
1593 return n;
1594
1595 show_group_page();
1596 }
1597 return 0;
1598 }
1599
1600
1601 /*
1602 * We don't directly invoke the pager, but pass through the thread menu
1603 * to keep navigation sane.
1604 * 'art' is the arts[art] we wish to read
1605 * ignore_unavail should be set if we wish to 'keep going' after 'article unavailable'
1606 * Return a -ve ret_code if we must exit the group menu on return
1607 */
1608 static int
1609 enter_pager(
1610 int art,
1611 t_bool ignore_unavail)
1612 {
1613 t_pagerinfo page;
1614
1615 page.art = art;
1616 page.ignore_unavail = CAST_BOOL(ignore_unavail);
1617
1618 return enter_thread(0, &page);
1619 }
1620
1621
1622 /*
1623 * Handle entry/exit with the thread menu
1624 * Return -ve ret_code if we must exit the group menu on return
1625 */
1626 static int
1627 enter_thread(
1628 int depth,
1629 t_pagerinfo *page)
1630 {
1631 int i, n;
1632
1633 if (grpmenu.curr < 0) {
1634 info_message(_(txt_no_arts));
1635 return 0;
1636 }
1637
1638 forever {
1639 switch (i = thread_page(curr_group, (int) base[grpmenu.curr], depth, page)) {
1640 case GRP_QUIT: /* 'Q'uit */
1641 case GRP_RETSELECT: /* Back to selection screen */
1642 return i;
1643 /* NOTREACHED */
1644 break;
1645
1646 case GRP_NEXT: /* 'c'atchup */
1647 show_group_page();
1648 move_down();
1649 return 0;
1650 /* NOTREACHED */
1651 break;
1652
1653 case GRP_NEXTUNREAD: /* 'C'atchup */
1654 if ((n = next_unread((int) base[grpmenu.curr])) >= 0) {
1655 if (page)
1656 page->art = n;
1657 if ((n = which_thread(n)) >= 0) {
1658 grpmenu.curr = n;
1659 depth = 0;
1660 break; /* Drop into next thread with unread */
1661 }
1662 }
1663 /* No more unread threads in this group, enter next group */
1664 grpmenu.curr = 0;
1665 return GRP_NEXTUNREAD;
1666 /* NOTREACHED */
1667 break;
1668
1669 case GRP_KILLED:
1670 grpmenu.curr = 0;
1671 /* FALLTHROUGH */
1672
1673 case GRP_EXIT:
1674 /* case GRP_GOTOTHREAD will never make it up this far */
1675 default: /* ie >= 0 Shouldn't happen any more? */
1676 clear_note_area();
1677 show_group_page();
1678 return 0;
1679 /* NOTREACHED */
1680 break;
1681 }
1682 }
1683 /* NOTREACHED */
1684 return 0;
1685 }
1686
1687
1688 /*
1689 * Return a ret_code
1690 */
1691 static int
1692 tab_pressed(
1693 void)
1694 {
1695 int n;
1696
1697 if ((n = ((grpmenu.curr < 0) ? -1 : next_unread((int) base[grpmenu.curr]))) < 0)
1698 return GRP_NEXTUNREAD; /* => Enter next unread group */
1699
1700 /* We still have unread arts in the current group ... */
1701 return enter_pager(n, TRUE);
1702 }
1703
1704
1705 /*
1706 * There are three ways this is called
1707 * catchup & return to group menu
1708 * catchup & go to next group with unread articles
1709 * group exit via left arrow if auto-catchup is set
1710 * Return a -ve ret_code if we're done with the group menu
1711 */
1712 static int
1713 group_catchup(
1714 t_function func)
1715 {
1716 char buf[LEN];
1717 int pyn = 1;
1718
1719 if (num_of_tagged_arts && prompt_yn(_(txt_catchup_despite_tags), TRUE) != 1)
1720 return 0;
1721
1722 snprintf(buf, sizeof(buf), _(txt_mark_arts_read), (func == CATCHUP_NEXT_UNREAD) ? _(txt_enter_next_unread_group) : "");
1723
1724 if (!curr_group->newsrc.num_unread || (!TINRC_CONFIRM_ACTION) || (pyn = prompt_yn(buf, TRUE)) == 1)
1725 grp_mark_read(curr_group, arts);
1726
1727 switch (func) {
1728 case CATCHUP: /* 'c' */
1729 if (pyn == 1)
1730 return GRP_NEXT;
1731 break;
1732
1733 case CATCHUP_NEXT_UNREAD: /* 'C' */
1734 if (pyn == 1)
1735 return GRP_NEXTUNREAD;
1736 break;
1737
1738 case SPECIAL_CATCHUP_LEFT: /* <- group catchup on exit */
1739 switch (pyn) {
1740 case -1: /* ESCAPE - do nothing */
1741 break;
1742
1743 case 1: /* We caught up - advance group */
1744 return GRP_NEXT;
1745 /* NOTREACHED */
1746 break;
1747
1748 default: /* Just leave the group */
1749 return GRP_EXIT;
1750 /* NOTREACHED */
1751 break;
1752 }
1753 /* FALLTHROUGH */
1754 default: /* Should not be here */
1755 break;
1756 }
1757 return 0; /* Stay in this menu by default */
1758 }
1759
1760
1761 static t_bool
1762 prompt_getart_limit(
1763 void)
1764 {
1765 char *p;
1766 t_bool ret = FALSE;
1767
1768 clear_message();
1769 if ((p = tin_getline(_(txt_enter_getart_limit), 2, NULL, 0, FALSE, HIST_OTHER)) != NULL) {
1770 tinrc.getart_limit = atoi(p);
1771 ret = TRUE;
1772 }
1773 clear_message();
1774 return ret;
1775 }
1776
1777
1778 /*
1779 * Redraw all necessary parts of the screen after FEED_MARK_(UN)READ
1780 * Move cursor to next unread item if needed
1781 *
1782 * Returns TRUE when no next unread art, FALSE otherwise
1783 */
1784 t_bool
1785 group_mark_postprocess(
1786 int function,
1787 t_function feed_type,
1788 int respnum)
1789 {
1790 int n;
1791
1792 show_group_title(TRUE);
1793 switch (function) {
1794 case (FEED_MARK_READ):
1795 if (feed_type == FEED_THREAD || feed_type == FEED_ARTICLE)
1796 build_sline(grpmenu.curr);
1797 else
1798 show_group_page();
1799
1800 if ((n = next_unread(next_response(respnum))) == -1) {
1801 draw_subject_arrow();
1802 return TRUE;
1803 }
1804
1805 move_to_item(which_thread(n));
1806 break;
1807
1808 case (FEED_MARK_UNREAD):
1809 if (feed_type == FEED_THREAD || feed_type == FEED_ARTICLE)
1810 build_sline(grpmenu.curr);
1811 else
1812 show_group_page();
1813
1814 draw_subject_arrow();
1815 break;
1816
1817 default:
1818 break;
1819 }
1820 return FALSE;
1821 }