1 #if !defined(lint) && !defined(DOS)
2 static char rcsid
[] = "$Id: thread.c 942 2008-03-04 18:21:33Z hubert@u.washington.edu $";
6 * ========================================================================
7 * Copyright 2013-2020 Eduardo Chappa
8 * Copyright 2006-2008 University of Washington
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
14 * http://www.apache.org/licenses/LICENSE-2.0
16 * ========================================================================
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"
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
*);
49 fetch_thread(MAILSTREAM
*stream
, long unsigned int rawno
)
53 PINETHRD_S
*thrd
= NULL
;
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
))
68 fetch_head_thread(MAILSTREAM
*stream
)
71 PINETHRD_S
*thrd
= NULL
;
74 /* first find any thread */
75 for(rawno
= 1L; !thrd
&& rawno
<= stream
->nmsgs
; rawno
++)
76 thrd
= fetch_thread(stream
, rawno
);
78 if(thrd
&& thrd
->head
)
79 thrd
= fetch_thread(stream
, thrd
->head
);
87 * Set flag f to v for all messages in thrd.
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.
95 set_flags_for_thread(MAILSTREAM
*stream
, MSGNO_S
*msgmap
, int f
, PINETHRD_S
*thrd
, int v
)
97 PINETHRD_S
*nthrd
, *bthrd
;
99 if(!(stream
&& thrd
&& msgmap
))
102 set_lflag(stream
, msgmap
, mn_raw2m(msgmap
, thrd
->rawno
), f
, v
);
105 nthrd
= fetch_thread(stream
, thrd
->next
);
107 set_flags_for_thread(stream
, msgmap
, f
, nthrd
, v
);
111 bthrd
= fetch_thread(stream
, thrd
->branch
);
113 set_flags_for_thread(stream
, msgmap
, f
, bthrd
, v
);
119 erase_threading_info(MAILSTREAM
*stream
, MSGNO_S
*msgmap
)
125 if(!(stream
&& stream
->spare
))
128 ps_global
->view_skipped_index
= 0;
129 sp_set_viewing_a_thread(stream
, 0);
132 setup_for_thread_index_screen();
134 setup_for_index_index_screen();
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
;
145 fs_give((void **) &peltp
->pthrd
);
152 sort_thread_callback(MAILSTREAM
*stream
, THREADNODE
*tree
)
154 THREADNODE
*collapsed_tree
= NULL
;
155 PINETHRD_S
*thrd
= NULL
;
156 unsigned long msgno
, rawno
;
157 int un_view_thread
= 0;
159 char *dup_chk
= NULL
;
162 dprint((2, "sort_thread_callback\n"));
164 g_sort
.msgmap
->max_thrdno
= 0L;
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.
171 if(F_ON(F_THREAD_SORTS_BY_ARRIVAL
, ps_global
))
172 collapsed_tree
= collapse_threadnode_tree_sorted(tree
);
174 collapsed_tree
= collapse_threadnode_tree(tree
);
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));
180 memset(&g_sort
.msgmap
->sort
[1], 0, mn_get_total(g_sort
.msgmap
) * sizeof(long));
182 (void) sort_thread_flatten(collapsed_tree
, stream
,
183 &g_sort
.msgmap
->sort
[1],
184 dup_chk
, mn_get_nmsgs(g_sort
.msgmap
),
187 /* reset the inverse array */
188 msgno_reset_isort(g_sort
.msgmap
);
191 fs_give((void **) &dup_chk
);
194 mail_free_threadnode(&collapsed_tree
);
196 if(any_lflagged(g_sort
.msgmap
, MN_HIDE
))
197 g_sort
.msgmap
->visible_threads
= calculate_visible_threads(stream
);
199 g_sort
.msgmap
->visible_threads
= g_sort
.msgmap
->max_thrdno
;
201 raw_current
= mn_m2raw(g_sort
.msgmap
, mn_get_cur(g_sort
.msgmap
));
203 sp_set_need_to_rethread(stream
, 0);
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
211 && (COLL_THRDS() || SEP_THRDINDX())
212 && mn_get_total(g_sort
.msgmap
) > 1L){
214 collapse_threads(stream
, g_sort
.msgmap
, NULL
);
216 else if(stream
->spare
){
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.
224 if(any_lflagged(g_sort
.msgmap
, MN_USOR
)){
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
);
232 * Node is new, unsorted, top-level thread,
233 * and we're using auto collapse.
235 if(thrd
&& !thrd
->parent
)
236 set_lflag(stream
, g_sort
.msgmap
, msgno
, MN_COLL
, 1);
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.
244 if(thrd
&& thrd
->parent
){
245 thrd
= fetch_thread(stream
, thrd
->parent
);
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);
255 thrd
= fetch_thread(stream
, thrd
->parent
);
265 set_lflags(stream
, g_sort
.msgmap
, MN_USOR
, 0);
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
);
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.
283 set_lflags(stream
, g_sort
.msgmap
, MN_CHID2
, 0);
284 set_thread_lflags(stream
, thrd
, g_sort
.msgmap
,
288 * Outside of the viewed thread everything else
289 * should be collapsed at the top-levels.
291 collapse_threads(stream
, g_sort
.msgmap
, thrd
);
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.
301 msgno
= mn_raw2m(g_sort
.msgmap
, (long) thrd
->rawno
);
303 set_lflag(stream
, g_sort
.msgmap
, msgno
, MN_CHID
, 0);
308 nthrd
= fetch_thread(stream
, thrd
->next
);
310 make_thrdflags_consistent(stream
, g_sort
.msgmap
,
312 get_lflag(stream
, NULL
,
327 set_lflags(stream
, g_sort
.msgmap
, MN_CHID2
, 0);
328 unview_thread(ps_global
, stream
, g_sort
.msgmap
);
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);
336 else if(SEP_THRDINDX()){
337 set_lflags(stream
, g_sort
.msgmap
, MN_CHID2
, 0);
338 collapse_threads(stream
, g_sort
.msgmap
, NULL
);
341 thrd
= fetch_head_thread(stream
);
344 * The top-level threads aren't hidden by collapse.
346 msgno
= mn_raw2m(g_sort
.msgmap
, thrd
->rawno
);
348 set_lflag(stream
, g_sort
.msgmap
, msgno
, MN_CHID
, 0);
353 nthrd
= fetch_thread(stream
, thrd
->next
);
355 make_thrdflags_consistent(stream
, g_sort
.msgmap
,
357 get_lflag(stream
, NULL
,
363 thrd
= fetch_thread(stream
, thrd
->nextthd
);
372 dprint((2, "sort_thread_callback done\n"));
377 collapse_threads(MAILSTREAM
*stream
, MSGNO_S
*msgmap
, PINETHRD_S
*not_this_thread
)
379 PINETHRD_S
*thrd
= NULL
, *nthrd
;
382 dprint((9, "collapse_threads\n"));
384 thrd
= fetch_head_thread(stream
);
386 if(thrd
!= not_this_thread
){
387 msgno
= mn_raw2m(g_sort
.msgmap
, thrd
->rawno
);
389 /* set collapsed bit */
391 set_lflag(stream
, g_sort
.msgmap
, msgno
, MN_COLL
, 1);
392 set_lflag(stream
, g_sort
.msgmap
, msgno
, MN_CHID
, 0);
395 /* hide its children */
396 if(thrd
->next
&& (nthrd
= fetch_thread(stream
, thrd
->next
)))
397 set_thread_subtree(stream
, nthrd
, msgmap
, 1, MN_CHID
);
401 thrd
= fetch_thread(stream
, thrd
->nextthd
);
406 dprint((9, "collapse_threads done\n"));
411 make_thrdflags_consistent(MAILSTREAM
*stream
, MSGNO_S
*msgmap
, PINETHRD_S
*thrd
,
412 int a_parent_is_collapsed
)
414 PINETHRD_S
*nthrd
, *bthrd
;
420 msgno
= mn_raw2m(msgmap
, thrd
->rawno
);
422 if(a_parent_is_collapsed
){
423 /* if some parent is collapsed, we should be hidden */
425 set_lflag(stream
, msgmap
, msgno
, MN_CHID
, 1);
428 /* no parent is collapsed so we are not hidden */
430 set_lflag(stream
, msgmap
, msgno
, MN_CHID
, 0);
434 nthrd
= fetch_thread(stream
, thrd
->next
);
436 make_thrdflags_consistent(stream
, msgmap
, nthrd
,
437 a_parent_is_collapsed
438 ? a_parent_is_collapsed
439 : get_lflag(stream
, NULL
, thrd
->rawno
,
444 bthrd
= fetch_thread(stream
, thrd
->branch
);
446 make_thrdflags_consistent(stream
, msgmap
, bthrd
,
447 a_parent_is_collapsed
);
453 calculate_visible_threads(MAILSTREAM
*stream
)
455 PINETHRD_S
*thrd
= NULL
;
458 thrd
= fetch_head_thread(stream
);
460 vis
+= (thread_has_some_visible(stream
, thrd
) ? 1 : 0);
463 thrd
= fetch_thread(stream
, thrd
->nextthd
);
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.
489 sort_thread_flatten(THREADNODE
*node
, MAILSTREAM
*stream
,
490 long *entry
, char *dup_chk
, long maxno
,
491 PINETHRD_S
*thrd
, unsigned int flags
)
493 PINETHRD_S
*newthrd
= NULL
;
496 if(node
->num
> 0L && node
->num
<= maxno
){ /* holes happen */
497 if(!dup_chk
[node
->num
]){ /* not a duplicate */
499 dup_chk
[node
->num
] = 1;
502 * Build a richer threading structure that will help us paint
503 * and operate on threads and subthreads.
505 newthrd
= msgno_thread_info(stream
, node
->num
, thrd
, flags
);
510 entry
= sort_thread_flatten(node
->next
, stream
,
511 entry
, dup_chk
, maxno
,
515 entry
= sort_thread_flatten(node
->branch
, stream
,
516 entry
, dup_chk
, maxno
,
518 (flags
== THD_TOP
) ? THD_TOP
530 * Make a copy of c-client's THREAD tree while eliminating dummy nodes.
533 collapse_threadnode_tree(THREADNODE
*tree
)
535 THREADNODE
*newtree
= NULL
;
539 newtree
= mail_newthreadnode(NULL
);
540 newtree
->num
= tree
->num
;
542 newtree
->next
= collapse_threadnode_tree(tree
->next
);
545 newtree
->branch
= collapse_threadnode_tree(tree
->branch
);
549 newtree
= collapse_threadnode_tree(tree
->next
);
553 THREADNODE
*last_branch
= NULL
;
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.
561 last_branch
= newtree
;
562 while(last_branch
->branch
)
563 last_branch
= last_branch
->branch
;
565 last_branch
->branch
= collapse_threadnode_tree(tree
->branch
);
568 newtree
= collapse_threadnode_tree(tree
->branch
);
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
584 collapse_threadnode_tree_sorted(THREADNODE
*tree
)
586 THREADNODE
*sorted_tree
= NULL
;
588 sorted_tree
= sort_threads_and_collapse(tree
);
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.
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.
603 * Recurse through the tree, sorting each top-level branch by the
604 * greatest num in the thread.
607 sort_threads_and_collapse(THREADNODE
*tree
)
609 THREADNODE
*newtree
= NULL
, *newbranchtree
= NULL
, *newtreefree
= NULL
;
612 newtree
= mail_newthreadnode(NULL
);
613 newtree
->num
= tree
->num
;
616 * Only sort at the top level. Individual threads can
617 * rely on collapse_threadnode_tree
620 newtree
->next
= collapse_threadnode_tree(tree
->next
);
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.
629 newbranchtree
= sort_threads_and_collapse(tree
->branch
);
633 newtree
= insert_tree_in_place(newtree
, newbranchtree
);
636 * If top node is a dummy, here is where we collapse it.
638 newtreefree
= newtree
;
639 newtree
= insert_tree_in_place(newtree
->next
, newbranchtree
);
640 newtreefree
->next
= NULL
;
641 mail_free_threadnode(&newtreefree
);
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
653 insert_tree_in_place(THREADNODE
*newtree
, THREADNODE
*tree
)
655 THREADNODE
*node
= NULL
;
656 unsigned long newtree_greatest_num
= 0;
658 node
= newtree
->branch
;
659 newtree
->branch
= NULL
;
660 tree
= insert_tree_in_place(node
, tree
);
663 newtree_greatest_num
= branch_greatest_num(newtree
, 0);
667 * Since tree is already sorted, we can insert when we find something
668 * newtree is less than
670 if(newtree_greatest_num
< branch_greatest_num(tree
, 0))
671 newtree
->branch
= tree
;
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
;
681 node
->branch
= newtree
;
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.
696 branch_greatest_num(THREADNODE
*tree
, int is_subthread
)
698 unsigned long ret
, branch_ret
;
702 if(tree
->next
&& (branch_ret
= branch_greatest_num(tree
->next
, 1)) > ret
)
704 if(is_subthread
&& tree
->branch
&&
705 (branch_ret
= branch_greatest_num(tree
->branch
, 1)) > ret
)
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
720 msgno_thread_info(MAILSTREAM
*stream
, long unsigned int rawno
,
721 PINETHRD_S
*attached_to_thrd
, unsigned int flags
)
726 if(!stream
|| rawno
< 1L || rawno
> stream
->nmsgs
)
730 * any private elt data yet?
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
));
738 if((*peltp
)->pthrd
== NULL
)
739 (*peltp
)->pthrd
= (PINETHRD_S
*) fs_get(sizeof(PINETHRD_S
));
741 memset((*peltp
)->pthrd
, 0, sizeof(PINETHRD_S
));
743 (*peltp
)->pthrd
->rawno
= rawno
;
746 (*peltp
)->pthrd
->head
= attached_to_thrd
->head
;
748 (*peltp
)->pthrd
->head
= (*peltp
)->pthrd
->rawno
; /* it's me */
750 if(flags
== THD_TOP
){
752 * We can tell this thread is a top-level thread because it doesn't
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;
762 (*peltp
)->pthrd
->thrdno
= 1L; /* 1st thread */
764 g_sort
.msgmap
->max_thrdno
= (*peltp
)->pthrd
->thrdno
;
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
;
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
;
781 return((*peltp
)->pthrd
);
786 * Collapse or expand a threading subtree. Not called from separate thread
790 collapse_or_expand(struct pine
*state
, MAILSTREAM
*stream
, MSGNO_S
*msgmap
,
791 long unsigned int msgno
)
793 int collapsed
, adjust_current
= 0;
794 PINETHRD_S
*thrd
= NULL
, *nthrd
;
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.
806 if(msgno
> 0L && msgno
<= mn_get_total(msgmap
)){
807 rawno
= mn_m2raw(msgmap
, msgno
);
809 thrd
= fetch_thread(stream
, rawno
);
811 else if(msgno
== 0L){
812 rawno
= mn_m2raw(msgmap
, mn_get_cur(msgmap
));
814 thrd
= fetch_thread(stream
, rawno
);
816 if(thrd
&& thrd
->top
!= thrd
->rawno
){
818 thrd
= fetch_thread(stream
, thrd
->top
);
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.
832 collapsed
= get_lflag(stream
, NULL
, thrd
->rawno
, MN_COLL
)
835 if(!collapsed
&& get_lflag(stream
, NULL
, thrd
->rawno
, MN_HIDE
))
836 thrd
= fetch_thread(stream
, rawno
);
844 collapsed
= get_lflag(stream
, NULL
, thrd
->rawno
, MN_COLL
) && thrd
->next
;
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);
851 if((nthrd
= fetch_thread(stream
, thrd
->next
)) != NULL
)
852 set_thread_subtree(stream
, nthrd
, msgmap
, 0, MN_CHID
);
854 clear_index_cache_ent(stream
, msgno
, 0);
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
);
865 clear_index_cache_ent(stream
, msgno
, 0);
869 q_status_message(SM_ORDER
, 0, 1,
870 _("No thread to collapse or expand on this line"));
872 /* if current is hidden, adjust */
874 adjust_cur_to_visible(stream
, msgmap
);
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.
885 select_thread_stmp(struct pine
*state
, MAILSTREAM
*stream
, MSGNO_S
*msgmap
)
887 PINETHRD_S
*thrd
= NULL
;
888 unsigned long rawno
, in_thread
, set_in_thread
, save_branch
;
890 /* ugly bit means the same thing as return of 1 from individual_select */
891 state
->ugly_consider_advancing_bit
= 0;
893 if(!(stream
&& msgmap
))
896 rawno
= mn_m2raw(msgmap
, mn_get_cur(msgmap
));
898 thrd
= fetch_thread(stream
, rawno
);
903 /* run through thrd to see if it is all selected */
904 save_branch
= thrd
->branch
;
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
))){
909 * If everything is selected, the first unselect should cause
910 * an autozoom. In order to trigger the right code in
913 * we set the MN_HIDE bit on the current message here.
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);
921 set_thread_lflags(stream
, thrd
, msgmap
, MN_STMP
, 1);
922 state
->ugly_consider_advancing_bit
= 1;
925 thrd
->branch
= save_branch
;
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
));
934 q_status_message1(SM_ORDER
, 0, 3,
935 _("Selected %s more messages in thread"),
936 comatose((long) (in_thread
-set_in_thread
)));
941 * Count how many of this system flag in this thread subtree.
942 * If flags == 0 count the messages in the thread.
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.
950 count_flags_in_thread(MAILSTREAM
*stream
, PINETHRD_S
*thrd
, long int flags
)
952 unsigned long count
= 0;
953 PINETHRD_S
*nthrd
, *bthrd
;
956 if(!thrd
|| !stream
|| thrd
->rawno
< 1L || thrd
->rawno
> stream
->nmsgs
)
960 nthrd
= fetch_thread(stream
, thrd
->next
);
962 count
+= count_flags_in_thread(stream
, nthrd
, flags
);
966 bthrd
= fetch_thread(stream
, thrd
->branch
);
968 count
+= count_flags_in_thread(stream
, bthrd
, flags
);
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
))
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.
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.
991 count_lflags_in_thread(MAILSTREAM
*stream
, PINETHRD_S
*thrd
, MSGNO_S
*msgmap
, int flags
)
993 unsigned long count
= 0;
994 PINETHRD_S
*nthrd
, *bthrd
;
996 if(!thrd
|| !stream
|| thrd
->rawno
< 1L || thrd
->rawno
> stream
->nmsgs
)
1000 nthrd
= fetch_thread(stream
, thrd
->next
);
1002 count
+= count_lflags_in_thread(stream
, nthrd
, msgmap
, flags
);
1006 bthrd
= fetch_thread(stream
, thrd
->branch
);
1008 count
+= count_lflags_in_thread(stream
, bthrd
, msgmap
,flags
);
1011 if(flags
== MN_NONE
)
1014 count
+= get_lflag(stream
, msgmap
, mn_raw2m(msgmap
, thrd
->rawno
), flags
);
1021 * Special-purpose for performance improvement.
1024 thread_has_some_visible(MAILSTREAM
*stream
, PINETHRD_S
*thrd
)
1026 PINETHRD_S
*nthrd
, *bthrd
;
1028 if(!thrd
|| !stream
|| thrd
->rawno
< 1L || thrd
->rawno
> stream
->nmsgs
)
1031 if(get_lflag(stream
, NULL
, thrd
->rawno
, MN_HIDE
) == 0)
1035 nthrd
= fetch_thread(stream
, thrd
->next
);
1036 if(nthrd
&& thread_has_some_visible(stream
, nthrd
))
1041 bthrd
= fetch_thread(stream
, thrd
->branch
);
1042 if(bthrd
&& thread_has_some_visible(stream
, bthrd
))
1051 mark_msgs_in_thread(MAILSTREAM
*stream
, PINETHRD_S
*thrd
, MSGNO_S
*msgmap
)
1054 PINETHRD_S
*nthrd
, *bthrd
;
1057 if(!thrd
|| !stream
|| thrd
->rawno
< 1L || thrd
->rawno
> stream
->nmsgs
)
1061 nthrd
= fetch_thread(stream
, thrd
->next
);
1063 count
+= mark_msgs_in_thread(stream
, nthrd
, msgmap
);
1067 bthrd
= fetch_thread(stream
, thrd
->branch
);
1069 count
+= mark_msgs_in_thread(stream
, bthrd
, msgmap
);
1072 if(stream
&& thrd
->rawno
>= 1L && thrd
->rawno
<= stream
->nmsgs
&&
1073 (mc
= mail_elt(stream
,thrd
->rawno
))
1075 && !mc
->private.msg
.env
){
1085 * This sets or clears flags for the messages at this node and below in
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.
1094 set_thread_lflags(MAILSTREAM
*stream
, PINETHRD_S
*thrd
, MSGNO_S
*msgmap
, int flags
, int v
)
1098 /* flags to set or clear */
1101 unsigned long msgno
;
1102 PINETHRD_S
*nthrd
, *bthrd
;
1104 if(!thrd
|| !stream
|| thrd
->rawno
< 1L || thrd
->rawno
> stream
->nmsgs
)
1107 msgno
= mn_raw2m(msgmap
, thrd
->rawno
);
1109 set_lflag(stream
, msgmap
, msgno
, flags
, v
);
1112 * Careful, performance hack. This should logically be a separate
1113 * operation on the thread but it is convenient to stick it in here.
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
1122 if(msgno
> 0L && flags
== MN_CHID2
&& v
== 1)
1123 clear_index_cache_ent(stream
, msgno
, 0);
1126 nthrd
= fetch_thread(stream
, thrd
->next
);
1128 set_thread_lflags(stream
, nthrd
, msgmap
, flags
, v
);
1132 bthrd
= fetch_thread(stream
, thrd
->branch
);
1134 set_thread_lflags(stream
, bthrd
, msgmap
, flags
, v
);
1140 status_symbol_for_thread(MAILSTREAM
*stream
, PINETHRD_S
*thrd
, IndexColType type
)
1143 unsigned long save_branch
, cnt
, tot_in_thrd
;
1145 if(!thrd
|| !stream
|| thrd
->rawno
< 1L || thrd
->rawno
> stream
->nmsgs
)
1148 save_branch
= thrd
->branch
;
1149 thrd
->branch
= 0L; /* branch is a sibling, not part of thread */
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,
1158 if(type
== iStatus
){
1159 tot_in_thrd
= count_flags_in_thread(stream
, thrd
, F_NONE
);
1161 if(count_flags_in_thread(stream
, thrd
, F_DEL
) == tot_in_thrd
)
1164 else if(count_flags_in_thread(stream
, thrd
, F_ANS
) == tot_in_thrd
)
1167 else if(count_flags_in_thread(stream
, thrd
, F_FWD
) == tot_in_thrd
)
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
))
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
);
1180 status
= (cnt
== tot_in_thrd
) ? 'N' : 'n';
1183 else if(type
== iIStatus
|| type
== iSIStatus
){
1184 tot_in_thrd
= count_flags_in_thread(stream
, thrd
, F_NONE
);
1186 /* unseen and recent */
1187 cnt
= count_flags_in_thread(stream
, thrd
, F_RECENT
|F_UNSEEN
);
1189 status
= (cnt
== tot_in_thrd
) ? 'N' : 'n';
1191 /* unseen and !recent */
1192 cnt
= count_flags_in_thread(stream
, thrd
, F_UNSEEN
);
1194 status
= (cnt
== tot_in_thrd
) ? 'U' : 'u';
1196 /* seen and recent */
1197 cnt
= count_flags_in_thread(stream
, thrd
, F_RECENT
|F_SEEN
);
1199 status
= (cnt
== tot_in_thrd
) ? 'R' : 'r';
1204 thrd
->branch
= save_branch
;
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.
1216 to_us_symbol_for_thread(MAILSTREAM
*stream
, PINETHRD_S
*thrd
, int consider_flagged
)
1219 char branch_to_us
= ' ';
1220 PINETHRD_S
*nthrd
, *bthrd
;
1223 if(!thrd
|| !stream
|| thrd
->rawno
< 1L || thrd
->rawno
> stream
->nmsgs
)
1227 nthrd
= fetch_thread(stream
, thrd
->next
);
1229 to_us
= to_us_symbol_for_thread(stream
, nthrd
, consider_flagged
);
1232 if(((consider_flagged
&& to_us
!= '*') || (!consider_flagged
&& to_us
!= '+'))
1234 bthrd
= fetch_thread(stream
, thrd
->branch
);
1236 branch_to_us
= to_us_symbol_for_thread(stream
, bthrd
, consider_flagged
);
1238 /* use branch to_us symbol if it has higher priority than what we have so far */
1240 if(branch_to_us
== '-' || branch_to_us
== '+' || branch_to_us
== '*')
1241 to_us
= branch_to_us
;
1243 else if(to_us
== '-'){
1244 if(branch_to_us
== '+' || branch_to_us
== '*')
1245 to_us
= branch_to_us
;
1247 else if(to_us
== '+'){
1248 if(branch_to_us
== '*')
1249 to_us
= branch_to_us
;
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
))
1259 else if(to_us
!= '+' && !IS_NEWS(stream
)){
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
));
1277 for(addr
= fetch_to(&idata
); addr
; addr
= addr
->next
)
1278 if(address_is_us(addr
, ps_global
)){
1283 if(to_us
!= '+' && resent_to_us(&idata
))
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
)){
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
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.
1311 set_thread_subtree(MAILSTREAM
*stream
, PINETHRD_S
*thrd
, MSGNO_S
*msgmap
, int v
, int flags
)
1316 /* flags to set or clear */
1319 unsigned long msgno
;
1320 PINETHRD_S
*nthrd
, *bthrd
;
1322 hiding
= (flags
== MN_CHID
) && v
;
1324 if(!thrd
|| !stream
|| thrd
->rawno
< 1L || thrd
->rawno
> stream
->nmsgs
)
1327 msgno
= mn_raw2m(msgmap
, thrd
->rawno
);
1329 set_lflag(stream
, msgmap
, msgno
, flags
, v
);
1331 if(thrd
->next
&& (hiding
|| !get_lflag(stream
,NULL
,thrd
->rawno
,MN_COLL
))){
1332 nthrd
= fetch_thread(stream
, thrd
->next
);
1334 set_thread_subtree(stream
, nthrd
, msgmap
, v
, flags
);
1338 bthrd
= fetch_thread(stream
, thrd
->branch
);
1340 set_thread_subtree(stream
, bthrd
, msgmap
, v
, flags
);
1346 * View a thread. Move from the thread index screen to a message index
1347 * screen for the current thread.
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.
1356 view_thread(struct pine
*state
, MAILSTREAM
*stream
, MSGNO_S
*msgmap
, int set_lflags
)
1358 PINETHRD_S
*thrd
= NULL
;
1359 unsigned long rawno
, cur
;
1361 if(!any_messages(msgmap
, NULL
, "to View"))
1364 if(!(stream
&& msgmap
))
1367 rawno
= mn_m2raw(msgmap
, mn_get_cur(msgmap
));
1369 thrd
= fetch_thread(stream
, rawno
);
1371 if(thrd
&& thrd
->top
&& thrd
->top
!= thrd
->rawno
)
1372 thrd
= fetch_thread(stream
, thrd
->top
);
1378 * Clear hidden and collapsed flag for this thread.
1380 * Don't have to worry about there being a branch because
1381 * this is a toplevel thread.
1384 set_thread_lflags(stream
, thrd
, msgmap
, MN_COLL
| MN_CHID
, 0);
1385 set_thread_lflags(stream
, thrd
, msgmap
, MN_CHID2
, 1);
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).
1393 if(mn_get_revsort(msgmap
)){
1394 cur
= mn_get_cur(msgmap
);
1395 while(cur
> 1L && get_lflag(stream
, msgmap
, cur
-1L, MN_CHID2
))
1398 if(cur
!= mn_get_cur(msgmap
))
1399 mn_set_cur(msgmap
, cur
);
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
))
1408 if(cur
!= mn_get_cur(msgmap
))
1409 mn_set_cur(msgmap
, cur
);
1412 msgmap
->top
= mn_get_cur(msgmap
);
1413 sp_set_viewing_a_thread(stream
, 1);
1415 state
->mangled_screen
= 1;
1416 setup_for_index_index_screen();
1423 unview_thread(struct pine
*state
, MAILSTREAM
*stream
, MSGNO_S
*msgmap
)
1425 PINETHRD_S
*thrd
= NULL
, *topthrd
= NULL
;
1426 unsigned long rawno
;
1428 if(!(stream
&& msgmap
))
1431 rawno
= mn_m2raw(msgmap
, mn_get_cur(msgmap
));
1433 thrd
= fetch_thread(stream
, rawno
);
1435 if(thrd
&& thrd
->top
)
1436 topthrd
= fetch_thread(stream
, thrd
->top
);
1441 /* hide this thread */
1442 set_thread_lflags(stream
, topthrd
, msgmap
, MN_CHID
, 1);
1444 /* clear special CHID2 flags for this thread */
1445 set_thread_lflags(stream
, topthrd
, msgmap
, MN_CHID2
, 0);
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);
1451 mn_set_cur(msgmap
, mn_raw2m(msgmap
, topthrd
->rawno
));
1452 sp_set_viewing_a_thread(stream
, 0);
1453 setup_for_thread_index_screen();
1460 find_thread_by_number(MAILSTREAM
*stream
, MSGNO_S
*msgmap
, long int target
, PINETHRD_S
*startthrd
)
1462 PINETHRD_S
*thrd
= NULL
;
1464 if(!(stream
&& msgmap
))
1469 if(!thrd
|| !(thrd
->prevthd
|| thrd
->nextthd
))
1470 thrd
= fetch_thread(stream
, mn_m2raw(msgmap
, mn_get_cur(msgmap
)));
1472 if(thrd
&& !(thrd
->prevthd
|| thrd
->nextthd
) && thrd
->head
)
1473 thrd
= fetch_thread(stream
, thrd
->head
);
1476 /* go forward from here */
1477 if(thrd
->thrdno
< target
){
1479 if(thrd
->thrdno
== target
)
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
);
1490 /* back up from here */
1491 else if(thrd
->thrdno
> target
1492 && (mn_get_revsort(msgmap
)
1493 || (thrd
->thrdno
- target
) < (target
- 1L))){
1495 if(thrd
->thrdno
== target
)
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
);
1506 /* go forward from head */
1507 else if(thrd
->thrdno
> target
){
1509 thrd
= fetch_thread(stream
, thrd
->head
);
1511 if(thrd
->thrdno
== target
)
1515 thrd
= fetch_thread(stream
, thrd
->nextthd
);
1528 * Set search bit for every message in a thread.
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.
1535 * If msgset is non-NULL, then only set the search bit for a message if that
1536 * message is included in the msgset.
1539 set_search_bit_for_thread(MAILSTREAM
*stream
, PINETHRD_S
*thrd
, SEARCHSET
**msgset
)
1541 PINETHRD_S
*nthrd
, *bthrd
;
1543 if(!(stream
&& thrd
))
1546 if(thrd
->rawno
> 0L && thrd
->rawno
<= stream
->nmsgs
1547 && (!(msgset
&& *msgset
) || in_searchset(*msgset
, thrd
->rawno
)))
1548 mm_searched(stream
, thrd
->rawno
);
1551 nthrd
= fetch_thread(stream
, thrd
->next
);
1553 set_search_bit_for_thread(stream
, nthrd
, msgset
);
1557 bthrd
= fetch_thread(stream
, thrd
->branch
);
1559 set_search_bit_for_thread(stream
, bthrd
, msgset
);