tin  2.6.1
About: TIN is a threaded NNTP and spool based UseNet newsreader.
  Fossies Dox: tin-2.6.1.tar.xz  ("unofficial" and yet experimental doxygen-generated source code documentation)  

thread.c
Go to the documentation of this file.
1/*
2 * Project : tin - a Usenet reader
3 * Module : thread.c
4 * Author : I. Lea
5 * Created : 1991-04-01
6 * Updated : 2021-07-25
7 * Notes :
8 *
9 * Copyright (c) 1991-2022 Iain Lea <iain@bricbrac.de>
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#define IS_EXPIRED(a) ((a)->article == ART_UNAVAILABLE || arts[(a)->article].thread == ART_EXPIRED)
50
51int thread_basenote = 0; /* Index in base[] of basenote */
52static int thread_respnum = 0; /* Index in arts[] of basenote ie base[thread_basenote] */
53static struct t_fmt thrd_fmt;
55
56/*
57 * Local prototypes
58 */
59#if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
60 static wchar_t get_art_mark(struct t_article *art);
61#else
62 static char get_art_mark(struct t_article *art);
63#endif /* MULTIBYTE_ABLE && !NO_LOCALE */
64static int enter_pager(int art, t_bool ignore_unavail, int level);
65static int thread_catchup(t_function func, struct t_group *group);
66static int thread_tab_pressed(void);
67static t_bool find_unexpired(struct t_msgid *ptr);
68static t_bool has_sibling(struct t_msgid *ptr);
69static t_function thread_left(void);
70static t_function thread_right(void);
71static void build_tline(int l, struct t_article *art);
72static void draw_thread_arrow(void);
73static void draw_thread_item(int item);
74static void make_prefix(struct t_msgid *art, char *prefix, int maxlen);
75static void show_thread_page(void);
76static void update_thread_page(void);
77
78
79/*
80 * thdmenu.curr Current screen cursor position in thread
81 * thdmenu.max Essentially = # threaded arts in current thread
82 * thdmenu.first Response # at top of screen
83 */
85
86/* TODO: find a better solution */
87static int ret_code = 0; /* Set to < 0 when it is time to leave this menu */
88
89/*
90 * returns the mark which should be used for this article
91 */
92#if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
93 static wchar_t
94#else
95 static char
96#endif /* MULTIBYTE_ABLE && !NO_LOCALE */
98 struct t_article *art)
99{
100 if (art->inrange) {
102 } else if (art->status == ART_UNREAD) {
103 return (art->selected ? tinrc.art_marked_selected : (tinrc.recent_time && ((time((time_t *) 0) - art->date) < (tinrc.recent_time * DAY))) ? tinrc.art_marked_recent : tinrc.art_marked_unread);
104 } else if (art->status == ART_WILL_RETURN) {
106 } else if (art->killed && tinrc.kill_level != KILL_NOTHREAD) {
108 } else {
109 if (/* tinrc.kill_level != KILL_UNREAD && */ art->score >= tinrc.score_select)
110 return tinrc.art_marked_read_selected; /* read hot chil^H^H^H^H article */
111 else
112 return tinrc.art_marked_read;
113 }
114}
115
116
117/*
118 * Build one line of the thread page display. Looks long winded, but
119 * there are a lot of variables in the format for the output
120 *
121 * WARNING: some other code expects to find the article mark (ART_MARK_READ,
122 * ART_MARK_SELECTED, etc) at mark_offset from beginning of the line.
123 * So, if you change the format used in this routine, be sure to check that
124 * the value of mark_offset is still correct.
125 * Yes, this is somewhat kludgy.
126 */
127static void
129 int l,
130 struct t_article *art)
131{
132 int gap, fill, i;
133 size_t len, len_start, len_end;
134 struct t_msgid *ptr;
135 char *buffer, *buf;
136 char *fmt = thrd_fmt.str;
137 char tmp[LEN];
138#if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
139 char markbuf[sizeof(wchar_t) + 4];
140 wchar_t *wtmp, *wtmp2;
141 wchar_t mark[] = { L'\0', L'\0' };
142#else
143 char mark[] = { '\0', '\0' };
144#endif /* MULTIBYTE_ABLE && !NO_LOCALE */
145
146#ifdef USE_CURSES
147 /*
148 * Allocate line buffer
149 * make it the same size like in !USE_CURSES case to simplify some code
150 */
151# if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
152 buffer = my_malloc(cCOLS * MB_CUR_MAX + 2);
153# else
154 buffer = my_malloc(cCOLS + 2);
155# endif /* MULTIBYTE_ABLE && !NO_LOCALE */
156#else
157 buffer = screen[INDEX2SNUM(l)].col;
158#endif /* USE_CURSES */
159
160 buffer[0] = '\0';
161
162 if (tinrc.draw_arrow)
163 strcat(buffer, " ");
164
165 for (; *fmt; fmt++) {
166 if (*fmt != '%') {
167 strncat(buffer, fmt, 1);
168 continue;
169 }
170 switch (*++fmt) {
171 case '\0':
172 break;
173
174 case '%':
175 strncat(buffer, fmt, 1);
176 break;
177
178 case 'D': /* date */
179 buf = my_malloc(LEN);
180 if (my_strftime(buf, LEN - 1, thrd_fmt.date_str, localtime(&art->date))) {
181#if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
182 if ((wtmp = char2wchar_t(buf)) != NULL) {
183 wtmp2 = wcspart(wtmp, (int) thrd_fmt.len_date_max, TRUE);
184 if (wcstombs(tmp, wtmp2, sizeof(tmp) - 1) != (size_t) -1)
185 strcat(buffer, tmp);
186
187 free(wtmp);
188 free(wtmp2);
189 }
190#else
191 strncat(buffer, buf, thrd_fmt.len_date_max);
192#endif /* MULTIBYTE_ABLE && !NO_LOCALE */
193 }
194 free(buf);
195 break;
196
197 case 'F': /* from */
198#if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
199 get_author(TRUE, art, tmp, sizeof(tmp) - 1);
200
201 if ((wtmp = char2wchar_t(tmp)) != NULL) {
202 wtmp2 = wcspart(wtmp, (int) thrd_fmt.len_from, TRUE);
203 if (wcstombs(tmp, wtmp2, sizeof(tmp) - 1) != (size_t) -1)
204 strcat(buffer, tmp);
205
206 free(wtmp);
207 free(wtmp2);
208 }
209#else
211 len_start = strwidth(buffer);
212 get_author(TRUE, art, buffer + strlen(buffer), thrd_fmt.len_from);
213 fill = thrd_fmt.len_from - (strwidth(buffer) - len_start);
214 gap = strlen(buffer);
215 for (i = 0; i < fill; i++)
216 buffer[gap + i] = ' ';
217 buffer[gap + fill] = '\0';
218 }
219#endif /* MULTIBYTE_ABLE && !NO_LOCALE */
220 break;
221
222 case 'I': /* initials */
223 len = MIN(thrd_fmt.len_initials, sizeof(tmp) - 1);
224 get_initials(art, tmp, (int) len);
225 strcat(buffer, tmp);
226 if ((i = (int) (len - (size_t) strwidth(tmp))) > 0) {
227 buf = buffer + strlen(buffer);
228 for (; i > 0; --i)
229 *buf++ = ' ';
230 *buf = '\0';
231 }
232 break;
233
234 case 'L': /* lines */
235 if (art->line_count != -1)
236 strcat(buffer, tin_ltoa(art->line_count, (int) thrd_fmt.len_linecnt));
237 else {
238 buf = buffer + strlen(buffer);
239 for (i = (int) thrd_fmt.len_linecnt; i > 1; --i)
240 *buf++ = ' ';
241 *buf++ = '?';
242 *buf = '\0';
243 }
244 break;
245
246 case 'm': /* article flags, tag number, or whatever */
248 thrd_fmt.mark_offset = (size_t) (mark_offset = strwidth(buffer) + 2);
249 if (art->tagged) {
250 strcat(buffer, " ");
251 strcat(buffer, tin_ltoa(art->tagged, 3));
252#if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
253 mark[0] = L'\0';
254#else
255 mark[0] = '\0';
256#endif /* MULTIBYTE_ABLE && !NO_LOCALE */
257 } else {
258 mark[0] = get_art_mark(art);
259#if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
260 snprintf(markbuf, sizeof(markbuf), "%s%lc", art_mark_width > wcwidth(mark[0]) ? " " : " ", mark[0]);
261 strcat(buffer, markbuf);
262#else
263 strcat(buffer, " ");
264 buffer[strlen(buffer) - 1] = mark[0]; /* insert mark */
265#endif /* MULTIBYTE_ABLE && !NO_LOCALE */
266 }
267 break;
268
269 case 'M': /* message-id */
270 len = MIN(thrd_fmt.len_msgid, sizeof(tmp) - 1);
271 strncpy(tmp, art->refptr ? art->refptr->txt : "", len);
272 tmp[len] = '\0';
273 strcat(buffer, tmp);
274 if ((i = (int) (len - (size_t) strwidth(tmp))) > 0) {
275 buf = buffer + strlen(buffer);
276 for (; i > 0; --i)
277 *buf++ = ' ';
278 *buf = '\0';
279 }
280 break;
281
282 case 'n':
283 strcat(buffer, tin_ltoa(l + 1, (int) thrd_fmt.len_linenumber));
284 break;
285
286 case 'S': /* score */
287 strcat(buffer, tin_ltoa(art->score, (int) thrd_fmt.len_score));
288 break;
289
290 case 'T': /* thread/subject */
292 len_start = (size_t) strwidth(buffer);
293
295 case THREAD_REFS:
296 case THREAD_BOTH:
297 /*
298 * Mutt-like thread tree. by sjpark@sparcs.kaist.ac.kr
299 * Insert tree-structure strings "`->", "+->", ...
300 */
301
302 if (art->refptr) {
303 make_prefix(art->refptr, buffer + strlen(buffer), (int) len);
304
305 len_end = (size_t) strwidth(buffer);
306
307 /*
308 * Copy in the subject up to where the author (if any) starts
309 */
310 gap = (len - (len_end - len_start));
311
312 /*
313 * Mutt-like thread tree. by sjpark@sparcs.kaist.ac.kr
314 * Hide subject if same as parent's.
315 */
316 if (gap > 0) {
317 for (ptr = art->refptr->parent; ptr && IS_EXPIRED(ptr); ptr = ptr->parent)
318 ;
319 if (!(ptr && arts[ptr->article].subject == art->subject))
320#if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
321 {
322 if ((wtmp = char2wchar_t(art->subject)) != NULL) {
323 wtmp2 = wcspart(wtmp, gap, TRUE);
324 if (wcstombs(tmp, wtmp2, sizeof(tmp) - 1) != (size_t) -1)
325 strcat(buffer, tmp);
326
327 free(wtmp);
328 free(wtmp2);
329 }
330 }
331#else
332 {
333 strncat(buffer, art->subject, gap);
334 }
335 buffer[len_end + gap] = '\0'; /* Just in case */
336#endif /* MULTIBYTE_ABLE && !NO_LOCALE */
337 }
338 }
339 break;
340
341 case THREAD_NONE:
342 case THREAD_SUBJ:
343 case THREAD_MULTI:
344 case THREAD_PERC:
345 len_end = (size_t) strwidth(buffer);
346 gap = (len - (len_end - len_start));
347 if (gap > 0) {
348#if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
349 {
350 if ((wtmp = char2wchar_t(art->subject)) != NULL) {
351 wtmp2 = wcspart(wtmp, gap, TRUE);
352 if (wcstombs(tmp, wtmp2, sizeof(tmp) - 1) != (size_t) -1)
353 strcat(buffer, tmp);
354
355 free(wtmp);
356 free(wtmp2);
357 }
358 }
359#else
360 {
361 strncat(buffer, art->subject, gap);
362 }
363 buffer[len_end + gap] = '\0'; /* Just in case */
364#endif /* MULTIBYTE_ABLE && !NO_LOCALE */
365 }
366 break;
367
368 default:
369 break;
370 }
371
372 /* pad out */
373 fill = (len - ((size_t) strwidth(buffer) - len_start));
374 gap = (int) strlen(buffer);
375 for (i = 0; i < fill; i++)
376 buffer[gap + i] = ' ';
377 buffer[gap + fill] = '\0';
378 break;
379
380 default:
381 break;
382 }
383 }
384 /* protect display from non-displayable characters (e.g., form-feed) */
386
387#ifndef USE_CURSES
389 strcat(strip_line(buffer), cCRLF);
390#endif /* !USE_CURSES */
391
392 WriteLine(INDEX2LNUM(l), buffer);
393
394#ifdef USE_CURSES
395 free(buffer);
396#endif /* USE_CURSES */
397
398 if (mark[0] == tinrc.art_marked_selected)
400 my_flush();
401}
402
403
404static void
406 int item)
407{
409}
410
411
412static t_function
414 void)
415{
417 return SPECIAL_CATCHUP_LEFT; /* ie, not via 'c' or 'C' */
418 else
419 return GLOBAL_QUIT;
420}
421
422
423static t_function
425 void)
426{
427 return THREAD_READ_ARTICLE;
428}
429
430
431/*
432 * Show current thread.
433 * If threaded on Subject: show
434 * <respnum> <name>
435 * If threaded on References:
436 * <respnum> <subject> <name>
437 * Return values:
438 * GRP_RETSELECT Return to selection screen
439 * GRP_QUIT 'Q'uit all the way out
440 * GRP_NEXT Catchup goto next group
441 * GRP_NEXTUNREAD Catchup enter next unread thread
442 * GRP_KILLED Thread was killed at art level?
443 * GRP_EXIT Return to group menu
444 */
445int
447 struct t_group *group,
448 int respnum, /* base[] article of thread to view */
449 int thread_depth, /* initial depth in thread */
450 t_pagerinfo *page) /* !NULL if we must go direct to the pager */
451{
452 char key[MAXKEYLEN];
453#if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
454 wchar_t mark[] = { L'\0', L'\0' };
455 wchar_t *wtmp;
456#else
457 char mark[] = { '\0', '\0' };
458#endif /* MULTIBYTE_ABLE && !NO_LOCALE */
459 int i, n;
460 t_artnum old_artnum;
461 t_bool repeat_search;
463
464 thread_respnum = respnum; /* Bodge to make this variable global */
465
466 if ((n = which_thread(thread_respnum)) >= 0)
467 thread_basenote = n;
468 if ((thdmenu.max = num_of_responses(thread_basenote) + 1) <= 0) {
470 return GRP_EXIT;
471 }
472
473 /*
474 * Set the cursor to the last response unless pos_first_unread is on
475 * or an explicit thread_depth has been specified
476 */
478 /* reset the first item on screen to 0 */
479 thdmenu.first = 0;
480
481 if (thread_depth)
482 thdmenu.curr = thread_depth;
483 else {
484 if (group->attribute->pos_first_unread) {
486 for (n = 0, i = (int) base[thread_basenote]; i >= 0; i = arts[i].thread, n++) {
487 if (arts[i].status == ART_UNREAD || arts[i].status == ART_WILL_RETURN) {
488 if (arts[i].thread == ART_EXPIRED)
489 art_mark(group, &arts[i], ART_READ);
490 else
491 thdmenu.curr = n;
492 break;
493 }
494 }
495 }
496 }
497 }
498
499 if (thdmenu.curr < 0)
500 thdmenu.curr = 0;
501
502 /*
503 * See if we're on a direct call from the group menu to the pager
504 */
505 if (page) {
506 if ((ret_code = enter_pager(page->art, page->ignore_unavail, GROUP_LEVEL)) != 0)
507 return ret_code;
508 /* else fall through to stay in thread level */
509 }
510
511 /* Now we know where the cursor is, actually put something on the screen */
513
514 /* reset ret_code */
515 ret_code = 0;
516 while (ret_code >= 0) {
520 repeat_search = TRUE;
521 } else
522 repeat_search = FALSE;
523
524 switch (func) {
525 case GLOBAL_ABORT: /* Abort */
526 break;
527
528 case DIGIT_1:
529 case DIGIT_2:
530 case DIGIT_3:
531 case DIGIT_4:
532 case DIGIT_5:
533 case DIGIT_6:
534 case DIGIT_7:
535 case DIGIT_8:
536 case DIGIT_9:
537 if (thdmenu.max == 1)
539 else
541 break;
542
543#ifndef NO_SHELL_ESCAPE
546 break;
547#endif /* !NO_SHELL_ESCAPE */
548
549 case GLOBAL_FIRST_PAGE: /* show first page of articles */
550 top_of_list();
551 break;
552
553 case GLOBAL_LAST_PAGE: /* show last page of articles */
554 end_of_list();
555 break;
556
557 case GLOBAL_LAST_VIEWED: /* show last viewed article */
558 if (this_resp < 0 || (which_thread(this_resp) == -1)) {
560 break;
561 }
563 break;
564
565 case GLOBAL_SET_RANGE: /* set range */
569 }
570 break;
571
572 case GLOBAL_PIPE: /* pipe article(s) to command */
573 if (thread_basenote >= 0)
575 break;
576
577#ifndef DISABLE_PRINTING
578 case GLOBAL_PRINT: /* print article(s) */
579 if (thread_basenote >= 0)
581 break;
582#endif /* !DISABLE_PRINTING */
583
584 case THREAD_MAIL: /* mail article(s) to somebody */
585 if (thread_basenote >= 0)
587 break;
588
589 case THREAD_SAVE: /* save articles with prompting */
590 if (thread_basenote >= 0)
592 break;
593
594 case THREAD_AUTOSAVE: /* Auto-save articles without prompting */
595 if (thread_basenote >= 0)
597 break;
598
599 case MARK_FEED_READ: /* mark selected articles as read */
600 if (thread_basenote >= 0)
602 break;
603
604 case MARK_FEED_UNREAD: /* mark selected articles as unread */
605 if (thread_basenote >= 0)
607 break;
608
612 if (filter_menu(func, group, &arts[n])) {
613 old_artnum = arts[n].artnum;
614 unfilter_articles(group);
615 filter_articles(group);
616 make_threads(group, FALSE);
617 if ((n = find_artnum(old_artnum)) == -1 || which_thread(n) == -1) { /* We have lost the thread */
619 break;
620 }
621 fixup_thread(n, TRUE);
622 }
624 break;
625
629 unfilter_articles(group);
631 filter_articles(group);
632 make_threads(group, FALSE);
633 if ((n = find_artnum(old_artnum)) == -1 || which_thread(n) == -1) { /* We have lost the thread */
635 break;
636 }
637 fixup_thread(n, TRUE);
638 }
640 break;
641
642 case THREAD_READ_ARTICLE: /* read current article within thread */
644 break;
645
648 break;
649
650 case THREAD_CANCEL: /* cancel current article */
651 if (can_post || group->attribute->mailing_list != NULL) {
652 int ret;
653
655 ret = art_open(TRUE, &arts[n], group, &pgart, TRUE, _(txt_reading_article));
656 if (ret != ART_UNAVAILABLE && ret != ART_ABORT && cancel_article(group, &arts[n], n))
659 } else
661 break;
662
663 case GLOBAL_POST: /* post a basenote */
664 if (post_article(group->name))
666 break;
667
668 case GLOBAL_REDRAW_SCREEN: /* redraw screen */
669 my_retouch();
672 break;
673
674 case GLOBAL_LINE_DOWN:
675 move_down();
676 break;
677
678 case GLOBAL_LINE_UP:
679 move_up();
680 break;
681
682 case GLOBAL_PAGE_UP:
683 page_up();
684 break;
685
686 case GLOBAL_PAGE_DOWN:
687 page_down();
688 break;
689
691 scroll_down();
692 break;
693
694 case GLOBAL_SCROLL_UP:
695 scroll_up();
696 break;
697
698 case SPECIAL_CATCHUP_LEFT: /* come here when exiting thread via <- */
699 case CATCHUP: /* catchup thread, move to next one */
700 case CATCHUP_NEXT_UNREAD: /* -> next with unread arts */
701 ret_code = thread_catchup(func, group);
702 break;
703
704 case THREAD_MARK_ARTICLE_READ: /* mark current article/range/tagged articles as read */
705 case MARK_ARTICLE_UNREAD: /* or unread */
706 if (thread_basenote >= 0) {
707 t_function function, type;
708
711 if (feed_articles(function, THREAD_LEVEL, type, group, find_response(thread_basenote, thdmenu.curr)) == 1)
713 }
714 break;
715
716 case THREAD_TOGGLE_SUBJECT_DISPLAY: /* toggle display of subject & subj/author */
717 if (show_subject) {
721 }
722 break;
723
726 old_artnum = arts[n].artnum;
728 if ((n = find_artnum(old_artnum)) == -1 || which_thread(n) == -1) { /* We have lost the thread */
731 } else {
735 }
736 break;
737
738 case GLOBAL_HELP: /* help */
741 break;
742
744 if ((n = prompt_msgid()) != ART_UNAVAILABLE)
746 break;
747
750 break;
751
752 case GLOBAL_SEARCH_BODY: /* search article body */
753 if ((n = search_body(group, find_response(thread_basenote, thdmenu.curr), repeat_search)) != -1) {
756 }
757 break;
758
759 case GLOBAL_SEARCH_AUTHOR_FORWARD: /* author search */
761 case GLOBAL_SEARCH_SUBJECT_FORWARD: /* subject search */
763 if ((n = search(func, find_response(thread_basenote, thdmenu.curr), repeat_search)) != -1)
764 fixup_thread(n, TRUE);
765 break;
766
767 case GLOBAL_TOGGLE_HELP_DISPLAY: /* toggle mini help menu */
770 break;
771
772 case GLOBAL_TOGGLE_INVERSE_VIDEO: /* toggle inverse video */
776 break;
777
778#ifdef HAVE_COLOR
779 case GLOBAL_TOGGLE_COLOR: /* toggle color */
780 if (toggle_color()) {
782 show_color_status();
783 }
784 break;
785#endif /* HAVE_COLOR */
786
787 case GLOBAL_QUIT: /* return to previous level */
789 break;
790
791 case GLOBAL_QUIT_TIN: /* quit */
793 break;
794
795 case THREAD_TAG_PARTS: /* tag/untag article */
796 /* Find index of current article */
798 break;
799 else {
800 int old_num = num_of_tagged_arts;
801
802 if (tag_multipart(n) != 0) {
804
805 if (old_num < num_of_tagged_arts)
807 else
809 }
810 }
811 break;
812
813 case THREAD_TAG: /* tag/untag article */
814 /* Find index of current article */
816 break;
817 else {
818 t_bool tagged;
819
820 if ((tagged = tag_article(n))) {
821#if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
822 if ((wtmp = char2wchar_t(tin_ltoa((&arts[n])->tagged, 3)))) {
824 free(wtmp);
825 }
826#else
827 mark_screen(thdmenu.curr, mark_offset - 2, tin_ltoa((&arts[n])->tagged, 3));
828#endif /* MULTIBYTE_ABLE && !NO_LOCALE */
829 } else
830 update_thread_page(); /* Must update whole page */
831
832 /* Automatically advance to next art if not at end of thread */
833 if (thdmenu.curr + 1 < thdmenu.max)
834 move_down();
835 else
837
839 }
840 break;
841
842 case GLOBAL_BUGREPORT:
843 bug_report();
844 break;
845
846 case THREAD_UNTAG: /* untag all articles */
847 if (grpmenu.curr >= 0 && untag_all_articles())
849 break;
850
851 case GLOBAL_VERSION: /* version */
853 break;
854
855 case MARK_THREAD_UNREAD: /* mark thread as unread */
859 break;
860
861 case THREAD_SELECT_ARTICLE: /* mark article as selected */
862 case THREAD_TOGGLE_ARTICLE_SELECTION: /* toggle article as selected */
864 break;
865 arts[n].selected = (!(func == THREAD_TOGGLE_ARTICLE_SELECTION && arts[n].selected)); /* TODO: optimise? */
866/* update_thread_page(); */
867 mark[0] = get_art_mark(&arts[n]);
868#if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
869 mark_screen(thdmenu.curr, mark_offset + (art_mark_width - wcwidth(mark[0])), mark);
870#else
872#endif /* MULTIBYTE_ABLE && !NO_LOCALE */
873 if (thdmenu.curr + 1 < thdmenu.max)
874 move_down();
875 else
877 break;
878
879 case THREAD_REVERSE_SELECTIONS: /* reverse selections */
881 arts[i].selected = bool_not(arts[i].selected);
883 break;
884
885 case THREAD_UNDO_SELECTIONS: /* undo selections */
887 arts[i].selected = FALSE;
889 break;
890
891 case GLOBAL_POSTPONED: /* post postponed article */
892 if (can_post) {
895 } else
897 break;
898
899 case GLOBAL_DISPLAY_POST_HISTORY: /* display messages posted by user */
900 if (post_hist_page())
901 return GRP_EXIT;
902 break;
903
904 case GLOBAL_TOGGLE_INFO_LAST_LINE: /* display subject in last line */
907 break;
908
909 default:
911 }
912 } /* ret_code >= 0 */
913
916
917 return ret_code;
918}
919
920
921static void
923 void)
924{
925 char *title;
926 int i, art;
927
929 currmenu = &thdmenu;
931
932 ClearScreen();
934
936 mark_offset = 0;
937
938 if (show_subject)
940 else
941 title = fmt_string(_(txt_stp_thread), cCOLS - 23, arts[thread_respnum].subject);
942 show_title(title);
943 free(title);
944
946 for (i = thdmenu.first; i < thdmenu.first + NOTESLINES && i < thdmenu.max; ++i) {
947 build_tline(i, &arts[art]);
948 if ((art = next_response(art)) < 0)
949 break;
950 }
951
954}
955
956
957static void
959 void)
960{
961#if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
962 wchar_t mark[] = { L'\0', L'\0' };
963 wchar_t *wtmp;
964#else
965 char mark[] = { '\0', '\0' };
966#endif /* MULTIBYTE_ABLE && !NO_LOCALE */
967 int i, the_index;
968
970 assert(thdmenu.first != 0 || the_index == thread_respnum);
971
972 for (i = thdmenu.first; i < thdmenu.first + NOTESLINES && i < thdmenu.max; ++i) {
973 if ((&arts[the_index])->tagged) {
974#if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
975 if ((wtmp = char2wchar_t(tin_ltoa((&arts[the_index])->tagged, 3)))) {
976 mark_screen(i, mark_offset - (3 - art_mark_width), wtmp);
977 free(wtmp);
978 }
979#else
980 mark_screen(i, mark_offset - 2, tin_ltoa((&arts[the_index])->tagged, 3));
981#endif /* MULTIBYTE_ABLE && !NO_LOCALE */
982 } else {
983 mark[0] = get_art_mark(&arts[the_index]);
984#if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
985 mark_screen(i, mark_offset - (3 - art_mark_width), L" "); /* clear space used by tag numbering */
986 mark_screen(i, mark_offset + (art_mark_width - wcwidth(mark[0])), mark);
987#else
988 mark_screen(i, mark_offset - 2, " "); /* clear space used by tag numbering */
989 mark_screen(i, mark_offset, mark);
990#endif /* MULTIBYTE_ABLE && !NO_LOCALE */
991 if (mark[0] == tinrc.art_marked_selected)
993 }
994 if ((the_index = next_response(the_index)) == -1)
995 break;
996 }
997
999}
1000
1001
1002static void
1004 void)
1005{
1007
1010 else if (thdmenu.curr == thdmenu.max - 1)
1012}
1013
1014
1015/*
1016 * Fix all the internal pointers if the current thread/response has
1017 * changed.
1018 */
1019void
1021 int respnum,
1022 t_bool redraw)
1023{
1024 int basenote = which_thread(respnum);
1025 int old_thread_basenote = thread_basenote;
1026
1027 if (basenote >= 0) {
1028 thread_basenote = basenote;
1031 grpmenu.curr = basenote;
1032 if (redraw && basenote != old_thread_basenote)
1034 }
1035
1036 if (redraw)
1037 move_to_item(which_response(respnum)); /* Redraw screen etc.. */
1038}
1039
1040
1041/*
1042 * Return the number of unread articles there are within a thread
1043 */
1044int
1046 int thread)
1047{
1048 int i;
1049 int sum = 0;
1050
1051 for_each_art_in_thread(i, thread) {
1052 if (arts[i].status != ART_READ)
1053 sum++;
1054 }
1055
1056 return sum;
1057}
1058
1059
1060/*
1061 * Which base note (an index into base[]) does a respnum (an index into
1062 * arts[]) correspond to?
1063 *
1064 * In other words, base[] points to an entry in arts[] which is the head of
1065 * a thread, linked with arts[].thread. For any q: arts[q], find i such that
1066 * base[i]->arts[n]->arts[o]->...->arts[q]
1067 *
1068 * Note that which_thread() can return -1 if in show_read_only mode and the
1069 * article of interest has been read as well as all other articles in the
1070 * thread, thus resulting in no base[] entry for it.
1071 */
1072int
1074 int n)
1075{
1076 int i, j;
1077
1078 /* Move to top of thread */
1079 for (i = n; arts[i].prev >= 0; i = arts[i].prev)
1080 ;
1081 /* Find in base[] */
1082 for (j = 0; j < grpmenu.max; j++) {
1083 if (base[j] == i)
1084 return j;
1085 }
1086
1087#ifdef DEBUG
1088 if (debug & (DEBUG_FILTER | DEBUG_REFS))
1089 error_message(2, _(txt_cannot_find_base_art), n);
1090#endif /* DEBUG */
1091 return -1;
1092}
1093
1094
1095/*
1096 * Find how deep in its' thread arts[n] is. Start counting at zero
1097 */
1098int
1100 int n)
1101{
1102 int i, j;
1103 int num = 0;
1104
1105 if ((i = which_thread(n)) == -1)
1106 return 0;
1107
1109 if (j == n)
1110 break;
1111 else
1112 num++;
1113 }
1114
1115 return num;
1116}
1117
1118
1119/*
1120 * Given an index into base[], find the number of responses for
1121 * that basenote
1122 */
1123int
1125 int n)
1126{
1127 int i;
1128 int sum = 0;
1129#ifndef NDEBUG
1130 int oldi = -3;
1131
1132 assert(n < grpmenu.max && n >= 0);
1133#endif /* !NDEBUG */
1134
1136#ifndef NDEBUG
1137 assert(i != ART_EXPIRED);
1138 assert(i != oldi);
1139 oldi = i;
1140#endif /* !NDEBUG */
1141 sum++;
1142 }
1143
1144 return sum - 1;
1145}
1146
1147
1148/*
1149 * Calculating the score of a thread has been extracted from stat_thread()
1150 * because we need it also in art.c to sort base[].
1151 * get_score_of_thread expects the number of the first article of a thread.
1152 */
1153int
1155 int n)
1156{
1157 int i;
1158 int j = 0;
1159 int score = 0;
1160
1161 for (i = n; i >= 0; i = arts[i].thread) {
1162 /*
1163 * TODO: do we want to take the score of read articles into account?
1164 */
1165 if (arts[i].status != ART_READ || arts[i].killed == ART_KILLED_UNREAD /* || tinrc.kill_level == KILL_THREAD */) {
1167 /* we use the maximum article score for the complete thread */
1168 if ((arts[i].score > score) && (arts[i].score > 0))
1169 score = arts[i].score;
1170 else {
1171 if ((arts[i].score < score) && (score <= 0))
1172 score = arts[i].score;
1173 }
1174 } else { /* tinrc.thread_score >= THREAD_SCORE_SUM */
1175 /* sum scores of unread arts and count num. arts */
1176 score += arts[i].score;
1177 j++;
1178 }
1179 }
1180 }
1182 score /= j;
1183
1184 return score;
1185}
1186
1187
1188/*
1189 * Given an index into base[], return relevant statistics
1190 */
1191int
1193 int n,
1194 struct t_art_stat *sbuf) /* return value is always ignored */
1195{
1196 int i;
1197 MultiPartInfo minfo = {0};
1198
1199 sbuf->total = 0;
1200 sbuf->unread = 0;
1201 sbuf->seen = 0;
1202 sbuf->deleted = 0;
1203 sbuf->inrange = 0;
1204 sbuf->selected_total = 0;
1205 sbuf->selected_unread = 0;
1206 sbuf->selected_seen = 0;
1207 sbuf->killed = 0;
1209 sbuf->score = 0 /* -(SCORE_MAX) */;
1210 sbuf->time = 0;
1211 sbuf->multipart_compare_len = 0;
1212 sbuf->multipart_total = 0;
1213 sbuf->multipart_have = 0;
1214
1216 ++sbuf->total;
1217 if (arts[i].inrange)
1218 ++sbuf->inrange;
1219
1220 if (arts[i].delete_it)
1221 ++sbuf->deleted;
1222
1223 if (arts[i].status == ART_UNREAD) {
1224 ++sbuf->unread;
1225
1226 if (arts[i].date > sbuf->time)
1227 sbuf->time = arts[i].date;
1228 } else if (arts[i].status == ART_WILL_RETURN)
1229 ++sbuf->seen;
1230
1231 if (arts[i].selected) {
1232 ++sbuf->selected_total;
1233 if (arts[i].status == ART_UNREAD)
1234 ++sbuf->selected_unread;
1235 else if (arts[i].status == ART_WILL_RETURN)
1236 ++sbuf->selected_seen;
1237 }
1238
1239 if (arts[i].killed)
1240 ++sbuf->killed;
1241
1242 if ((curr_group->attribute->thread_articles == THREAD_MULTI) && global_get_multipart_info(i, &minfo) && (minfo.total >= 1)) {
1244 sbuf->multipart_total = minfo.total;
1245 sbuf->multipart_have++;
1246 }
1247 }
1248
1249 sbuf->score = get_score_of_thread((int) base[n]);
1250
1251 if (sbuf->inrange)
1253 else if (sbuf->deleted)
1255 else if (sbuf->selected_unread)
1257 else if (sbuf->unread) {
1258 if (tinrc.recent_time && (time((time_t *) 0) - sbuf->time) < (tinrc.recent_time * DAY))
1260 else
1262 }
1263 else if (sbuf->seen)
1265 else if (sbuf->selected_total)
1267 else if (sbuf->killed == sbuf->total)
1269 else
1271 return sbuf->total;
1272}
1273
1274
1275/*
1276 * Find the next response to arts[n]. Go to the next basenote if there
1277 * are no more responses in this thread
1278 */
1279int
1281 int n)
1282{
1283 int i;
1284
1285 if (arts[n].thread >= 0)
1286 return arts[n].thread;
1287
1288 i = which_thread(n) + 1;
1289
1290 if (i >= grpmenu.max)
1291 return -1;
1292
1293 return (int) base[i];
1294}
1295
1296
1297/*
1298 * Given a respnum (index into arts[]), find the respnum of the
1299 * next basenote
1300 */
1301int
1303 int n)
1304{
1305 int i;
1306
1307 i = which_thread(n) + 1;
1308 if (i >= grpmenu.max)
1309 return -1;
1310
1311 return (int) base[i];
1312}
1313
1314
1315/*
1316 * Find the previous response. Go to the last response in the previous
1317 * thread if we go past the beginning of this thread.
1318 * Return -1 if we are at the start of the group
1319 */
1320int
1322 int n)
1323{
1324 int i;
1325
1326 if (arts[n].prev >= 0)
1327 return arts[n].prev;
1328
1329 i = which_thread(n) - 1;
1330
1331 if (i < 0)
1332 return -1;
1333
1334 return find_response(i, num_of_responses(i));
1335}
1336
1337
1338/*
1339 * return index in arts[] of the 'n'th response in thread base 'i'
1340 */
1341int
1343 int i,
1344 int n)
1345{
1346 int j;
1347
1348 j = (int) base[i];
1349
1350 while (n-- > 0 && arts[j].thread >= 0)
1351 j = arts[j].thread;
1352
1353 return j;
1354}
1355
1356
1357/*
1358 * Find the next unread response to art[n] in this group. If no response is
1359 * found from current point to the end restart from beginning of articles.
1360 * If no more responses can be found, return -1
1361 */
1362int
1364 int n)
1365{
1366 int cur_base_art = n;
1367
1368 while (n >= 0) {
1369 if (((arts[n].status == ART_UNREAD) || (arts[n].status == ART_WILL_RETURN)) && arts[n].thread != ART_EXPIRED)
1370 return n;
1371
1372 n = next_response(n);
1373 }
1374
1376 n = (int) base[0];
1377 while (n != cur_base_art && n >= 0) {
1378 if (((arts[n].status == ART_UNREAD) || (arts[n].status == ART_WILL_RETURN)) && arts[n].thread != ART_EXPIRED)
1379 return n;
1380
1381 n = next_response(n);
1382 }
1383 }
1384
1385 return -1;
1386}
1387
1388
1389/*
1390 * Find the previous unread response in this thread
1391 */
1392int
1394 int n)
1395{
1396 while (n >= 0) {
1397 if (arts[n].status != ART_READ && arts[n].thread != ART_EXPIRED)
1398 return n;
1399
1400 n = prev_response(n);
1401 }
1402
1403 return -1;
1404}
1405
1406
1407static t_bool
1409 struct t_msgid *ptr)
1410{
1411 return ptr && (!IS_EXPIRED(ptr) || find_unexpired(ptr->child) || find_unexpired(ptr->sibling));
1412}
1413
1414
1415static t_bool
1417 struct t_msgid *ptr)
1418{
1419 do {
1420 if (find_unexpired(ptr->sibling))
1421 return TRUE;
1422 ptr = ptr->parent;
1423 } while (ptr && IS_EXPIRED(ptr));
1424 return FALSE;
1425}
1426
1427
1428/*
1429 * mutt-like subject according. by sjpark@sparcs.kaist.ac.kr
1430 * string in prefix will be overwritten up to length len prefix will always
1431 * be terminated with \0
1432 * make sure prefix is at least len+1 bytes long (to hold the terminating
1433 * null byte)
1434 */
1435static void
1437 struct t_msgid *art,
1438 char *prefix,
1439 int maxlen)
1440{
1441#if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
1442 char *result;
1443 wchar_t *buf, *buf2;
1444#else
1445 char *buf;
1446#endif /* MULTIBYTE_ABLE && !NO_LOCALE */
1447 int prefix_ptr;
1448 int depth = 0;
1449 int depth_level = 0;
1450 struct t_msgid *ptr;
1451
1452 for (ptr = art->parent; ptr; ptr = ptr->parent)
1453 depth += (!IS_EXPIRED(ptr) ? 1 : 0);
1454
1455 if ((depth == 0) || (maxlen < 1)) {
1456 prefix[0] = '\0';
1457 return;
1458 }
1459
1460 prefix_ptr = depth * 2 - 1;
1461
1462 if (prefix_ptr > maxlen - 1 - !(maxlen % 2)) {
1463 int odd = ((maxlen % 2) ? 0 : 1);
1464
1465 prefix_ptr -= maxlen - ++depth_level - 2 - odd;
1466
1467 while (prefix_ptr > maxlen - 2 - odd) {
1468 if (depth_level < maxlen / 5)
1469 depth_level++;
1470 prefix_ptr -= maxlen - depth_level - 2 - odd;
1471 odd = (odd ? 0 : 1);
1472 }
1473 }
1474
1475#if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
1476 buf = my_malloc(sizeof(wchar_t) * (size_t) prefix_ptr + 3 * sizeof(wchar_t));
1477 buf[prefix_ptr + 2] = (wchar_t) '\0';
1478#else
1479 buf = my_malloc(prefix_ptr + 3);
1480 buf[prefix_ptr + 2] = '\0';
1481#endif /* MULTIBYTE_ABLE && !NO_LOCALE */
1482 buf[prefix_ptr + 1] = TREE_ARROW;
1483 buf[prefix_ptr] = TREE_HORIZ;
1484 buf[--prefix_ptr] = (has_sibling(art) ? TREE_VERT_RIGHT : TREE_UP_RIGHT);
1485
1486 for (ptr = art->parent; prefix_ptr > 1; ptr = ptr->parent) {
1487 if (IS_EXPIRED(ptr))
1488 continue;
1489 buf[--prefix_ptr] = TREE_BLANK;
1490 buf[--prefix_ptr] = (has_sibling(ptr) ? TREE_VERT : TREE_BLANK);
1491 }
1492
1493 while (depth_level)
1494 buf[--depth_level] = TREE_ARROW_WRAP;
1495
1496#if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
1497 buf2 = wcspart(buf, maxlen, FALSE);
1498 result = wchar_t2char(buf2);
1499 strcpy(prefix, result);
1500 free(buf);
1501 FreeIfNeeded(buf2);
1502 FreeIfNeeded(result);
1503#else
1504 strncpy(prefix, buf, maxlen);
1505 prefix[maxlen] = '\0'; /* just in case strlen(buf) > maxlen */
1506 free(buf);
1507#endif /* MULTIBYTE_ABLE && !NO_LOCALE */
1508}
1509
1510
1511/*
1512 * There are 3 catchup methods:
1513 * When exiting thread via <-
1514 * Catchup thread, move to next one
1515 * Catchup thread and enter next one with unread arts
1516 * Return a suitable ret_code
1517 */
1518static int
1521 struct t_group *group)
1522{
1523 char buf[LEN];
1524 int i, n;
1525 int pyn = 1;
1526
1527 /* Find first unread art in this thread */
1529 for (i = n; i != -1; i = arts[i].thread) {
1530 if ((arts[i].status == ART_UNREAD) || (arts[i].status == ART_WILL_RETURN))
1531 break;
1532 }
1533
1534 if (i != -1) { /* still unread arts in this thread */
1535 if (group->attribute->thread_articles == THREAD_NONE)
1537 else
1539 if ((!TINRC_CONFIRM_ACTION) || (pyn = prompt_yn(buf, TRUE)) == 1)
1541 }
1542
1543 switch (func) {
1544 case CATCHUP: /* 'c' */
1545 if (pyn == 1)
1546 return GRP_NEXT;
1547 break;
1548
1549 case CATCHUP_NEXT_UNREAD: /* 'C' */
1550 if (pyn == 1)
1551 return GRP_NEXTUNREAD;
1552 break;
1553
1554 case SPECIAL_CATCHUP_LEFT: /* <- thread catchup on exit */
1555 switch (pyn) {
1556 case -1: /* ESC from prompt, stay in group */
1557 break;
1558
1559 case 1: /* We caught up - advance group */
1560 return GRP_NEXT;
1561
1562 default: /* Just leave the group */
1563 return GRP_EXIT;
1564 }
1565 /* FALLTHROUGH */
1566 default:
1567 break;
1568 }
1569 return 0; /* Default is to stay in current screen */
1570}
1571
1572
1573/*
1574 * This is the single entry point into the article pager
1575 * art
1576 * is the arts[art] we wish to read
1577 * ignore_unavail
1578 * should be set if we wish to keep going after article unavailable
1579 * level
1580 * is the menu from which we came. This should be only be GROUP or THREAD
1581 * it is used to set the return code to go back to the calling menu when
1582 * not explicitly set
1583 * Return:
1584 * <0 to quit to group menu
1585 * 0 to stay in thread menu
1586 * >0 after normal exit from pager to return to previous menu level
1587 */
1588static int
1590 int art,
1591 t_bool ignore_unavail,
1592 int level)
1593{
1594 int i;
1595
1596again:
1597 switch ((i = show_page(curr_group, art, &thdmenu.curr))) {
1598 /* These exit to previous menu level */
1599 case GRP_QUIT: /* 'Q' all the way out */
1600 case GRP_EXIT: /* back to group menu */
1601 case GRP_RETSELECT: /* 'T' back to select menu */
1602 case GRP_NEXT: /* 'c' Move to next thread on group menu */
1603 case GRP_NEXTUNREAD: /* 'C' */
1604 case GRP_KILLED: /* article/thread was killed at page level */
1605 break;
1606
1607 case GRP_ARTABORT: /* user 'q'uit load of article */
1608 /* break forces return to group menu */
1609 if (level == GROUP_LEVEL)
1610 break;
1611 /* else stay on thread menu */
1613 return 0;
1614
1615 /* Keeps us in thread menu */
1616 case GRP_ARTUNAVAIL:
1617 if (ignore_unavail && (art = next_unread(art)) != -1)
1618 goto again;
1619 else if (level == GROUP_LEVEL)
1620 return GRP_ARTABORT;
1621 /* back to thread menu */
1623 return 0;
1624
1625 case GRP_GOTOTHREAD: /* 'l' from pager */
1628 return 0;
1629
1630 default: /* >=0 normal exit, new basenote */
1632
1633 if (currmenu != &grpmenu) /* group menu will redraw itself */
1634 currmenu->redraw();
1635
1636 return 1; /* Must return any +ve integer */
1637 }
1638 return i;
1639}
1640
1641
1642/*
1643 * Find index in arts[] of next unread article _IN_THIS_THREAD_
1644 * Page it or return GRP_NEXTUNREAD if thread is all read
1645 * (to tell group menu to skip to next thread)
1646 */
1647static int
1649 void)
1650{
1651 int i, n;
1652
1653 /*
1654 * Find current position in thread
1655 */
1657
1658 /*
1659 * Find and display next unread
1660 */
1661 for (i = n; i != -1; i = arts[i].thread) {
1662 if ((arts[i].status == ART_UNREAD) || (arts[i].status == ART_WILL_RETURN))
1663 return (enter_pager(i, TRUE, THREAD_LEVEL));
1664 }
1665
1666 /*
1667 * We ran out of thread, tell group.c to enter the next with unread
1668 */
1669 return GRP_NEXTUNREAD;
1670}
1671
1672
1673/*
1674 * Redraw all necessary parts of the screen after FEED_MARK_(UN)READ
1675 * Move cursor to next unread item if needed
1676 *
1677 * Returns TRUE when no next unread art, FALSE otherwise
1678 */
1679t_bool
1681 int function,
1682 t_function feed_type,
1683 int respnum)
1684{
1685#if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
1686 wchar_t mark[] = { L'\0', L'\0' };
1687#else
1688 char mark[] = { '\0', '\0' };
1689#endif /* MULTIBYTE_ABLE && !NO_LOCALE */
1690 int n;
1691
1692 switch (function) {
1693 case (FEED_MARK_READ):
1694 if (feed_type == FEED_ARTICLE) {
1695 mark[0] = get_art_mark(&arts[respnum]);
1696#if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
1698 mark_screen(thdmenu.curr, mark_offset + (art_mark_width - wcwidth(mark[0])), mark);
1699#else
1701#endif /* MULTIBYTE_ABLE && !NO_LOCALE */
1702 } else
1704
1705 if ((n = next_unread(respnum)) == -1) /* no more unread articles */
1706 return TRUE;
1707 else
1708 fixup_thread(n, TRUE); /* We may be in the next thread now */
1709 break;
1710
1711 case (FEED_MARK_UNREAD):
1712 if (feed_type == FEED_ARTICLE) {
1713 mark[0] = get_art_mark(&arts[respnum]);
1714#if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
1716 mark_screen(thdmenu.curr, mark_offset + (art_mark_width - wcwidth(mark[0])), mark);
1717#else
1719#endif /* MULTIBYTE_ABLE && !NO_LOCALE */
1721 } else
1723 break;
1724
1725 default:
1726 break;
1727 }
1728 return FALSE;
1729}
unsigned t_bool
Definition: bool.h:77
#define bool_not(b)
Definition: bool.h:81
#define TRUE
Definition: bool.h:74
#define FALSE
Definition: bool.h:70
static t_openartinfo * art
Definition: cook.c:78
#define DEBUG_REFS
Definition: debug.h:51
#define DEBUG_FILTER
Definition: debug.h:49
constext txt_thread_upper[]
Definition: lang.c:890
int this_resp
Definition: page.c:71
t_function last_search
Definition: init.c:117
constext txt_bad_command[]
Definition: lang.c:112
constext txt_cannot_post[]
Definition: lang.c:130
constext txt_info_all_parts_untagged[]
Definition: lang.c:550
int NOTESLINES
Definition: signal.c:111
t_openartinfo pgart
Definition: page.c:63
constext txt_mark_art_read[]
Definition: lang.c:634
constext txt_no_resps_in_thread[]
Definition: lang.c:697
constext txt_mark_thread_read[]
Definition: lang.c:636
t_menu grpmenu
Definition: group.c:83
struct t_article * arts
Definition: memory.c:69
constext txt_article_singular[]
Definition: lang.c:75
constext txt_stp_list_thread[]
Definition: lang.c:859
int art_mark_width
Definition: init.c:118
constext txt_no_last_message[]
Definition: lang.c:686
constext txt_select_art[]
Definition: lang.c:849
constext txt_prefix_untagged[]
Definition: lang.c:748
int num_of_tagged_arts
Definition: tags.c:47
int filter_file_offset
Definition: filter.c:93
char filter_file[PATH_LEN]
Definition: init.c:89
t_bool range_active
Definition: init.c:148
char cvers[LEN]
Definition: init.c:70
int signal_context
Definition: signal.c:105
constext txt_reading_article[]
Definition: lang.c:772
int mark_offset
Definition: screen.c:48
constext txt_no_responses[]
Definition: lang.c:696
t_menu * currmenu
Definition: init.c:166
t_artnum * base
Definition: memory.c:65
constext txt_no_prev_search[]
Definition: lang.c:694
constext txt_marked_as_unread[]
Definition: lang.c:629
int cCOLS
Definition: curses.c:53
constext txt_thread_com[]
Definition: lang.c:891
constext txt_stp_thread[]
Definition: lang.c:860
constext txt_info_all_parts_tagged[]
Definition: lang.c:549
t_bool can_post
Definition: nntplib.c:32
constext txt_prefix_tagged[]
Definition: lang.c:747
constext txt_end_of_thread[]
Definition: lang.c:172
constext txt_enter_next_thread[]
Definition: lang.c:176
struct t_group * curr_group
Definition: group.c:55
struct t_config tinrc
Definition: init.c:192
unsigned short debug
Definition: debug.c:51
struct t_screen * screen
Definition: screen.c:51
constext txt_enter_next_unread_art[]
Definition: lang.c:177
t_function global_mouse_action(t_function(*left_action)(void), t_function(*right_action)(void))
Definition: global.c:321
#define MAXKEYLEN
Definition: keymap.h:136
t_function handle_keypad(t_function(*left_action)(void), t_function(*right_action)(void), t_function(*mouse_action)(t_function(*left_action)(void), t_function(*right_action)(void)), const struct keylist keys)
Definition: global.c:355
struct keylist thread_keys
Definition: keymap.c:90
@ GLOBAL_SCROLL_UP
Definition: keymap.h:214
@ THREAD_SELECT_ARTICLE
Definition: keymap.h:366
@ DIGIT_7
Definition: keymap.h:157
@ GLOBAL_SHELL_ESCAPE
Definition: keymap.h:223
@ THREAD_MARK_ARTICLE_READ
Definition: keymap.h:361
@ DIGIT_3
Definition: keymap.h:153
@ GLOBAL_PAGE_UP
Definition: keymap.h:201
@ GLOBAL_SET_RANGE
Definition: keymap.h:221
@ FEED_ARTICLE
Definition: keymap.h:176
@ DIGIT_6
Definition: keymap.h:156
@ THREAD_READ_ARTICLE
Definition: keymap.h:363
@ GLOBAL_PIPE
Definition: keymap.h:202
@ GLOBAL_POST
Definition: keymap.h:203
@ GLOBAL_SEARCH_SUBJECT_FORWARD
Definition: keymap.h:220
@ THREAD_READ_NEXT_ARTICLE_OR_THREAD
Definition: keymap.h:362
@ THREAD_TOGGLE_SUBJECT_DISPLAY
Definition: keymap.h:370
@ GLOBAL_LINE_DOWN
Definition: keymap.h:194
@ GLOBAL_SCROLL_DOWN
Definition: keymap.h:213
@ THREAD_CANCEL
Definition: keymap.h:359
@ GLOBAL_HELP
Definition: keymap.h:191
@ GLOBAL_LOOKUP_MESSAGEID
Definition: keymap.h:196
@ DIGIT_2
Definition: keymap.h:152
@ THREAD_TOGGLE_ARTICLE_SELECTION
Definition: keymap.h:369
@ GLOBAL_PRINT
Definition: keymap.h:206
@ GLOBAL_SEARCH_SUBJECT_BACKWARD
Definition: keymap.h:219
@ MARK_FEED_UNREAD
Definition: keymap.h:264
@ FEED_RANGE
Definition: keymap.h:180
@ GLOBAL_TOGGLE_INVERSE_VIDEO
Definition: keymap.h:230
@ GLOBAL_TOGGLE_HELP_DISPLAY
Definition: keymap.h:228
@ GLOBAL_VERSION
Definition: keymap.h:231
@ MARK_FEED_READ
Definition: keymap.h:263
@ THREAD_REVERSE_SELECTIONS
Definition: keymap.h:364
@ DIGIT_9
Definition: keymap.h:159
@ NOT_ASSIGNED
Definition: keymap.h:149
@ GLOBAL_POSTPONED
Definition: keymap.h:204
@ THREAD_UNTAG
Definition: keymap.h:372
@ GLOBAL_SEARCH_AUTHOR_BACKWARD
Definition: keymap.h:217
@ GLOBAL_PAGE_DOWN
Definition: keymap.h:200
@ SPECIAL_CATCHUP_LEFT
Definition: keymap.h:167
@ GLOBAL_EDIT_FILTER
Definition: keymap.h:189
@ GLOBAL_ABORT
Definition: keymap.h:186
@ GLOBAL_SEARCH_REPEAT
Definition: keymap.h:216
@ GLOBAL_SEARCH_AUTHOR_FORWARD
Definition: keymap.h:218
@ GLOBAL_QUIT
Definition: keymap.h:210
@ GLOBAL_FIRST_PAGE
Definition: keymap.h:190
@ GLOBAL_DISPLAY_POST_HISTORY
Definition: keymap.h:188
@ GLOBAL_REDRAW_SCREEN
Definition: keymap.h:212
@ DIGIT_8
Definition: keymap.h:158
@ GLOBAL_TOGGLE_INFO_LAST_LINE
Definition: keymap.h:229
@ GLOBAL_LAST_PAGE
Definition: keymap.h:192
@ DIGIT_1
Definition: keymap.h:151
@ THREAD_SAVE
Definition: keymap.h:365
@ GLOBAL_LINE_UP
Definition: keymap.h:195
@ GLOBAL_SEARCH_BODY
Definition: keymap.h:215
@ DIGIT_4
Definition: keymap.h:154
@ THREAD_AUTOSAVE
Definition: keymap.h:358
@ THREAD_MAIL
Definition: keymap.h:360
@ GLOBAL_OPTION_MENU
Definition: keymap.h:199
@ GLOBAL_MENU_FILTER_SELECT
Definition: keymap.h:198
@ DIGIT_5
Definition: keymap.h:155
@ GLOBAL_QUIT_TIN
Definition: keymap.h:211
@ CATCHUP_NEXT_UNREAD
Definition: keymap.h:170
@ MARK_ARTICLE_UNREAD
Definition: keymap.h:261
@ THREAD_TAG
Definition: keymap.h:367
@ THREAD_UNDO_SELECTIONS
Definition: keymap.h:371
@ GLOBAL_MENU_FILTER_KILL
Definition: keymap.h:197
@ MARK_THREAD_UNREAD
Definition: keymap.h:262
@ THREAD_TAG_PARTS
Definition: keymap.h:368
@ CATCHUP
Definition: keymap.h:169
@ GLOBAL_BUGREPORT
Definition: keymap.h:187
@ GLOBAL_LAST_VIEWED
Definition: keymap.h:193
char func_to_key(t_function func, const struct keylist keys)
Definition: keymap.c:125
enum defined_functions t_function
Definition: keymap.h:375
#define PrintFuncKey(buf, func, keys)
Definition: keymap.h:445
static char buf[16]
Definition: langinfo.c:50
void scroll_down(void)
Definition: global.c:252
void move_to_item(int n)
Definition: global.c:227
void page_up(void)
Definition: global.c:130
void prompt_item_num(int ch, const char *prompt)
Definition: global.c:200
void draw_arrow_mark(int line)
Definition: screen.c:352
t_bool pickup_postponed_articles(t_bool ask, t_bool all)
Definition: post.c:2904
void show_mini_help(int level)
Definition: help.c:798
void make_threads(struct t_group *group, t_bool rethread)
Definition: art.c:1229
t_bool filter_articles(struct t_group *group)
Definition: filter.c:1843
int tag_multipart(int arts_index)
Definition: tags.c:57
t_bool tag_article(int art)
Definition: tags.c:138
t_bool read_filter_file(const char *file)
Definition: filter.c:308
int find_artnum(t_artnum art)
Definition: art.c:3223
void show_help_page(const int level, const char *title)
Definition: help.c:734
int get_initials(struct t_article *art, char *s, int maxsize)
Definition: misc.c:1993
size_t my_strftime(char *s, size_t maxsize, const char *format, struct tm *timeptr)
Definition: strftime.c:64
t_bool set_range(int level, int min, int max, int curr)
Definition: tags.c:214
int global_get_multipart_info(int aindex, MultiPartInfo *setme)
Definition: art.c:976
void error_message(unsigned int sdelay, const char *fmt,...)
Definition: screen.c:224
void art_mark(struct t_group *group, struct t_article *art, int flag)
Definition: newsrc.c:1611
void parse_format_string(const char *fmtstr, struct t_fmt *fmt)
Definition: string.c:1437
void get_author(t_bool thread, struct t_article *art, char *str, size_t len)
Definition: misc.c:1011
char * strip_line(char *line)
Definition: misc.c:3644
void ClearScreen(void)
Definition: curses.c:410
void do_shell_escape(void)
Definition: misc.c:547
void set_xclick_on(void)
Definition: curses.c:691
void scroll_up(void)
Definition: global.c:278
void config_page(const char *grpname, enum context level)
Definition: options_menu.c:939
t_bool filter_menu(t_function type, struct t_group *group, struct t_article *art)
Definition: filter.c:1061
void info_message(const char *fmt,...)
Definition: screen.c:102
void toggle_inverse_video(void)
Definition: misc.c:1050
int feed_articles(int function, int level, t_function type, struct t_group *group, int respnum)
Definition: feed.c:566
char * convert_to_printable(char *buf, t_bool keep_tab)
Definition: charset.c:394
void show_inverse_video_status(void)
Definition: misc.c:1065
void pos_first_unread_thread(void)
Definition: group.c:1130
int prompt_msgid(void)
Definition: prompt.c:621
void toggle_mini_help(int level)
Definition: help.c:1078
int show_page(struct t_group *group, int start_respnum, int *threadnum)
Definition: page.c:306
void draw_mark_selected(int i)
Definition: misc.c:4179
t_bool invoke_editor(const char *filename, int lineno, struct t_group *group)
Definition: misc.c:377
void art_close(t_openartinfo *artinfo)
Definition: rfc2046.c:1623
void thd_mark_unread(struct t_group *group, long thread)
Definition: newsrc.c:806
t_bool untag_all_articles(void)
Definition: tags.c:179
t_bool post_hist_page(void)
Definition: post.c:519
void page_down(void)
Definition: global.c:155
void move_up(void)
Definition: global.c:81
void set_xclick_off(void)
Definition: curses.c:703
void clear_note_area(void)
Definition: group.c:1051
void move_down(void)
Definition: global.c:110
int search_body(struct t_group *group, int current_art, t_bool repeat)
Definition: search.c:722
int strwidth(const char *str)
Definition: string.c:1050
void set_first_screen_item(void)
Definition: global.c:61
void end_of_list(void)
Definition: global.c:191
void unfilter_articles(struct t_group *group)
Definition: filter.c:1818
t_bool post_article(const char *groupname)
Definition: post.c:2994
void thd_mark_read(struct t_group *group, long thread)
Definition: newsrc.c:789
int art_open(t_bool wrap_lines, struct t_article *art, struct t_group *group, t_openartinfo *artinfo, t_bool show_progress_meter, const char *pmesg)
Definition: rfc2046.c:1566
int search(t_function func, int current_art, t_bool repeat)
Definition: search.c:564
t_bool cancel_article(struct t_group *group, struct t_article *art, int respnum)
Definition: post.c:4222
void mark_screen(int screen_row, int screen_col, const char *value)
Definition: group.c:1147
void show_title(const char *title)
Definition: screen.c:457
void top_of_list(void)
Definition: global.c:182
int prompt_yn(const char *prompt, t_bool default_answer)
Definition: prompt.c:165
char * fmt_string(const char *fmt,...)
Definition: string.c:1386
char * tin_ltoa(t_artnum value, int digits)
Definition: string.c:80
void bug_report(void)
Definition: global.c:430
void(* func)(SIG_ARGS)
Definition: signal.c:176
int subject_compare_len
Definition: tin.h:2045
int total
Definition: tin.h:2047
int deleted
Definition: tin.h:1996
int multipart_total
Definition: tin.h:2003
int score
Definition: tin.h:2002
int selected_unread
Definition: tin.h:1999
int selected_seen
Definition: tin.h:2000
time_t time
Definition: tin.h:2006
int inrange
Definition: tin.h:1997
char art_mark
Definition: tin.h:1991
int killed
Definition: tin.h:2001
int unread
Definition: tin.h:1994
int selected_total
Definition: tin.h:1998
int total
Definition: tin.h:1993
int multipart_compare_len
Definition: tin.h:2005
int seen
Definition: tin.h:1995
int multipart_have
Definition: tin.h:2004
Definition: tin.h:1533
time_t date
Definition: tin.h:1544
char * subject
Definition: tin.h:1535
int prev
Definition: tin.h:1549
int thread
Definition: tin.h:1548
int score
Definition: tin.h:1550
t_artnum artnum
Definition: tin.h:1534
t_bool selected
Definition: tin.h:1555
unsigned int status
Definition: tin.h:1551
unsigned thread_articles
Definition: tin.h:1677
char * mailing_list
Definition: tin.h:1626
unsigned mark_ignore_tags
Definition: tin.h:1663
char * thread_format
Definition: tin.h:1618
unsigned show_author
Definition: tin.h:1680
unsigned pos_first_unread
Definition: tin.h:1665
unsigned thread_catchup_on_exit
Definition: tin.h:1678
unsigned wrap_on_next_unread
Definition: tin.h:1693
t_bool strip_blanks
Definition: tinrc.h:249
char art_marked_return
Definition: tinrc.h:72
t_bool draw_arrow
Definition: tinrc.h:223
char art_marked_selected
Definition: tinrc.h:73
int kill_level
Definition: tinrc.h:151
char art_marked_recent
Definition: tinrc.h:74
char art_marked_unread
Definition: tinrc.h:75
int thread_score
Definition: tinrc.h:166
char art_marked_read
Definition: tinrc.h:76
char art_marked_killed
Definition: tinrc.h:77
char art_marked_deleted
Definition: tinrc.h:70
char art_marked_inrange
Definition: tinrc.h:71
char art_marked_read_selected
Definition: tinrc.h:78
int recent_time
Definition: tinrc.h:148
t_bool info_in_last_line
Definition: tinrc.h:226
int score_select
Definition: tinrc.h:171
Definition: tin.h:1853
size_t len_msgid
Definition: tin.h:1866
size_t len_from
Definition: tin.h:1859
size_t len_linenumber
Definition: tin.h:1864
size_t len_linecnt
Definition: tin.h:1865
size_t len_initials
Definition: tin.h:1863
char str[1024]
Definition: tin.h:1854
size_t len_score
Definition: tin.h:1868
size_t len_subj
Definition: tin.h:1869
size_t mark_offset
Definition: tin.h:1872
size_t len_date_max
Definition: tin.h:1857
char date_str[1024]
Definition: tin.h:1855
Definition: tin.h:1816
struct t_attribute * attribute
Definition: tin.h:1834
char * name
Definition: tin.h:1817
Definition: tin.h:2055
int curr
Definition: tin.h:2056
void(* redraw)(void)
Definition: tin.h:2059
int first
Definition: tin.h:2058
int max
Definition: tin.h:2057
Definition: tin.h:1509
struct t_msgid * sibling
Definition: tin.h:1512
struct t_msgid * child
Definition: tin.h:1513
struct t_msgid * parent
Definition: tin.h:1511
int article
Definition: tin.h:1514
int art
Definition: tin.h:2069
t_bool ignore_unavail
Definition: tin.h:2070
char * col
Definition: tin.h:1974
#define my_flush()
Definition: tcurses.h:177
#define cCRLF
Definition: tcurses.h:156
#define WriteLine(row, buffer)
Definition: tcurses.h:180
#define my_retouch()
Definition: tcurses.h:179
static int thread_catchup(t_function func, struct t_group *group)
Definition: thread.c:1519
static void draw_thread_arrow(void)
Definition: thread.c:1003
static void show_thread_page(void)
Definition: thread.c:922
static char get_art_mark(struct t_article *art)
Definition: thread.c:97
void fixup_thread(int respnum, t_bool redraw)
Definition: thread.c:1020
static void build_tline(int l, struct t_article *art)
Definition: thread.c:128
static struct t_fmt thrd_fmt
Definition: thread.c:53
int num_of_responses(int n)
Definition: thread.c:1124
static int thread_tab_pressed(void)
Definition: thread.c:1648
static void make_prefix(struct t_msgid *art, char *prefix, int maxlen)
Definition: thread.c:1436
static t_bool find_unexpired(struct t_msgid *ptr)
Definition: thread.c:1408
int which_response(int n)
Definition: thread.c:1099
#define IS_EXPIRED(a)
Definition: thread.c:49
static int thread_respnum
Definition: thread.c:52
int prev_unread(int n)
Definition: thread.c:1393
static t_function thread_left(void)
Definition: thread.c:413
static int enter_pager(int art, t_bool ignore_unavail, int level)
Definition: thread.c:1589
static void update_thread_page(void)
Definition: thread.c:958
int next_thread(int n)
Definition: thread.c:1302
static t_bool has_sibling(struct t_msgid *ptr)
Definition: thread.c:1416
int stat_thread(int n, struct t_art_stat *sbuf)
Definition: thread.c:1192
int new_responses(int thread)
Definition: thread.c:1045
int prev_response(int n)
Definition: thread.c:1321
t_bool show_subject
Definition: thread.c:54
int find_response(int i, int n)
Definition: thread.c:1342
int thread_basenote
Definition: thread.c:51
int next_response(int n)
Definition: thread.c:1280
int which_thread(int n)
Definition: thread.c:1073
static int ret_code
Definition: thread.c:87
int get_score_of_thread(int n)
Definition: thread.c:1154
static t_function thread_right(void)
Definition: thread.c:424
t_bool thread_mark_postprocess(int function, t_function feed_type, int respnum)
Definition: thread.c:1680
static void draw_thread_item(int item)
Definition: thread.c:405
static t_menu thdmenu
Definition: thread.c:84
int next_unread(int n)
Definition: thread.c:1363
int thread_page(struct t_group *group, int respnum, int thread_depth, t_pagerinfo *page)
Definition: thread.c:446
#define TINRC_CONFIRM_ACTION
Definition: tin.h:958
#define LEN
Definition: tin.h:860
long t_artnum
Definition: tin.h:229
#define TREE_BLANK
Definition: tin.h:940
#define THREAD_NONE
Definition: tin.h:1139
@ cThread
Definition: tin.h:107
#define ART_KILLED_UNREAD
Definition: tin.h:1355
#define TREE_VERT
Definition: tin.h:943
#define FEED_PIPE
Definition: tin.h:1127
#define THREAD_BOTH
Definition: tin.h:1142
#define MIN(a, b)
Definition: tin.h:811
#define FEED_AUTOSAVE
Definition: tin.h:1130
#define GROUP_LEVEL
Definition: tin.h:1113
#define FEED_SAVE
Definition: tin.h:1129
#define THREAD_SCORE_MAX
Definition: tin.h:1161
#define DAY
Definition: tin.h:870
#define TREE_ARROW
Definition: tin.h:938
#define INDEX2LNUM(i)
Definition: tin.h:1020
#define FEED_PRINT
Definition: tin.h:1128
#define my_malloc(size)
Definition: tin.h:2245
#define FreeIfNeeded(p)
Definition: tin.h:2252
#define INDEX2SNUM(i)
Definition: tin.h:1022
#define TREE_HORIZ
Definition: tin.h:941
#define SHOW_FROM_NONE
Definition: tin.h:1153
#define ART_ABORT
Definition: tin.h:1360
#define THREAD_SCORE_WEIGHT
Definition: tin.h:1163
#define _(Text)
Definition: tin.h:94
#define KILL_NOTHREAD
Definition: tin.h:1219
#define for_each_art_in_thread(x, y)
Definition: tin.h:2261
#define FEED_MARK_UNREAD
Definition: tin.h:1133
#define snprintf
Definition: tin.h:2464
#define THREAD_REFS
Definition: tin.h:1141
#define TREE_ARROW_WRAP
Definition: tin.h:939
#define TREE_VERT_RIGHT
Definition: tin.h:944
#define ART_EXPIRED
Definition: tin.h:1335
#define ART_WILL_RETURN
Definition: tin.h:1347
#define INDEX_TOP
Definition: tin.h:1019
#define FEED_MAIL
Definition: tin.h:1126
#define ART_READ
Definition: tin.h:1345
#define THREAD_SUBJ
Definition: tin.h:1140
#define FEED_MARK_READ
Definition: tin.h:1132
#define THREAD_LEVEL
Definition: tin.h:1114
@ GRP_EXIT
Definition: tin.h:1294
@ GRP_NEXTUNREAD
Definition: tin.h:1287
@ GRP_NEXT
Definition: tin.h:1288
@ GRP_RETSELECT
Definition: tin.h:1285
@ GRP_GOTOTHREAD
Definition: tin.h:1292
@ GRP_QUIT
Definition: tin.h:1286
@ GRP_KILLED
Definition: tin.h:1291
@ GRP_ARTUNAVAIL
Definition: tin.h:1289
@ GRP_ARTABORT
Definition: tin.h:1290
#define THREAD_MULTI
Definition: tin.h:1143
#define assert(p)
Definition: tin.h:1320
#define ART_UNREAD
Definition: tin.h:1346
#define ART_UNAVAILABLE
Definition: tin.h:1348
#define TREE_UP_RIGHT
Definition: tin.h:942
#define THREAD_PERC
Definition: tin.h:1144
#define SHOW_FROM_BOTH
Definition: tin.h:1156