1 #if !defined(lint) && !defined(DOS)
2 static char rcsid[] = "$Id: thread.c 942 2008-03-04 18:21:33Z hubert@u.washington.edu $";
3 #endif
4
5 /*
6 * ========================================================================
7 * Copyright 2013-2021 Eduardo Chappa
8 * Copyright 2006-2008 University of Washington
9 *
10 * Licensed under the Apache License, Version 2.0 (the "License");
11 * you may not use this file except in compliance with the License.
12 * You may obtain a copy of the License at
13 *
14 * http://www.apache.org/licenses/LICENSE-2.0
15 *
16 * ========================================================================
17 */
18
19 #include "../pith/headers.h"
20 #include "../pith/thread.h"
21 #include "../pith/flag.h"
22 #include "../pith/icache.h"
23 #include "../pith/mailindx.h"
24 #include "../pith/msgno.h"
25 #include "../pith/sort.h"
26 #include "../pith/pineelt.h"
27 #include "../pith/status.h"
28 #include "../pith/news.h"
29 #include "../pith/search.h"
30 #include "../pith/mailcmd.h"
31 #include "../pith/ablookup.h"
32
33
34 /*
35 * Internal prototypes
36 */
37 long *sort_thread_flatten(THREADNODE *, MAILSTREAM *, long *,
38 char *, long, PINETHRD_S *, unsigned);
39 void make_thrdflags_consistent(MAILSTREAM *, MSGNO_S *, PINETHRD_S *, int);
40 THREADNODE *collapse_threadnode_tree(THREADNODE *);
41 THREADNODE *collapse_threadnode_tree_sorted(THREADNODE *);
42 THREADNODE *sort_threads_and_collapse(THREADNODE *);
43 THREADNODE *insert_tree_in_place(THREADNODE *, THREADNODE *);
44 unsigned long branch_greatest_num(THREADNODE *, int);
45 long calculate_visible_threads(MAILSTREAM *);
46
47
48 PINETHRD_S *
fetch_thread(MAILSTREAM * stream,long unsigned int rawno)49 fetch_thread(MAILSTREAM *stream, long unsigned int rawno)
50 {
51 MESSAGECACHE *mc;
52 PINELT_S *pelt;
53 PINETHRD_S *thrd = NULL;
54
55 if(stream && rawno > 0L && rawno <= stream->nmsgs
56 && !sp_need_to_rethread(stream)){
57 mc = (rawno > 0L && stream && rawno <= stream->nmsgs)
58 ? mail_elt(stream, rawno) : NULL;
59 if(mc && (pelt = (PINELT_S *) mc->sparep))
60 thrd = pelt->pthrd;
61 }
62
63 return(thrd);
64 }
65
66
67 PINETHRD_S *
fetch_head_thread(MAILSTREAM * stream)68 fetch_head_thread(MAILSTREAM *stream)
69 {
70 unsigned long rawno;
71 PINETHRD_S *thrd = NULL;
72
73 if(stream){
74 /* first find any thread */
75 for(rawno = 1L; !thrd && rawno <= stream->nmsgs; rawno++)
76 thrd = fetch_thread(stream, rawno);
77
78 if(thrd && thrd->head)
79 thrd = fetch_thread(stream, thrd->head);
80 }
81
82 return(thrd);
83 }
84
85
86 /*
87 * Set flag f to v for all messages in thrd.
88 *
89 * Watch out when calling this. The thrd->branch is not part of thrd.
90 * Branch is a sibling to thrd, not a child. Zero out branch before calling
91 * or call on thrd->next and worry about thrd separately.
92 * Ok to call it on top-level thread which has no branch already.
93 */
94 void
set_flags_for_thread(MAILSTREAM * stream,MSGNO_S * msgmap,int f,PINETHRD_S * thrd,int v)95 set_flags_for_thread(MAILSTREAM *stream, MSGNO_S *msgmap, int f, PINETHRD_S *thrd, int v)
96 {
97 PINETHRD_S *nthrd, *bthrd;
98
99 if(!(stream && thrd && msgmap))
100 return;
101
102 set_lflag(stream, msgmap, mn_raw2m(msgmap, thrd->rawno), f, v);
103
104 if(thrd->next){
105 nthrd = fetch_thread(stream, thrd->next);
106 if(nthrd)
107 set_flags_for_thread(stream, msgmap, f, nthrd, v);
108 }
109
110 if(thrd->branch){
111 bthrd = fetch_thread(stream, thrd->branch);
112 if(bthrd)
113 set_flags_for_thread(stream, msgmap, f, bthrd, v);
114 }
115 }
116
117
118 void
erase_threading_info(MAILSTREAM * stream,MSGNO_S * msgmap)119 erase_threading_info(MAILSTREAM *stream, MSGNO_S *msgmap)
120 {
121 unsigned long n;
122 MESSAGECACHE *mc;
123 PINELT_S *peltp;
124
125 if(!(stream && stream->spare))
126 return;
127
128 ps_global->view_skipped_index = 0;
129 sp_set_viewing_a_thread(stream, 0);
130
131 if(THRD_INDX())
132 setup_for_thread_index_screen();
133 else
134 setup_for_index_index_screen();
135
136 stream->spare = 0;
137
138 for(n = 1L; n <= stream->nmsgs; n++){
139 set_lflag(stream, msgmap, mn_raw2m(msgmap, n),
140 MN_COLL | MN_CHID | MN_CHID2, 0);
141 mc = mail_elt(stream, n);
142 if(mc && mc->sparep){
143 peltp = (PINELT_S *) mc->sparep;
144 if(peltp->pthrd)
145 fs_give((void **) &peltp->pthrd);
146 }
147 }
148 }
149
150
151 void
sort_thread_callback(MAILSTREAM * stream,THREADNODE * tree)152 sort_thread_callback(MAILSTREAM *stream, THREADNODE *tree)
153 {
154 THREADNODE *collapsed_tree = NULL;
155 PINETHRD_S *thrd = NULL;
156 unsigned long msgno, rawno;
157 int un_view_thread = 0;
158 long raw_current;
159 char *dup_chk = NULL;
160
161
162 dprint((2, "sort_thread_callback\n"));
163
164 g_sort.msgmap->max_thrdno = 0L;
165
166 /*
167 * Eliminate dummy nodes from tree and collapse the tree in a logical
168 * way. If the dummy node is at the top-level, then its children are
169 * promoted to the top-level as separate threads.
170 */
171 if(F_ON(F_THREAD_SORTS_BY_ARRIVAL, ps_global))
172 collapsed_tree = collapse_threadnode_tree_sorted(tree);
173 else
174 collapsed_tree = collapse_threadnode_tree(tree);
175
176 /* dup_chk is like sort with an origin of 1 */
177 dup_chk = (char *) fs_get((mn_get_nmsgs(g_sort.msgmap)+1) * sizeof(char));
178 memset(dup_chk, 0, (mn_get_nmsgs(g_sort.msgmap)+1) * sizeof(char));
179
180 memset(&g_sort.msgmap->sort[1], 0, mn_get_total(g_sort.msgmap) * sizeof(long));
181
182 (void) sort_thread_flatten(collapsed_tree, stream,
183 &g_sort.msgmap->sort[1],
184 dup_chk, mn_get_nmsgs(g_sort.msgmap),
185 NULL, THD_TOP);
186
187 /* reset the inverse array */
188 msgno_reset_isort(g_sort.msgmap);
189
190 if(dup_chk)
191 fs_give((void **) &dup_chk);
192
193 if(collapsed_tree)
194 mail_free_threadnode(&collapsed_tree);
195
196 if(any_lflagged(g_sort.msgmap, MN_HIDE))
197 g_sort.msgmap->visible_threads = calculate_visible_threads(stream);
198 else
199 g_sort.msgmap->visible_threads = g_sort.msgmap->max_thrdno;
200
201 raw_current = mn_m2raw(g_sort.msgmap, mn_get_cur(g_sort.msgmap));
202
203 sp_set_need_to_rethread(stream, 0);
204
205 /*
206 * Set appropriate bits to start out collapsed if desired. We use the
207 * stream spare bit to tell us if we've done this before for this
208 * stream.
209 */
210 if(!stream->spare
211 && (COLL_THRDS() || SEP_THRDINDX())
212 && mn_get_total(g_sort.msgmap) > 1L){
213
214 collapse_threads(stream, g_sort.msgmap, NULL);
215 }
216 else if(stream->spare){
217
218 /*
219 * If we're doing auto collapse then new threads need to have
220 * their collapse bit set. This happens below if we're in the
221 * thread index, but if we're in the regular index with auto
222 * collapse we have to look for these.
223 */
224 if(any_lflagged(g_sort.msgmap, MN_USOR)){
225 if(COLL_THRDS()){
226 for(msgno = 1L; msgno <= mn_get_total(g_sort.msgmap); msgno++){
227 rawno = mn_m2raw(g_sort.msgmap, msgno);
228 if(get_lflag(stream, NULL, rawno, MN_USOR)){
229 thrd = fetch_thread(stream, rawno);
230
231 /*
232 * Node is new, unsorted, top-level thread,
233 * and we're using auto collapse.
234 */
235 if(thrd && !thrd->parent)
236 set_lflag(stream, g_sort.msgmap, msgno, MN_COLL, 1);
237
238 /*
239 * If a parent is collapsed, clear that parent's
240 * index cache entry. This is only necessary if
241 * the parent's index display can depend on its
242 * children, of course.
243 */
244 if(thrd && thrd->parent){
245 thrd = fetch_thread(stream, thrd->parent);
246 while(thrd){
247 long t;
248
249 if(get_lflag(stream, NULL, thrd->rawno, MN_COLL)
250 && (t = mn_raw2m(g_sort.msgmap,
251 (long) thrd->rawno)))
252 clear_index_cache_ent(stream, t, 0);
253
254 if(thrd->parent)
255 thrd = fetch_thread(stream, thrd->parent);
256 else
257 thrd = NULL;
258 }
259 }
260
261 }
262 }
263 }
264
265 set_lflags(stream, g_sort.msgmap, MN_USOR, 0);
266 }
267
268 if(sp_viewing_a_thread(stream)){
269 if(any_lflagged(g_sort.msgmap, MN_CHID2)){
270 /* current should be part of viewed thread */
271 if(get_lflag(stream, NULL, raw_current, MN_CHID2)){
272 thrd = fetch_thread(stream, raw_current);
273 if(thrd && thrd->top && thrd->top != thrd->rawno)
274 thrd = fetch_thread(stream, thrd->top);
275
276 if(thrd){
277 /*
278 * For messages that are part of thread set MN_CHID2
279 * and for messages that aren't part of the thread
280 * clear MN_CHID2. Easiest is to just do it instead
281 * of checking if it is true first.
282 */
283 set_lflags(stream, g_sort.msgmap, MN_CHID2, 0);
284 set_thread_lflags(stream, thrd, g_sort.msgmap,
285 MN_CHID2, 1);
286
287 /*
288 * Outside of the viewed thread everything else
289 * should be collapsed at the top-levels.
290 */
291 collapse_threads(stream, g_sort.msgmap, thrd);
292
293 /*
294 * Inside of the thread, the top of the thread
295 * can't be hidden, the rest are hidden if a
296 * parent somewhere above them is collapsed.
297 * There can be collapse points that are hidden
298 * inside of the tree. They remain collapsed even
299 * if the parent above them uncollapses.
300 */
301 msgno = mn_raw2m(g_sort.msgmap, (long) thrd->rawno);
302 if(msgno)
303 set_lflag(stream, g_sort.msgmap, msgno, MN_CHID, 0);
304
305 if(thrd->next){
306 PINETHRD_S *nthrd;
307
308 nthrd = fetch_thread(stream, thrd->next);
309 if(nthrd)
310 make_thrdflags_consistent(stream, g_sort.msgmap,
311 nthrd,
312 get_lflag(stream, NULL,
313 thrd->rawno,
314 MN_COLL));
315 }
316 }
317 else
318 un_view_thread++;
319 }
320 else
321 un_view_thread++;
322 }
323 else
324 un_view_thread++;
325
326 if(un_view_thread){
327 set_lflags(stream, g_sort.msgmap, MN_CHID2, 0);
328 unview_thread(ps_global, stream, g_sort.msgmap);
329 }
330 else{
331 mn_reset_cur(g_sort.msgmap,
332 mn_raw2m(g_sort.msgmap, raw_current));
333 view_thread(ps_global, stream, g_sort.msgmap, 0);
334 }
335 }
336 else if(SEP_THRDINDX()){
337 set_lflags(stream, g_sort.msgmap, MN_CHID2, 0);
338 collapse_threads(stream, g_sort.msgmap, NULL);
339 }
340 else{
341 thrd = fetch_head_thread(stream);
342 while(thrd){
343 /*
344 * The top-level threads aren't hidden by collapse.
345 */
346 msgno = mn_raw2m(g_sort.msgmap, thrd->rawno);
347 if(msgno)
348 set_lflag(stream, g_sort.msgmap, msgno, MN_CHID, 0);
349
350 if(thrd->next){
351 PINETHRD_S *nthrd;
352
353 nthrd = fetch_thread(stream, thrd->next);
354 if(nthrd)
355 make_thrdflags_consistent(stream, g_sort.msgmap,
356 nthrd,
357 get_lflag(stream, NULL,
358 thrd->rawno,
359 MN_COLL));
360 }
361
362 if(thrd->nextthd)
363 thrd = fetch_thread(stream, thrd->nextthd);
364 else
365 thrd = NULL;
366 }
367 }
368 }
369
370 stream->spare = 1;
371
372 dprint((2, "sort_thread_callback done\n"));
373 }
374
375
376 void
collapse_threads(MAILSTREAM * stream,MSGNO_S * msgmap,PINETHRD_S * not_this_thread)377 collapse_threads(MAILSTREAM *stream, MSGNO_S *msgmap, PINETHRD_S *not_this_thread)
378 {
379 PINETHRD_S *thrd = NULL, *nthrd;
380 unsigned long msgno;
381
382 dprint((9, "collapse_threads\n"));
383
384 thrd = fetch_head_thread(stream);
385 while(thrd){
386 if(thrd != not_this_thread){
387 msgno = mn_raw2m(g_sort.msgmap, thrd->rawno);
388
389 /* set collapsed bit */
390 if(msgno){
391 set_lflag(stream, g_sort.msgmap, msgno, MN_COLL, 1);
392 set_lflag(stream, g_sort.msgmap, msgno, MN_CHID, 0);
393 }
394
395 /* hide its children */
396 if(thrd->next && (nthrd = fetch_thread(stream, thrd->next)))
397 set_thread_subtree(stream, nthrd, msgmap, 1, MN_CHID);
398 }
399
400 if(thrd->nextthd)
401 thrd = fetch_thread(stream, thrd->nextthd);
402 else
403 thrd = NULL;
404 }
405
406 dprint((9, "collapse_threads done\n"));
407 }
408
409
410 void
make_thrdflags_consistent(MAILSTREAM * stream,MSGNO_S * msgmap,PINETHRD_S * thrd,int a_parent_is_collapsed)411 make_thrdflags_consistent(MAILSTREAM *stream, MSGNO_S *msgmap, PINETHRD_S *thrd,
412 int a_parent_is_collapsed)
413 {
414 PINETHRD_S *nthrd, *bthrd;
415 unsigned long msgno;
416
417 if(!thrd)
418 return;
419
420 msgno = mn_raw2m(msgmap, thrd->rawno);
421
422 if(a_parent_is_collapsed){
423 /* if some parent is collapsed, we should be hidden */
424 if(msgno)
425 set_lflag(stream, msgmap, msgno, MN_CHID, 1);
426 }
427 else{
428 /* no parent is collapsed so we are not hidden */
429 if(msgno)
430 set_lflag(stream, msgmap, msgno, MN_CHID, 0);
431 }
432
433 if(thrd->next){
434 nthrd = fetch_thread(stream, thrd->next);
435 if(nthrd)
436 make_thrdflags_consistent(stream, msgmap, nthrd,
437 a_parent_is_collapsed
438 ? a_parent_is_collapsed
439 : get_lflag(stream, NULL, thrd->rawno,
440 MN_COLL));
441 }
442
443 if(thrd->branch){
444 bthrd = fetch_thread(stream, thrd->branch);
445 if(bthrd)
446 make_thrdflags_consistent(stream, msgmap, bthrd,
447 a_parent_is_collapsed);
448 }
449 }
450
451
452 long
calculate_visible_threads(MAILSTREAM * stream)453 calculate_visible_threads(MAILSTREAM *stream)
454 {
455 PINETHRD_S *thrd = NULL;
456 long vis = 0L;
457
458 thrd = fetch_head_thread(stream);
459 while(thrd){
460 vis += (thread_has_some_visible(stream, thrd) ? 1 : 0);
461
462 if(thrd->nextthd)
463 thrd = fetch_thread(stream, thrd->nextthd);
464 else
465 thrd = NULL;
466 }
467
468 return(vis);
469 }
470
471
472 /*
473 * This routine does a couple things. The input is the THREADNODE node
474 * that we get from c-client because of the THREAD command. The rest of
475 * the arguments are used to help guide this function through its
476 * recursive steps. One thing it does is to place the sort order in
477 * the array initially pointed to by the entry argument. All it is doing
478 * is walking the tree in the next then branch order you see below and
479 * incrementing the entry number one for each node. The other thing it
480 * is doing at the same time is to create a PINETHRD_S tree from the
481 * THREADNODE tree. The two trees are completely equivalent but the
482 * PINETHRD_S version has additional back pointers and parent pointers
483 * and so on to make it easier for alpine to deal with it. Each node
484 * of that tree is tied to the data associated with a particular message
485 * by the msgno_thread_info() call, so that we can go from a message
486 * number to the place in the thread tree that message sits.
487 */
488 long *
sort_thread_flatten(THREADNODE * node,MAILSTREAM * stream,long * entry,char * dup_chk,long maxno,PINETHRD_S * thrd,unsigned int flags)489 sort_thread_flatten(THREADNODE *node, MAILSTREAM *stream,
490 long *entry, char *dup_chk, long maxno,
491 PINETHRD_S *thrd, unsigned int flags)
492 {
493 PINETHRD_S *newthrd = NULL;
494
495 if(node){
496 if(node->num > 0L && node->num <= maxno){ /* holes happen */
497 if(!dup_chk[node->num]){ /* not a duplicate */
498 *entry = node->num;
499 dup_chk[node->num] = 1;
500
501 /*
502 * Build a richer threading structure that will help us paint
503 * and operate on threads and subthreads.
504 */
505 newthrd = msgno_thread_info(stream, node->num, thrd, flags);
506 if(newthrd){
507 entry++;
508
509 if(node->next)
510 entry = sort_thread_flatten(node->next, stream,
511 entry, dup_chk, maxno,
512 newthrd, THD_NEXT);
513
514 if(node->branch)
515 entry = sort_thread_flatten(node->branch, stream,
516 entry, dup_chk, maxno,
517 newthrd,
518 (flags == THD_TOP) ? THD_TOP
519 : THD_BRANCH);
520 }
521 }
522 }
523 }
524
525 return(entry);
526 }
527
528
529 /*
530 * Make a copy of c-client's THREAD tree while eliminating dummy nodes.
531 */
532 THREADNODE *
collapse_threadnode_tree(THREADNODE * tree)533 collapse_threadnode_tree(THREADNODE *tree)
534 {
535 THREADNODE *newtree = NULL;
536
537 if(tree){
538 if(tree->num){
539 newtree = mail_newthreadnode(NULL);
540 newtree->num = tree->num;
541 if(tree->next)
542 newtree->next = collapse_threadnode_tree(tree->next);
543
544 if(tree->branch)
545 newtree->branch = collapse_threadnode_tree(tree->branch);
546 }
547 else{
548 if(tree->next)
549 newtree = collapse_threadnode_tree(tree->next);
550
551 if(tree->branch){
552 if(newtree){
553 THREADNODE *last_branch = NULL;
554
555 /*
556 * Next moved up to replace "tree" in the tree.
557 * If next has no branches, then we want to branch off
558 * of next. If next has branches, we want to branch off
559 * of the last of those branches instead.
560 */
561 last_branch = newtree;
562 while(last_branch->branch)
563 last_branch = last_branch->branch;
564
565 last_branch->branch = collapse_threadnode_tree(tree->branch);
566 }
567 else
568 newtree = collapse_threadnode_tree(tree->branch);
569 }
570 }
571 }
572
573 return(newtree);
574 }
575
576
577 /*
578 * Like collapse_threadnode_tree, we collapse the dummy nodes.
579 * In addition we rearrange the threads by order of arrival of
580 * the last message in the thread, rather than the first message
581 * in the thread.
582 */
583 THREADNODE *
collapse_threadnode_tree_sorted(THREADNODE * tree)584 collapse_threadnode_tree_sorted(THREADNODE *tree)
585 {
586 THREADNODE *sorted_tree = NULL;
587
588 sorted_tree = sort_threads_and_collapse(tree);
589
590 /*
591 * We used to eliminate top-level dummy nodes here so that
592 * orphans would still get sorted together, but we changed
593 * to sort the orphans themselves as top-level threads.
594 *
595 * It might be a matter of choice how they get sorted, but
596 * we'll try doing it this way and not add another feature.
597 */
598
599 return(sorted_tree);
600 }
601
602 /*
603 * Recurse through the tree, sorting each top-level branch by the
604 * greatest num in the thread.
605 */
606 THREADNODE *
sort_threads_and_collapse(THREADNODE * tree)607 sort_threads_and_collapse(THREADNODE *tree)
608 {
609 THREADNODE *newtree = NULL, *newbranchtree = NULL, *newtreefree = NULL;
610
611 if(tree){
612 newtree = mail_newthreadnode(NULL);
613 newtree->num = tree->num;
614
615 /*
616 * Only sort at the top level. Individual threads can
617 * rely on collapse_threadnode_tree
618 */
619 if(tree->next)
620 newtree->next = collapse_threadnode_tree(tree->next);
621
622 if(tree->branch){
623 /*
624 * This recursive call returns an already re-sorted tree.
625 * With that, we can loop through and inject ourselves
626 * where we fit in with that sort, and pass back to the
627 * caller to inject themselves.
628 */
629 newbranchtree = sort_threads_and_collapse(tree->branch);
630 }
631
632 if(newtree->num)
633 newtree = insert_tree_in_place(newtree, newbranchtree);
634 else{
635 /*
636 * If top node is a dummy, here is where we collapse it.
637 */
638 newtreefree = newtree;
639 newtree = insert_tree_in_place(newtree->next, newbranchtree);
640 newtreefree->next = NULL;
641 mail_free_threadnode(&newtreefree);
642 }
643 }
644
645 return(newtree);
646 }
647
648 /*
649 * Recursively insert each of the top-level nodes in newtree in their place
650 * in tree according to which tree has the most recent arrival
651 */
652 THREADNODE *
insert_tree_in_place(THREADNODE * newtree,THREADNODE * tree)653 insert_tree_in_place(THREADNODE *newtree, THREADNODE *tree)
654 {
655 THREADNODE *node = NULL;
656 unsigned long newtree_greatest_num = 0;
657 if(newtree->branch){
658 node = newtree->branch;
659 newtree->branch = NULL;
660 tree = insert_tree_in_place(node, tree);
661 }
662
663 newtree_greatest_num = branch_greatest_num(newtree, 0);
664
665 if(tree){
666 /*
667 * Since tree is already sorted, we can insert when we find something
668 * newtree is less than
669 */
670 if(newtree_greatest_num < branch_greatest_num(tree, 0))
671 newtree->branch = tree;
672 else {
673 for(node = tree; node->branch; node = node->branch){
674 if(newtree_greatest_num < branch_greatest_num(node->branch, 0)){
675 newtree->branch = node->branch;
676 node->branch = newtree;
677 break;
678 }
679 }
680 if(!node->branch)
681 node->branch = newtree;
682
683 newtree = tree;
684 }
685 }
686
687 return(newtree);
688 }
689
690 /*
691 * Given a thread, return the greatest num in the tree.
692 * is_subthread tells us not to recurse through branches, so
693 * we can split the top level into threads.
694 */
695 unsigned long
branch_greatest_num(THREADNODE * tree,int is_subthread)696 branch_greatest_num(THREADNODE *tree, int is_subthread)
697 {
698 unsigned long ret, branch_ret;
699
700 ret = tree->num;
701
702 if(tree->next && (branch_ret = branch_greatest_num(tree->next, 1)) > ret)
703 ret = branch_ret;
704 if(is_subthread && tree->branch &&
705 (branch_ret = branch_greatest_num(tree->branch, 1)) > ret)
706 ret = branch_ret;
707
708 return ret;
709 }
710
711
712 /*
713 * Args stream -- the usual
714 * rawno -- the raw msg num associated with this new node
715 * attached_to_thrd -- the PINETHRD_S node that this is either a next or branch
716 * off of
717 * flags --
718 */
719 PINETHRD_S *
msgno_thread_info(MAILSTREAM * stream,long unsigned int rawno,PINETHRD_S * attached_to_thrd,unsigned int flags)720 msgno_thread_info(MAILSTREAM *stream, long unsigned int rawno,
721 PINETHRD_S *attached_to_thrd, unsigned int flags)
722 {
723 PINELT_S **peltp = NULL;
724 MESSAGECACHE *mc;
725
726 if(!stream || rawno < 1L || rawno > stream->nmsgs)
727 return NULL;
728
729 /*
730 * any private elt data yet?
731 */
732 if((mc = mail_elt(stream, rawno))
733 && (*(peltp = (PINELT_S **) &mc->sparep) == NULL)){
734 *peltp = (PINELT_S *) fs_get(sizeof(PINELT_S));
735 memset(*peltp, 0, sizeof(PINELT_S));
736 }
737
738 if((*peltp)->pthrd == NULL)
739 (*peltp)->pthrd = (PINETHRD_S *) fs_get(sizeof(PINETHRD_S));
740
741 memset((*peltp)->pthrd, 0, sizeof(PINETHRD_S));
742
743 (*peltp)->pthrd->rawno = rawno;
744
745 if(attached_to_thrd)
746 (*peltp)->pthrd->head = attached_to_thrd->head;
747 else
748 (*peltp)->pthrd->head = (*peltp)->pthrd->rawno; /* it's me */
749
750 if(flags == THD_TOP){
751 /*
752 * We can tell this thread is a top-level thread because it doesn't
753 * have a parent.
754 */
755 (*peltp)->pthrd->top = (*peltp)->pthrd->rawno; /* I am a top */
756 if(attached_to_thrd){
757 attached_to_thrd->nextthd = (*peltp)->pthrd->rawno;
758 (*peltp)->pthrd->prevthd = attached_to_thrd->rawno;
759 (*peltp)->pthrd->thrdno = attached_to_thrd->thrdno + 1L;
760 }
761 else
762 (*peltp)->pthrd->thrdno = 1L; /* 1st thread */
763
764 g_sort.msgmap->max_thrdno = (*peltp)->pthrd->thrdno;
765 }
766 else if(flags == THD_NEXT){
767 if(attached_to_thrd){
768 attached_to_thrd->next = (*peltp)->pthrd->rawno;
769 (*peltp)->pthrd->parent = attached_to_thrd->rawno;
770 (*peltp)->pthrd->top = attached_to_thrd->top;
771 }
772 }
773 else if(flags == THD_BRANCH){
774 if(attached_to_thrd){
775 attached_to_thrd->branch = (*peltp)->pthrd->rawno;
776 (*peltp)->pthrd->parent = attached_to_thrd->parent;
777 (*peltp)->pthrd->top = attached_to_thrd->top;
778 }
779 }
780
781 return((*peltp)->pthrd);
782 }
783
784
785 /*
786 * Collapse or expand a threading subtree. Not called from separate thread
787 * index.
788 */
789 void
collapse_or_expand(struct pine * state,MAILSTREAM * stream,MSGNO_S * msgmap,long unsigned int msgno)790 collapse_or_expand(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap,
791 long unsigned int msgno)
792 {
793 int collapsed, adjust_current = 0;
794 PINETHRD_S *thrd = NULL, *nthrd;
795 unsigned long rawno;
796
797 if(!stream)
798 return;
799
800 /*
801 * If msgno is a good msgno, then we collapse or expand the subthread
802 * which begins at msgno. If msgno is 0, we collapse or expand the
803 * entire current thread.
804 */
805
806 if(msgno > 0L && msgno <= mn_get_total(msgmap)){
807 rawno = mn_m2raw(msgmap, msgno);
808 if(rawno)
809 thrd = fetch_thread(stream, rawno);
810 }
811 else if(msgno == 0L){
812 rawno = mn_m2raw(msgmap, mn_get_cur(msgmap));
813 if(rawno)
814 thrd = fetch_thread(stream, rawno);
815
816 if(thrd && thrd->top != thrd->rawno){
817 adjust_current++;
818 thrd = fetch_thread(stream, thrd->top);
819
820 /*
821 * Special case. If the user is collapsing the entire thread
822 * (msgno == 0), and we are in a Zoomed view, and the top of
823 * the entire thread is not part of the Zoomed view, then watch
824 * out. If we were to collapse the entire thread it would just
825 * disappear, because the top is not in the Zoom. Therefore,
826 * don't allow it. Do what the user probably wants, which is to
827 * collapse the thread at that point instead of the entire thread,
828 * leaving behind the top of the subthread to expand if needed.
829 * In other words, treat it as if they didn't have the
830 * F_SLASH_COLL_ENTIRE feature set.
831 */
832 collapsed = get_lflag(stream, NULL, thrd->rawno, MN_COLL)
833 && thrd->next;
834
835 if(!collapsed && get_lflag(stream, NULL, thrd->rawno, MN_HIDE))
836 thrd = fetch_thread(stream, rawno);
837 }
838 }
839
840
841 if(!thrd)
842 return;
843
844 collapsed = get_lflag(stream, NULL, thrd->rawno, MN_COLL) && thrd->next;
845
846 if(collapsed){
847 msgno = mn_raw2m(msgmap, thrd->rawno);
848 if(msgno > 0L && msgno <= mn_get_total(msgmap)){
849 set_lflag(stream, msgmap, msgno, MN_COLL, 0);
850 if(thrd->next){
851 if((nthrd = fetch_thread(stream, thrd->next)) != NULL)
852 set_thread_subtree(stream, nthrd, msgmap, 0, MN_CHID);
853
854 clear_index_cache_ent(stream, msgno, 0);
855 }
856 }
857 }
858 else if(thrd && thrd->next){
859 msgno = mn_raw2m(msgmap, thrd->rawno);
860 if(msgno > 0L && msgno <= mn_get_total(msgmap)){
861 set_lflag(stream, msgmap, msgno, MN_COLL, 1);
862 if((nthrd = fetch_thread(stream, thrd->next)) != NULL)
863 set_thread_subtree(stream, nthrd, msgmap, 1, MN_CHID);
864
865 clear_index_cache_ent(stream, msgno, 0);
866 }
867 }
868 else
869 q_status_message(SM_ORDER, 0, 1,
870 _("No thread to collapse or expand on this line"));
871
872 /* if current is hidden, adjust */
873 if(adjust_current)
874 adjust_cur_to_visible(stream, msgmap);
875 }
876
877
878 /*
879 * Select the messages in a subthread. If all of the messages are already
880 * selected, unselect them. This routine is a bit strange because it
881 * doesn't set the MN_SLCT bit. Instead, it sets MN_STMP in apply_command
882 * and then thread_command copies the MN_STMP messages back to MN_SLCT.
883 */
884 void
select_thread_stmp(struct pine * state,MAILSTREAM * stream,MSGNO_S * msgmap)885 select_thread_stmp(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap)
886 {
887 PINETHRD_S *thrd = NULL;
888 unsigned long rawno, in_thread, set_in_thread, save_branch;
889
890 /* ugly bit means the same thing as return of 1 from individual_select */
891 state->ugly_consider_advancing_bit = 0;
892
893 if(!(stream && msgmap))
894 return;
895
896 rawno = mn_m2raw(msgmap, mn_get_cur(msgmap));
897 if(rawno)
898 thrd = fetch_thread(stream, rawno);
899
900 if(!thrd)
901 return;
902
903 /* run through thrd to see if it is all selected */
904 save_branch = thrd->branch;
905 thrd->branch = 0L;
906 if((set_in_thread = count_lflags_in_thread(stream, thrd, msgmap, MN_STMP))
907 == (in_thread = count_lflags_in_thread(stream, thrd, msgmap, MN_NONE))){
908 /*
909 * If everything is selected, the first unselect should cause
910 * an autozoom. In order to trigger the right code in
911 * thread_command()
912 * copy_lflags()
913 * we set the MN_HIDE bit on the current message here.
914 */
915 if(F_ON(F_AUTO_ZOOM, state) && !any_lflagged(msgmap, MN_HIDE)
916 && any_lflagged(msgmap, MN_STMP) == mn_get_total(msgmap))
917 set_lflag(stream, msgmap, mn_get_cur(msgmap), MN_HIDE, 1);
918 set_thread_lflags(stream, thrd, msgmap, MN_STMP, 0);
919 }
920 else{
921 set_thread_lflags(stream, thrd, msgmap, MN_STMP, 1);
922 state->ugly_consider_advancing_bit = 1;
923 }
924
925 thrd->branch = save_branch;
926
927 if(set_in_thread == in_thread)
928 q_status_message1(SM_ORDER, 0, 3, _("Unselected %s messages in thread"),
929 comatose((long) in_thread));
930 else if(set_in_thread == 0)
931 q_status_message1(SM_ORDER, 0, 3, _("Selected %s messages in thread"),
932 comatose((long) in_thread));
933 else
934 q_status_message1(SM_ORDER, 0, 3,
935 _("Selected %s more messages in thread"),
936 comatose((long) (in_thread-set_in_thread)));
937 }
938
939
940 /*
941 * Count how many of this system flag in this thread subtree.
942 * If flags == 0 count the messages in the thread.
943 *
944 * Watch out when calling this. The thrd->branch is not part of thrd.
945 * Branch is a sibling to thrd, not a child. Zero out branch before calling
946 * or call on thrd->next and worry about thrd separately.
947 * Ok to call it on top-level thread which has no branch already.
948 */
949 unsigned long
count_flags_in_thread(MAILSTREAM * stream,PINETHRD_S * thrd,long int flags)950 count_flags_in_thread(MAILSTREAM *stream, PINETHRD_S *thrd, long int flags)
951 {
952 unsigned long count = 0;
953 PINETHRD_S *nthrd, *bthrd;
954 MESSAGECACHE *mc;
955
956 if(!thrd || !stream || thrd->rawno < 1L || thrd->rawno > stream->nmsgs)
957 return count;
958
959 if(thrd->next){
960 nthrd = fetch_thread(stream, thrd->next);
961 if(nthrd)
962 count += count_flags_in_thread(stream, nthrd, flags);
963 }
964
965 if(thrd->branch){
966 bthrd = fetch_thread(stream, thrd->branch);
967 if(bthrd)
968 count += count_flags_in_thread(stream, bthrd, flags);
969 }
970
971 mc = (thrd && thrd->rawno > 0L && stream && thrd->rawno <= stream->nmsgs)
972 ? mail_elt(stream, thrd->rawno) : NULL;
973 if(mc && mc->valid && FLAG_MATCH(flags, mc, stream))
974 count++;
975
976 return count;
977 }
978
979
980 /*
981 * Count how many of this local flag in this thread subtree.
982 * If flags == MN_NONE then we just count the messages instead of whether
983 * the messages have a flag set.
984 *
985 * Watch out when calling this. The thrd->branch is not part of thrd.
986 * Branch is a sibling to thrd, not a child. Zero out branch before calling
987 * or call on thrd->next and worry about thrd separately.
988 * Ok to call it on top-level thread which has no branch already.
989 */
990 unsigned long
count_lflags_in_thread(MAILSTREAM * stream,PINETHRD_S * thrd,MSGNO_S * msgmap,int flags)991 count_lflags_in_thread(MAILSTREAM *stream, PINETHRD_S *thrd, MSGNO_S *msgmap, int flags)
992 {
993 unsigned long count = 0;
994 PINETHRD_S *nthrd, *bthrd;
995
996 if(!thrd || !stream || thrd->rawno < 1L || thrd->rawno > stream->nmsgs)
997 return count;
998
999 if(thrd->next){
1000 nthrd = fetch_thread(stream, thrd->next);
1001 if(nthrd)
1002 count += count_lflags_in_thread(stream, nthrd, msgmap, flags);
1003 }
1004
1005 if(thrd->branch){
1006 bthrd = fetch_thread(stream, thrd->branch);
1007 if(bthrd)
1008 count += count_lflags_in_thread(stream, bthrd, msgmap,flags);
1009 }
1010
1011 if(flags == MN_NONE)
1012 count++;
1013 else
1014 count += get_lflag(stream, msgmap, mn_raw2m(msgmap, thrd->rawno), flags);
1015
1016 return count;
1017 }
1018
1019
1020 /*
1021 * Special-purpose for performance improvement.
1022 */
1023 int
thread_has_some_visible(MAILSTREAM * stream,PINETHRD_S * thrd)1024 thread_has_some_visible(MAILSTREAM *stream, PINETHRD_S *thrd)
1025 {
1026 PINETHRD_S *nthrd, *bthrd;
1027
1028 if(!thrd || !stream || thrd->rawno < 1L || thrd->rawno > stream->nmsgs)
1029 return 0;
1030
1031 if(get_lflag(stream, NULL, thrd->rawno, MN_HIDE) == 0)
1032 return 1;
1033
1034 if(thrd->next){
1035 nthrd = fetch_thread(stream, thrd->next);
1036 if(nthrd && thread_has_some_visible(stream, nthrd))
1037 return 1;
1038 }
1039
1040 if(thrd->branch){
1041 bthrd = fetch_thread(stream, thrd->branch);
1042 if(bthrd && thread_has_some_visible(stream, bthrd))
1043 return 1;
1044 }
1045
1046 return 0;
1047 }
1048
1049
1050 int
mark_msgs_in_thread(MAILSTREAM * stream,PINETHRD_S * thrd,MSGNO_S * msgmap)1051 mark_msgs_in_thread(MAILSTREAM *stream, PINETHRD_S *thrd, MSGNO_S *msgmap)
1052 {
1053 int count = 0;
1054 PINETHRD_S *nthrd, *bthrd;
1055 MESSAGECACHE *mc;
1056
1057 if(!thrd || !stream || thrd->rawno < 1L || thrd->rawno > stream->nmsgs)
1058 return count;
1059
1060 if(thrd->next){
1061 nthrd = fetch_thread(stream, thrd->next);
1062 if(nthrd)
1063 count += mark_msgs_in_thread(stream, nthrd, msgmap);
1064 }
1065
1066 if(thrd->branch){
1067 bthrd = fetch_thread(stream, thrd->branch);
1068 if(bthrd)
1069 count += mark_msgs_in_thread(stream, bthrd, msgmap);
1070 }
1071
1072 if(stream && thrd->rawno >= 1L && thrd->rawno <= stream->nmsgs &&
1073 (mc = mail_elt(stream,thrd->rawno))
1074 && !mc->sequence
1075 && !mc->private.msg.env){
1076 mc->sequence = 1;
1077 count++;
1078 }
1079
1080 return count;
1081 }
1082
1083
1084 /*
1085 * This sets or clears flags for the messages at this node and below in
1086 * a tree.
1087 *
1088 * Watch out when calling this. The thrd->branch is not part of thrd.
1089 * Branch is a sibling to thrd, not a child. Zero out branch before calling
1090 * or call on thrd->next and worry about thrd separately.
1091 * Ok to call it on top-level thread which has no branch already.
1092 */
1093 void
set_thread_lflags(MAILSTREAM * stream,PINETHRD_S * thrd,MSGNO_S * msgmap,int flags,int v)1094 set_thread_lflags(MAILSTREAM *stream, PINETHRD_S *thrd, MSGNO_S *msgmap, int flags, int v)
1095
1096
1097
1098 /* flags to set or clear */
1099 /* set or clear? */
1100 {
1101 unsigned long msgno;
1102 PINETHRD_S *nthrd, *bthrd;
1103
1104 if(!thrd || !stream || thrd->rawno < 1L || thrd->rawno > stream->nmsgs)
1105 return;
1106
1107 msgno = mn_raw2m(msgmap, thrd->rawno);
1108
1109 set_lflag(stream, msgmap, msgno, flags, v);
1110
1111 /*
1112 * Careful, performance hack. This should logically be a separate
1113 * operation on the thread but it is convenient to stick it in here.
1114 *
1115 * When we back out of viewing a thread to the separate-thread-index
1116 * we may leave behind some cached hlines that aren't quite right
1117 * because they were collapsed. In particular, the plus_col character
1118 * may be wrong. Instead of trying to figure out what it should be just
1119 * clear the cache entries for the this thread when we come back in
1120 * to view it again.
1121 */
1122 if(msgno > 0L && flags == MN_CHID2 && v == 1)
1123 clear_index_cache_ent(stream, msgno, 0);
1124
1125 if(thrd->next){
1126 nthrd = fetch_thread(stream, thrd->next);
1127 if(nthrd)
1128 set_thread_lflags(stream, nthrd, msgmap, flags, v);
1129 }
1130
1131 if(thrd->branch){
1132 bthrd = fetch_thread(stream, thrd->branch);
1133 if(bthrd)
1134 set_thread_lflags(stream, bthrd, msgmap, flags, v);
1135 }
1136 }
1137
1138
1139 char
status_symbol_for_thread(MAILSTREAM * stream,PINETHRD_S * thrd,IndexColType type)1140 status_symbol_for_thread(MAILSTREAM *stream, PINETHRD_S *thrd, IndexColType type)
1141 {
1142 char status = ' ';
1143 unsigned long save_branch, cnt, tot_in_thrd;
1144
1145 if(!thrd || !stream || thrd->rawno < 1L || thrd->rawno > stream->nmsgs)
1146 return status;
1147
1148 save_branch = thrd->branch;
1149 thrd->branch = 0L; /* branch is a sibling, not part of thread */
1150
1151 /*
1152 * This is D if all of thread is deleted,
1153 * else A if all of thread is answered,
1154 * else F if all of thread is forwarded,
1155 * else N if any unseen and not deleted,
1156 * else blank.
1157 */
1158 if(type == iStatus){
1159 tot_in_thrd = count_flags_in_thread(stream, thrd, F_NONE);
1160 /* all deleted */
1161 if(count_flags_in_thread(stream, thrd, F_DEL) == tot_in_thrd)
1162 status = 'D';
1163 /* all answered */
1164 else if(count_flags_in_thread(stream, thrd, F_ANS) == tot_in_thrd)
1165 status = 'A';
1166 /* all forwarded */
1167 else if(count_flags_in_thread(stream, thrd, F_FWD) == tot_in_thrd)
1168 status = 'F';
1169 /* or any new and not deleted */
1170 else if((!IS_NEWS(stream)
1171 || F_ON(F_FAKE_NEW_IN_NEWS, ps_global))
1172 && count_flags_in_thread(stream, thrd, F_UNDEL|F_UNSEEN))
1173 status = 'N';
1174 }
1175 else if(type == iFStatus){
1176 if(!IS_NEWS(stream) || F_ON(F_FAKE_NEW_IN_NEWS, ps_global)){
1177 tot_in_thrd = count_flags_in_thread(stream, thrd, F_NONE);
1178 cnt = count_flags_in_thread(stream, thrd, F_UNSEEN);
1179 if(cnt)
1180 status = (cnt == tot_in_thrd) ? 'N' : 'n';
1181 }
1182 }
1183 else if(type == iIStatus || type == iSIStatus){
1184 tot_in_thrd = count_flags_in_thread(stream, thrd, F_NONE);
1185
1186 /* unseen and recent */
1187 cnt = count_flags_in_thread(stream, thrd, F_RECENT|F_UNSEEN);
1188 if(cnt)
1189 status = (cnt == tot_in_thrd) ? 'N' : 'n';
1190 else{
1191 /* unseen and !recent */
1192 cnt = count_flags_in_thread(stream, thrd, F_UNSEEN);
1193 if(cnt)
1194 status = (cnt == tot_in_thrd) ? 'U' : 'u';
1195 else{
1196 /* seen and recent */
1197 cnt = count_flags_in_thread(stream, thrd, F_RECENT|F_SEEN);
1198 if(cnt)
1199 status = (cnt == tot_in_thrd) ? 'R' : 'r';
1200 }
1201 }
1202 }
1203
1204 thrd->branch = save_branch;
1205
1206 return status;
1207 }
1208
1209
1210 /*
1211 * Symbol is * if some message in thread is important,
1212 * + if some message is to us,
1213 * - if mark-for-cc and some message is cc to us, else blank.
1214 */
1215 char
to_us_symbol_for_thread(MAILSTREAM * stream,PINETHRD_S * thrd,int consider_flagged)1216 to_us_symbol_for_thread(MAILSTREAM *stream, PINETHRD_S *thrd, int consider_flagged)
1217 {
1218 char to_us = ' ';
1219 char branch_to_us = ' ';
1220 PINETHRD_S *nthrd, *bthrd;
1221 MESSAGECACHE *mc;
1222
1223 if(!thrd || !stream || thrd->rawno < 1L || thrd->rawno > stream->nmsgs)
1224 return to_us;
1225
1226 if(thrd->next){
1227 nthrd = fetch_thread(stream, thrd->next);
1228 if(nthrd)
1229 to_us = to_us_symbol_for_thread(stream, nthrd, consider_flagged);
1230 }
1231
1232 if(((consider_flagged && to_us != '*') || (!consider_flagged && to_us != '+'))
1233 && thrd->branch){
1234 bthrd = fetch_thread(stream, thrd->branch);
1235 if(bthrd)
1236 branch_to_us = to_us_symbol_for_thread(stream, bthrd, consider_flagged);
1237
1238 /* use branch to_us symbol if it has higher priority than what we have so far */
1239 if(to_us == ' '){
1240 if(branch_to_us == '-' || branch_to_us == '+' || branch_to_us == '*')
1241 to_us = branch_to_us;
1242 }
1243 else if(to_us == '-'){
1244 if(branch_to_us == '+' || branch_to_us == '*')
1245 to_us = branch_to_us;
1246 }
1247 else if(to_us == '+'){
1248 if(branch_to_us == '*')
1249 to_us = branch_to_us;
1250 }
1251 }
1252
1253 if((consider_flagged && to_us != '*') || (!consider_flagged && to_us != '+')){
1254 if(consider_flagged && thrd && thrd->rawno > 0L
1255 && stream && thrd->rawno <= stream->nmsgs
1256 && (mc = mail_elt(stream, thrd->rawno))
1257 && FLAG_MATCH(F_FLAG, mc, stream))
1258 to_us = '*';
1259 else if(to_us != '+' && !IS_NEWS(stream)){
1260 INDEXDATA_S idata;
1261 MESSAGECACHE *mc;
1262 ADDRESS *addr;
1263
1264 memset(&idata, 0, sizeof(INDEXDATA_S));
1265 idata.stream = stream;
1266 idata.rawno = thrd->rawno;
1267 idata.msgno = mn_raw2m(sp_msgmap(stream), idata.rawno);
1268 if(idata.rawno > 0L && stream && idata.rawno <= stream->nmsgs
1269 && (mc = mail_elt(stream, idata.rawno))){
1270 idata.size = mc->rfc822_size;
1271 index_data_env(&idata,
1272 pine_mail_fetchenvelope(stream, idata.rawno));
1273 }
1274 else
1275 idata.bogus = 2;
1276
1277 for(addr = fetch_to(&idata); addr; addr = addr->next)
1278 if(address_is_us(addr, ps_global)){
1279 to_us = '+';
1280 break;
1281 }
1282
1283 if(to_us != '+' && resent_to_us(&idata))
1284 to_us = '+';
1285
1286 if(to_us == ' ' && F_ON(F_MARK_FOR_CC,ps_global))
1287 for(addr = fetch_cc(&idata); addr; addr = addr->next)
1288 if(address_is_us(addr, ps_global)){
1289 to_us = '-';
1290 break;
1291 }
1292 }
1293 }
1294
1295 return to_us;
1296 }
1297
1298
1299 /*
1300 * This sets or clears flags for the messages at this node and below in
1301 * a tree. It doesn't just blindly do it, perhaps it should. Instead,
1302 * when un-hiding a subtree it leaves the sub-subtree hidden if a node
1303 * is collapsed.
1304 *
1305 * Watch out when calling this. The thrd->branch is not part of thrd.
1306 * Branch is a sibling to thrd, not a child. Zero out branch before calling
1307 * or call on thrd->next and worry about thrd separately.
1308 * Ok to call it on top-level thread which has no branch already.
1309 */
1310 void
set_thread_subtree(MAILSTREAM * stream,PINETHRD_S * thrd,MSGNO_S * msgmap,int v,int flags)1311 set_thread_subtree(MAILSTREAM *stream, PINETHRD_S *thrd, MSGNO_S *msgmap, int v, int flags)
1312
1313
1314
1315 /* set or clear? */
1316 /* flags to set or clear */
1317 {
1318 int hiding;
1319 unsigned long msgno;
1320 PINETHRD_S *nthrd, *bthrd;
1321
1322 hiding = (flags == MN_CHID) && v;
1323
1324 if(!thrd || !stream || thrd->rawno < 1L || thrd->rawno > stream->nmsgs)
1325 return;
1326
1327 msgno = mn_raw2m(msgmap, thrd->rawno);
1328
1329 set_lflag(stream, msgmap, msgno, flags, v);
1330
1331 if(thrd->next && (hiding || !get_lflag(stream,NULL,thrd->rawno,MN_COLL))){
1332 nthrd = fetch_thread(stream, thrd->next);
1333 if(nthrd)
1334 set_thread_subtree(stream, nthrd, msgmap, v, flags);
1335 }
1336
1337 if(thrd->branch){
1338 bthrd = fetch_thread(stream, thrd->branch);
1339 if(bthrd)
1340 set_thread_subtree(stream, bthrd, msgmap, v, flags);
1341 }
1342 }
1343
1344
1345 /*
1346 * View a thread. Move from the thread index screen to a message index
1347 * screen for the current thread.
1348 *
1349 * set_lflags - Set the local flags appropriately to start viewing
1350 * the thread. We would not want to set this if we are
1351 * already viewing the thread (and expunge or new mail
1352 * happened) and we want to preserve the collapsed state
1353 * of the subthreads.
1354 */
1355 int
view_thread(struct pine * state,MAILSTREAM * stream,MSGNO_S * msgmap,int set_lflags)1356 view_thread(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, int set_lflags)
1357 {
1358 PINETHRD_S *thrd = NULL;
1359 unsigned long rawno, cur;
1360
1361 if(!any_messages(msgmap, NULL, "to View"))
1362 return 0;
1363
1364 if(!(stream && msgmap))
1365 return 0;
1366
1367 rawno = mn_m2raw(msgmap, mn_get_cur(msgmap));
1368 if(rawno)
1369 thrd = fetch_thread(stream, rawno);
1370
1371 if(thrd && thrd->top && thrd->top != thrd->rawno)
1372 thrd = fetch_thread(stream, thrd->top);
1373
1374 if(!thrd)
1375 return 0;
1376
1377 /*
1378 * Clear hidden and collapsed flag for this thread.
1379 * And set CHID2.
1380 * Don't have to worry about there being a branch because
1381 * this is a toplevel thread.
1382 */
1383 if(set_lflags){
1384 set_thread_lflags(stream, thrd, msgmap, MN_COLL | MN_CHID, 0);
1385 set_thread_lflags(stream, thrd, msgmap, MN_CHID2, 1);
1386 }
1387
1388 /*
1389 * If this is one of those wacky users who like to sort backwards
1390 * they would probably prefer that the current message be the last
1391 * one in the thread (the one highest up the screen).
1392 */
1393 if(mn_get_revsort(msgmap)){
1394 cur = mn_get_cur(msgmap);
1395 while(cur > 1L && get_lflag(stream, msgmap, cur-1L, MN_CHID2))
1396 cur--;
1397
1398 if(cur != mn_get_cur(msgmap))
1399 mn_set_cur(msgmap, cur);
1400 }
1401
1402 /* first message in thread might be hidden if zoomed */
1403 if(any_lflagged(msgmap, MN_HIDE)){
1404 cur = mn_get_cur(msgmap);
1405 while(get_lflag(stream, msgmap, cur, MN_HIDE))
1406 cur++;
1407
1408 if(cur != mn_get_cur(msgmap))
1409 mn_set_cur(msgmap, cur);
1410 }
1411
1412 msgmap->top = mn_get_cur(msgmap);
1413 sp_set_viewing_a_thread(stream, 1);
1414
1415 state->mangled_screen = 1;
1416 setup_for_index_index_screen();
1417
1418 return 1;
1419 }
1420
1421
1422 int
unview_thread(struct pine * state,MAILSTREAM * stream,MSGNO_S * msgmap)1423 unview_thread(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap)
1424 {
1425 PINETHRD_S *thrd = NULL, *topthrd = NULL;
1426 unsigned long rawno;
1427
1428 if(!(stream && msgmap))
1429 return 0;
1430
1431 rawno = mn_m2raw(msgmap, mn_get_cur(msgmap));
1432 if(rawno)
1433 thrd = fetch_thread(stream, rawno);
1434
1435 if(thrd && thrd->top)
1436 topthrd = fetch_thread(stream, thrd->top);
1437
1438 if(!topthrd)
1439 return 0;
1440
1441 /* hide this thread */
1442 set_thread_lflags(stream, topthrd, msgmap, MN_CHID, 1);
1443
1444 /* clear special CHID2 flags for this thread */
1445 set_thread_lflags(stream, topthrd, msgmap, MN_CHID2, 0);
1446
1447 /* clear CHID for top-level message and set COLL */
1448 set_lflag(stream, msgmap, mn_raw2m(msgmap, topthrd->rawno), MN_CHID, 0);
1449 set_lflag(stream, msgmap, mn_raw2m(msgmap, topthrd->rawno), MN_COLL, 1);
1450
1451 mn_set_cur(msgmap, mn_raw2m(msgmap, topthrd->rawno));
1452 sp_set_viewing_a_thread(stream, 0);
1453 setup_for_thread_index_screen();
1454
1455 return 1;
1456 }
1457
1458
1459 PINETHRD_S *
find_thread_by_number(MAILSTREAM * stream,MSGNO_S * msgmap,long int target,PINETHRD_S * startthrd)1460 find_thread_by_number(MAILSTREAM *stream, MSGNO_S *msgmap, long int target, PINETHRD_S *startthrd)
1461 {
1462 PINETHRD_S *thrd = NULL;
1463
1464 if(!(stream && msgmap))
1465 return(thrd);
1466
1467 thrd = startthrd;
1468
1469 if(!thrd || !(thrd->prevthd || thrd->nextthd))
1470 thrd = fetch_thread(stream, mn_m2raw(msgmap, mn_get_cur(msgmap)));
1471
1472 if(thrd && !(thrd->prevthd || thrd->nextthd) && thrd->head)
1473 thrd = fetch_thread(stream, thrd->head);
1474
1475 if(thrd){
1476 /* go forward from here */
1477 if(thrd->thrdno < target){
1478 while(thrd){
1479 if(thrd->thrdno == target)
1480 break;
1481
1482 if(mn_get_revsort(msgmap) && thrd->prevthd)
1483 thrd = fetch_thread(stream, thrd->prevthd);
1484 else if(!mn_get_revsort(msgmap) && thrd->nextthd)
1485 thrd = fetch_thread(stream, thrd->nextthd);
1486 else
1487 thrd = NULL;
1488 }
1489 }
1490 /* back up from here */
1491 else if(thrd->thrdno > target
1492 && (mn_get_revsort(msgmap)
1493 || (thrd->thrdno - target) < (target - 1L))){
1494 while(thrd){
1495 if(thrd->thrdno == target)
1496 break;
1497
1498 if(mn_get_revsort(msgmap) && thrd->nextthd)
1499 thrd = fetch_thread(stream, thrd->nextthd);
1500 else if(!mn_get_revsort(msgmap) && thrd->prevthd)
1501 thrd = fetch_thread(stream, thrd->prevthd);
1502 else
1503 thrd = NULL;
1504 }
1505 }
1506 /* go forward from head */
1507 else if(thrd->thrdno > target){
1508 if(thrd->head){
1509 thrd = fetch_thread(stream, thrd->head);
1510 while(thrd){
1511 if(thrd->thrdno == target)
1512 break;
1513
1514 if(thrd->nextthd)
1515 thrd = fetch_thread(stream, thrd->nextthd);
1516 else
1517 thrd = NULL;
1518 }
1519 }
1520 }
1521 }
1522
1523 return(thrd);
1524 }
1525
1526
1527 /*
1528 * Set search bit for every message in a thread.
1529 *
1530 * Watch out when calling this. The thrd->branch is not part of thrd.
1531 * Branch is a sibling to thrd, not a child. Zero out branch before calling
1532 * or call on thrd->next and worry about thrd separately. Top-level threads
1533 * already have a branch equal to zero.
1534 *
1535 * If msgset is non-NULL, then only set the search bit for a message if that
1536 * message is included in the msgset.
1537 */
1538 void
set_search_bit_for_thread(MAILSTREAM * stream,PINETHRD_S * thrd,SEARCHSET ** msgset)1539 set_search_bit_for_thread(MAILSTREAM *stream, PINETHRD_S *thrd, SEARCHSET **msgset)
1540 {
1541 PINETHRD_S *nthrd, *bthrd;
1542
1543 if(!(stream && thrd))
1544 return;
1545
1546 if(thrd->rawno > 0L && thrd->rawno <= stream->nmsgs
1547 && (!(msgset && *msgset) || in_searchset(*msgset, thrd->rawno)))
1548 mm_searched(stream, thrd->rawno);
1549
1550 if(thrd->next){
1551 nthrd = fetch_thread(stream, thrd->next);
1552 if(nthrd)
1553 set_search_bit_for_thread(stream, nthrd, msgset);
1554 }
1555
1556 if(thrd->branch){
1557 bthrd = fetch_thread(stream, thrd->branch);
1558 if(bthrd)
1559 set_search_bit_for_thread(stream, bthrd, msgset);
1560 }
1561 }
1562