2 * ========================================================================
3 * Copyright 2013-2022 Eduardo Chappa
4 * Copyright 2006-2008 University of Washington
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * ========================================================================
15 #include "../pith/headers.h"
16 #include "../pith/thread.h"
17 #include "../pith/flag.h"
18 #include "../pith/icache.h"
19 #include "../pith/mailindx.h"
20 #include "../pith/msgno.h"
21 #include "../pith/sort.h"
22 #include "../pith/pineelt.h"
23 #include "../pith/status.h"
24 #include "../pith/news.h"
25 #include "../pith/search.h"
26 #include "../pith/mailcmd.h"
27 #include "../pith/ablookup.h"
33 long *sort_thread_flatten(THREADNODE
*, MAILSTREAM
*, long *,
34 char *, long, PINETHRD_S
*, unsigned);
35 void make_thrdflags_consistent(MAILSTREAM
*, MSGNO_S
*, PINETHRD_S
*, int);
36 THREADNODE
*collapse_threadnode_tree(THREADNODE
*);
37 THREADNODE
*collapse_threadnode_tree_sorted(THREADNODE
*);
38 THREADNODE
*sort_threads_and_collapse(THREADNODE
*);
39 THREADNODE
*insert_tree_in_place(THREADNODE
*, THREADNODE
*);
40 unsigned long branch_greatest_num(THREADNODE
*, int);
41 long calculate_visible_threads(MAILSTREAM
*);
45 fetch_thread(MAILSTREAM
*stream
, long unsigned int rawno
)
49 PINETHRD_S
*thrd
= NULL
;
51 if(stream
&& rawno
> 0L && rawno
<= stream
->nmsgs
52 && !sp_need_to_rethread(stream
)){
53 mc
= (rawno
> 0L && stream
&& rawno
<= stream
->nmsgs
)
54 ? mail_elt(stream
, rawno
) : NULL
;
55 if(mc
&& (pelt
= (PINELT_S
*) mc
->sparep
))
64 fetch_head_thread(MAILSTREAM
*stream
)
67 PINETHRD_S
*thrd
= NULL
;
70 /* first find any thread */
71 for(rawno
= 1L; !thrd
&& rawno
<= stream
->nmsgs
; rawno
++)
72 thrd
= fetch_thread(stream
, rawno
);
74 if(thrd
&& thrd
->head
)
75 thrd
= fetch_thread(stream
, thrd
->head
);
83 * Set flag f to v for all messages in thrd.
85 * Watch out when calling this. The thrd->branch is not part of thrd.
86 * Branch is a sibling to thrd, not a child. Zero out branch before calling
87 * or call on thrd->next and worry about thrd separately.
88 * Ok to call it on top-level thread which has no branch already.
91 set_flags_for_thread(MAILSTREAM
*stream
, MSGNO_S
*msgmap
, int f
, PINETHRD_S
*thrd
, int v
)
93 PINETHRD_S
*nthrd
, *bthrd
;
95 if(!(stream
&& thrd
&& msgmap
))
98 set_lflag(stream
, msgmap
, mn_raw2m(msgmap
, thrd
->rawno
), f
, v
);
101 nthrd
= fetch_thread(stream
, thrd
->next
);
103 set_flags_for_thread(stream
, msgmap
, f
, nthrd
, v
);
107 bthrd
= fetch_thread(stream
, thrd
->branch
);
109 set_flags_for_thread(stream
, msgmap
, f
, bthrd
, v
);
115 erase_threading_info(MAILSTREAM
*stream
, MSGNO_S
*msgmap
)
121 if(!(stream
&& stream
->spare
))
124 ps_global
->view_skipped_index
= 0;
125 sp_set_viewing_a_thread(stream
, 0);
128 setup_for_thread_index_screen();
130 setup_for_index_index_screen();
134 for(n
= 1L; n
<= stream
->nmsgs
; n
++){
135 set_lflag(stream
, msgmap
, mn_raw2m(msgmap
, n
),
136 MN_COLL
| MN_CHID
| MN_CHID2
, 0);
137 mc
= mail_elt(stream
, n
);
138 if(mc
&& mc
->sparep
){
139 peltp
= (PINELT_S
*) mc
->sparep
;
141 fs_give((void **) &peltp
->pthrd
);
148 sort_thread_callback(MAILSTREAM
*stream
, THREADNODE
*tree
)
150 THREADNODE
*collapsed_tree
= NULL
;
151 PINETHRD_S
*thrd
= NULL
;
152 unsigned long msgno
, rawno
;
153 int un_view_thread
= 0;
155 char *dup_chk
= NULL
;
158 dprint((2, "sort_thread_callback\n"));
160 g_sort
.msgmap
->max_thrdno
= 0L;
163 * Eliminate dummy nodes from tree and collapse the tree in a logical
164 * way. If the dummy node is at the top-level, then its children are
165 * promoted to the top-level as separate threads.
167 if(F_ON(F_THREAD_SORTS_BY_ARRIVAL
, ps_global
))
168 collapsed_tree
= collapse_threadnode_tree_sorted(tree
);
170 collapsed_tree
= collapse_threadnode_tree(tree
);
172 /* dup_chk is like sort with an origin of 1 */
173 dup_chk
= (char *) fs_get((mn_get_nmsgs(g_sort
.msgmap
)+1) * sizeof(char));
174 memset(dup_chk
, 0, (mn_get_nmsgs(g_sort
.msgmap
)+1) * sizeof(char));
176 memset(&g_sort
.msgmap
->sort
[1], 0, mn_get_total(g_sort
.msgmap
) * sizeof(long));
178 (void) sort_thread_flatten(collapsed_tree
, stream
,
179 &g_sort
.msgmap
->sort
[1],
180 dup_chk
, mn_get_nmsgs(g_sort
.msgmap
),
183 /* reset the inverse array */
184 msgno_reset_isort(g_sort
.msgmap
);
187 fs_give((void **) &dup_chk
);
190 mail_free_threadnode(&collapsed_tree
);
192 if(any_lflagged(g_sort
.msgmap
, MN_HIDE
))
193 g_sort
.msgmap
->visible_threads
= calculate_visible_threads(stream
);
195 g_sort
.msgmap
->visible_threads
= g_sort
.msgmap
->max_thrdno
;
197 raw_current
= mn_m2raw(g_sort
.msgmap
, mn_get_cur(g_sort
.msgmap
));
199 sp_set_need_to_rethread(stream
, 0);
202 * Set appropriate bits to start out collapsed if desired. We use the
203 * stream spare bit to tell us if we've done this before for this
207 && (COLL_THRDS() || SEP_THRDINDX())
208 && mn_get_total(g_sort
.msgmap
) > 1L){
210 collapse_threads(stream
, g_sort
.msgmap
, NULL
);
212 else if(stream
->spare
){
215 * If we're doing auto collapse then new threads need to have
216 * their collapse bit set. This happens below if we're in the
217 * thread index, but if we're in the regular index with auto
218 * collapse we have to look for these.
220 if(any_lflagged(g_sort
.msgmap
, MN_USOR
)){
222 for(msgno
= 1L; msgno
<= mn_get_total(g_sort
.msgmap
); msgno
++){
223 rawno
= mn_m2raw(g_sort
.msgmap
, msgno
);
224 if(get_lflag(stream
, NULL
, rawno
, MN_USOR
)){
225 thrd
= fetch_thread(stream
, rawno
);
228 * Node is new, unsorted, top-level thread,
229 * and we're using auto collapse.
231 if(thrd
&& !thrd
->parent
)
232 set_lflag(stream
, g_sort
.msgmap
, msgno
, MN_COLL
, 1);
235 * If a parent is collapsed, clear that parent's
236 * index cache entry. This is only necessary if
237 * the parent's index display can depend on its
238 * children, of course.
240 if(thrd
&& thrd
->parent
){
241 thrd
= fetch_thread(stream
, thrd
->parent
);
245 if(get_lflag(stream
, NULL
, thrd
->rawno
, MN_COLL
)
246 && (t
= mn_raw2m(g_sort
.msgmap
,
247 (long) thrd
->rawno
)))
248 clear_index_cache_ent(stream
, t
, 0);
251 thrd
= fetch_thread(stream
, thrd
->parent
);
261 set_lflags(stream
, g_sort
.msgmap
, MN_USOR
, 0);
264 if(sp_viewing_a_thread(stream
)){
265 if(any_lflagged(g_sort
.msgmap
, MN_CHID2
)){
266 /* current should be part of viewed thread */
267 if(get_lflag(stream
, NULL
, raw_current
, MN_CHID2
)){
268 thrd
= fetch_thread(stream
, raw_current
);
269 if(thrd
&& thrd
->top
&& thrd
->top
!= thrd
->rawno
)
270 thrd
= fetch_thread(stream
, thrd
->top
);
274 * For messages that are part of thread set MN_CHID2
275 * and for messages that aren't part of the thread
276 * clear MN_CHID2. Easiest is to just do it instead
277 * of checking if it is true first.
279 set_lflags(stream
, g_sort
.msgmap
, MN_CHID2
, 0);
280 set_thread_lflags(stream
, thrd
, g_sort
.msgmap
,
284 * Outside of the viewed thread everything else
285 * should be collapsed at the top-levels.
287 collapse_threads(stream
, g_sort
.msgmap
, thrd
);
290 * Inside of the thread, the top of the thread
291 * can't be hidden, the rest are hidden if a
292 * parent somewhere above them is collapsed.
293 * There can be collapse points that are hidden
294 * inside of the tree. They remain collapsed even
295 * if the parent above them uncollapses.
297 msgno
= mn_raw2m(g_sort
.msgmap
, (long) thrd
->rawno
);
299 set_lflag(stream
, g_sort
.msgmap
, msgno
, MN_CHID
, 0);
304 nthrd
= fetch_thread(stream
, thrd
->next
);
306 make_thrdflags_consistent(stream
, g_sort
.msgmap
,
308 get_lflag(stream
, NULL
,
323 set_lflags(stream
, g_sort
.msgmap
, MN_CHID2
, 0);
324 unview_thread(ps_global
, stream
, g_sort
.msgmap
);
327 mn_reset_cur(g_sort
.msgmap
,
328 mn_raw2m(g_sort
.msgmap
, raw_current
));
329 view_thread(ps_global
, stream
, g_sort
.msgmap
, 0);
332 else if(SEP_THRDINDX()){
333 set_lflags(stream
, g_sort
.msgmap
, MN_CHID2
, 0);
334 collapse_threads(stream
, g_sort
.msgmap
, NULL
);
337 thrd
= fetch_head_thread(stream
);
340 * The top-level threads aren't hidden by collapse.
342 msgno
= mn_raw2m(g_sort
.msgmap
, thrd
->rawno
);
344 set_lflag(stream
, g_sort
.msgmap
, msgno
, MN_CHID
, 0);
349 nthrd
= fetch_thread(stream
, thrd
->next
);
351 make_thrdflags_consistent(stream
, g_sort
.msgmap
,
353 get_lflag(stream
, NULL
,
359 thrd
= fetch_thread(stream
, thrd
->nextthd
);
368 dprint((2, "sort_thread_callback done\n"));
373 collapse_threads(MAILSTREAM
*stream
, MSGNO_S
*msgmap
, PINETHRD_S
*not_this_thread
)
375 PINETHRD_S
*thrd
= NULL
, *nthrd
;
378 dprint((9, "collapse_threads\n"));
380 thrd
= fetch_head_thread(stream
);
382 if(thrd
!= not_this_thread
){
383 msgno
= mn_raw2m(g_sort
.msgmap
, thrd
->rawno
);
385 /* set collapsed bit */
387 set_lflag(stream
, g_sort
.msgmap
, msgno
, MN_COLL
, 1);
388 set_lflag(stream
, g_sort
.msgmap
, msgno
, MN_CHID
, 0);
391 /* hide its children */
392 if(thrd
->next
&& (nthrd
= fetch_thread(stream
, thrd
->next
)))
393 set_thread_subtree(stream
, nthrd
, msgmap
, 1, MN_CHID
);
397 thrd
= fetch_thread(stream
, thrd
->nextthd
);
402 dprint((9, "collapse_threads done\n"));
407 make_thrdflags_consistent(MAILSTREAM
*stream
, MSGNO_S
*msgmap
, PINETHRD_S
*thrd
,
408 int a_parent_is_collapsed
)
410 PINETHRD_S
*nthrd
, *bthrd
;
416 msgno
= mn_raw2m(msgmap
, thrd
->rawno
);
418 if(a_parent_is_collapsed
){
419 /* if some parent is collapsed, we should be hidden */
421 set_lflag(stream
, msgmap
, msgno
, MN_CHID
, 1);
424 /* no parent is collapsed so we are not hidden */
426 set_lflag(stream
, msgmap
, msgno
, MN_CHID
, 0);
430 nthrd
= fetch_thread(stream
, thrd
->next
);
432 make_thrdflags_consistent(stream
, msgmap
, nthrd
,
433 a_parent_is_collapsed
434 ? a_parent_is_collapsed
435 : get_lflag(stream
, NULL
, thrd
->rawno
,
440 bthrd
= fetch_thread(stream
, thrd
->branch
);
442 make_thrdflags_consistent(stream
, msgmap
, bthrd
,
443 a_parent_is_collapsed
);
449 calculate_visible_threads(MAILSTREAM
*stream
)
451 PINETHRD_S
*thrd
= NULL
;
454 thrd
= fetch_head_thread(stream
);
456 vis
+= (thread_has_some_visible(stream
, thrd
) ? 1 : 0);
459 thrd
= fetch_thread(stream
, thrd
->nextthd
);
469 * This routine does a couple things. The input is the THREADNODE node
470 * that we get from c-client because of the THREAD command. The rest of
471 * the arguments are used to help guide this function through its
472 * recursive steps. One thing it does is to place the sort order in
473 * the array initially pointed to by the entry argument. All it is doing
474 * is walking the tree in the next then branch order you see below and
475 * incrementing the entry number one for each node. The other thing it
476 * is doing at the same time is to create a PINETHRD_S tree from the
477 * THREADNODE tree. The two trees are completely equivalent but the
478 * PINETHRD_S version has additional back pointers and parent pointers
479 * and so on to make it easier for alpine to deal with it. Each node
480 * of that tree is tied to the data associated with a particular message
481 * by the msgno_thread_info() call, so that we can go from a message
482 * number to the place in the thread tree that message sits.
485 sort_thread_flatten(THREADNODE
*node
, MAILSTREAM
*stream
,
486 long *entry
, char *dup_chk
, long maxno
,
487 PINETHRD_S
*thrd
, unsigned int flags
)
489 PINETHRD_S
*newthrd
= NULL
;
492 if(node
->num
> 0L && node
->num
<= maxno
){ /* holes happen */
493 if(!dup_chk
[node
->num
]){ /* not a duplicate */
495 dup_chk
[node
->num
] = 1;
498 * Build a richer threading structure that will help us paint
499 * and operate on threads and subthreads.
501 newthrd
= msgno_thread_info(stream
, node
->num
, thrd
, flags
);
506 entry
= sort_thread_flatten(node
->next
, stream
,
507 entry
, dup_chk
, maxno
,
511 entry
= sort_thread_flatten(node
->branch
, stream
,
512 entry
, dup_chk
, maxno
,
514 (flags
== THD_TOP
) ? THD_TOP
526 * Make a copy of c-client's THREAD tree while eliminating dummy nodes.
529 collapse_threadnode_tree(THREADNODE
*tree
)
531 THREADNODE
*newtree
= NULL
;
535 newtree
= mail_newthreadnode(NULL
);
536 newtree
->num
= tree
->num
;
538 newtree
->next
= collapse_threadnode_tree(tree
->next
);
541 newtree
->branch
= collapse_threadnode_tree(tree
->branch
);
545 newtree
= collapse_threadnode_tree(tree
->next
);
549 THREADNODE
*last_branch
= NULL
;
552 * Next moved up to replace "tree" in the tree.
553 * If next has no branches, then we want to branch off
554 * of next. If next has branches, we want to branch off
555 * of the last of those branches instead.
557 last_branch
= newtree
;
558 while(last_branch
->branch
)
559 last_branch
= last_branch
->branch
;
561 last_branch
->branch
= collapse_threadnode_tree(tree
->branch
);
564 newtree
= collapse_threadnode_tree(tree
->branch
);
574 * Like collapse_threadnode_tree, we collapse the dummy nodes.
575 * In addition we rearrange the threads by order of arrival of
576 * the last message in the thread, rather than the first message
580 collapse_threadnode_tree_sorted(THREADNODE
*tree
)
582 THREADNODE
*sorted_tree
= NULL
;
584 sorted_tree
= sort_threads_and_collapse(tree
);
587 * We used to eliminate top-level dummy nodes here so that
588 * orphans would still get sorted together, but we changed
589 * to sort the orphans themselves as top-level threads.
591 * It might be a matter of choice how they get sorted, but
592 * we'll try doing it this way and not add another feature.
599 * Recurse through the tree, sorting each top-level branch by the
600 * greatest num in the thread.
603 sort_threads_and_collapse(THREADNODE
*tree
)
605 THREADNODE
*newtree
= NULL
, *newbranchtree
= NULL
, *newtreefree
= NULL
;
608 newtree
= mail_newthreadnode(NULL
);
609 newtree
->num
= tree
->num
;
612 * Only sort at the top level. Individual threads can
613 * rely on collapse_threadnode_tree
616 newtree
->next
= collapse_threadnode_tree(tree
->next
);
620 * This recursive call returns an already re-sorted tree.
621 * With that, we can loop through and inject ourselves
622 * where we fit in with that sort, and pass back to the
623 * caller to inject themselves.
625 newbranchtree
= sort_threads_and_collapse(tree
->branch
);
629 newtree
= insert_tree_in_place(newtree
, newbranchtree
);
632 * If top node is a dummy, here is where we collapse it.
634 newtreefree
= newtree
;
635 newtree
= insert_tree_in_place(newtree
->next
, newbranchtree
);
636 newtreefree
->next
= NULL
;
637 mail_free_threadnode(&newtreefree
);
645 * Recursively insert each of the top-level nodes in newtree in their place
646 * in tree according to which tree has the most recent arrival
649 insert_tree_in_place(THREADNODE
*newtree
, THREADNODE
*tree
)
651 THREADNODE
*node
= NULL
;
652 unsigned long newtree_greatest_num
= 0;
654 node
= newtree
->branch
;
655 newtree
->branch
= NULL
;
656 tree
= insert_tree_in_place(node
, tree
);
659 newtree_greatest_num
= branch_greatest_num(newtree
, 0);
663 * Since tree is already sorted, we can insert when we find something
664 * newtree is less than
666 if(newtree_greatest_num
< branch_greatest_num(tree
, 0))
667 newtree
->branch
= tree
;
669 for(node
= tree
; node
->branch
; node
= node
->branch
){
670 if(newtree_greatest_num
< branch_greatest_num(node
->branch
, 0)){
671 newtree
->branch
= node
->branch
;
672 node
->branch
= newtree
;
677 node
->branch
= newtree
;
687 * Given a thread, return the greatest num in the tree.
688 * is_subthread tells us not to recurse through branches, so
689 * we can split the top level into threads.
692 branch_greatest_num(THREADNODE
*tree
, int is_subthread
)
694 unsigned long ret
, branch_ret
;
698 if(tree
->next
&& (branch_ret
= branch_greatest_num(tree
->next
, 1)) > ret
)
700 if(is_subthread
&& tree
->branch
&&
701 (branch_ret
= branch_greatest_num(tree
->branch
, 1)) > ret
)
709 * Args stream -- the usual
710 * rawno -- the raw msg num associated with this new node
711 * attached_to_thrd -- the PINETHRD_S node that this is either a next or branch
716 msgno_thread_info(MAILSTREAM
*stream
, long unsigned int rawno
,
717 PINETHRD_S
*attached_to_thrd
, unsigned int flags
)
719 PINELT_S
**peltp
= NULL
;
722 if(!stream
|| rawno
< 1L || rawno
> stream
->nmsgs
)
726 * any private elt data yet?
728 if((mc
= mail_elt(stream
, rawno
))
729 && (*(peltp
= (PINELT_S
**) &mc
->sparep
) == NULL
)){
730 *peltp
= (PINELT_S
*) fs_get(sizeof(PINELT_S
));
731 memset(*peltp
, 0, sizeof(PINELT_S
));
734 if((*peltp
)->pthrd
== NULL
)
735 (*peltp
)->pthrd
= (PINETHRD_S
*) fs_get(sizeof(PINETHRD_S
));
737 memset((*peltp
)->pthrd
, 0, sizeof(PINETHRD_S
));
739 (*peltp
)->pthrd
->rawno
= rawno
;
742 (*peltp
)->pthrd
->head
= attached_to_thrd
->head
;
744 (*peltp
)->pthrd
->head
= (*peltp
)->pthrd
->rawno
; /* it's me */
746 if(flags
== THD_TOP
){
748 * We can tell this thread is a top-level thread because it doesn't
751 (*peltp
)->pthrd
->top
= (*peltp
)->pthrd
->rawno
; /* I am a top */
752 if(attached_to_thrd
){
753 attached_to_thrd
->nextthd
= (*peltp
)->pthrd
->rawno
;
754 (*peltp
)->pthrd
->prevthd
= attached_to_thrd
->rawno
;
755 (*peltp
)->pthrd
->thrdno
= attached_to_thrd
->thrdno
+ 1L;
758 (*peltp
)->pthrd
->thrdno
= 1L; /* 1st thread */
760 g_sort
.msgmap
->max_thrdno
= (*peltp
)->pthrd
->thrdno
;
762 else if(flags
== THD_NEXT
){
763 if(attached_to_thrd
){
764 attached_to_thrd
->next
= (*peltp
)->pthrd
->rawno
;
765 (*peltp
)->pthrd
->parent
= attached_to_thrd
->rawno
;
766 (*peltp
)->pthrd
->top
= attached_to_thrd
->top
;
769 else if(flags
== THD_BRANCH
){
770 if(attached_to_thrd
){
771 attached_to_thrd
->branch
= (*peltp
)->pthrd
->rawno
;
772 (*peltp
)->pthrd
->parent
= attached_to_thrd
->parent
;
773 (*peltp
)->pthrd
->top
= attached_to_thrd
->top
;
777 return((*peltp
)->pthrd
);
782 * Collapse or expand a threading subtree. Not called from separate thread
786 collapse_or_expand(struct pine
*state
, MAILSTREAM
*stream
, MSGNO_S
*msgmap
,
787 long unsigned int msgno
)
789 int collapsed
, adjust_current
= 0;
790 PINETHRD_S
*thrd
= NULL
, *nthrd
;
797 * If msgno is a good msgno, then we collapse or expand the subthread
798 * which begins at msgno. If msgno is 0, we collapse or expand the
799 * entire current thread.
802 if(msgno
> 0L && msgno
<= mn_get_total(msgmap
)){
803 rawno
= mn_m2raw(msgmap
, msgno
);
805 thrd
= fetch_thread(stream
, rawno
);
807 else if(msgno
== 0L){
808 rawno
= mn_m2raw(msgmap
, mn_get_cur(msgmap
));
810 thrd
= fetch_thread(stream
, rawno
);
812 if(thrd
&& thrd
->top
!= thrd
->rawno
){
814 thrd
= fetch_thread(stream
, thrd
->top
);
817 * Special case. If the user is collapsing the entire thread
818 * (msgno == 0), and we are in a Zoomed view, and the top of
819 * the entire thread is not part of the Zoomed view, then watch
820 * out. If we were to collapse the entire thread it would just
821 * disappear, because the top is not in the Zoom. Therefore,
822 * don't allow it. Do what the user probably wants, which is to
823 * collapse the thread at that point instead of the entire thread,
824 * leaving behind the top of the subthread to expand if needed.
825 * In other words, treat it as if they didn't have the
826 * F_SLASH_COLL_ENTIRE feature set.
828 collapsed
= get_lflag(stream
, NULL
, thrd
->rawno
, MN_COLL
)
831 if(!collapsed
&& get_lflag(stream
, NULL
, thrd
->rawno
, MN_HIDE
))
832 thrd
= fetch_thread(stream
, rawno
);
840 collapsed
= get_lflag(stream
, NULL
, thrd
->rawno
, MN_COLL
) && thrd
->next
;
843 msgno
= mn_raw2m(msgmap
, thrd
->rawno
);
844 if(msgno
> 0L && msgno
<= mn_get_total(msgmap
)){
845 set_lflag(stream
, msgmap
, msgno
, MN_COLL
, 0);
847 if((nthrd
= fetch_thread(stream
, thrd
->next
)) != NULL
)
848 set_thread_subtree(stream
, nthrd
, msgmap
, 0, MN_CHID
);
850 clear_index_cache_ent(stream
, msgno
, 0);
854 else if(thrd
&& thrd
->next
){
855 msgno
= mn_raw2m(msgmap
, thrd
->rawno
);
856 if(msgno
> 0L && msgno
<= mn_get_total(msgmap
)){
857 set_lflag(stream
, msgmap
, msgno
, MN_COLL
, 1);
858 if((nthrd
= fetch_thread(stream
, thrd
->next
)) != NULL
)
859 set_thread_subtree(stream
, nthrd
, msgmap
, 1, MN_CHID
);
861 clear_index_cache_ent(stream
, msgno
, 0);
865 q_status_message(SM_ORDER
, 0, 1,
866 _("No thread to collapse or expand on this line"));
868 /* if current is hidden, adjust */
870 adjust_cur_to_visible(stream
, msgmap
);
875 * Select the messages in a subthread. If all of the messages are already
876 * selected, unselect them. This routine is a bit strange because it
877 * doesn't set the MN_SLCT bit. Instead, it sets MN_STMP in apply_command
878 * and then thread_command copies the MN_STMP messages back to MN_SLCT.
881 select_thread_stmp(struct pine
*state
, MAILSTREAM
*stream
, MSGNO_S
*msgmap
)
883 PINETHRD_S
*thrd
= NULL
;
884 unsigned long rawno
, in_thread
, set_in_thread
, save_branch
;
886 /* ugly bit means the same thing as return of 1 from individual_select */
887 state
->ugly_consider_advancing_bit
= 0;
889 if(!(stream
&& msgmap
))
892 rawno
= mn_m2raw(msgmap
, mn_get_cur(msgmap
));
894 thrd
= fetch_thread(stream
, rawno
);
899 /* run through thrd to see if it is all selected */
900 save_branch
= thrd
->branch
;
902 if((set_in_thread
= count_lflags_in_thread(stream
, thrd
, msgmap
, MN_STMP
))
903 == (in_thread
= count_lflags_in_thread(stream
, thrd
, msgmap
, MN_NONE
))){
905 * If everything is selected, the first unselect should cause
906 * an autozoom. In order to trigger the right code in
909 * we set the MN_HIDE bit on the current message here.
911 if(F_ON(F_AUTO_ZOOM
, state
) && !any_lflagged(msgmap
, MN_HIDE
)
912 && any_lflagged(msgmap
, MN_STMP
) == mn_get_total(msgmap
))
913 set_lflag(stream
, msgmap
, mn_get_cur(msgmap
), MN_HIDE
, 1);
914 set_thread_lflags(stream
, thrd
, msgmap
, MN_STMP
, 0);
917 set_thread_lflags(stream
, thrd
, msgmap
, MN_STMP
, 1);
918 state
->ugly_consider_advancing_bit
= 1;
921 thrd
->branch
= save_branch
;
923 if(set_in_thread
== in_thread
)
924 q_status_message1(SM_ORDER
, 0, 3, _("Unselected %s messages in thread"),
925 comatose((long) in_thread
));
926 else if(set_in_thread
== 0)
927 q_status_message1(SM_ORDER
, 0, 3, _("Selected %s messages in thread"),
928 comatose((long) in_thread
));
930 q_status_message1(SM_ORDER
, 0, 3,
931 _("Selected %s more messages in thread"),
932 comatose((long) (in_thread
-set_in_thread
)));
937 * Count how many of this system flag in this thread subtree.
938 * If flags == 0 count the messages in the thread.
940 * Watch out when calling this. The thrd->branch is not part of thrd.
941 * Branch is a sibling to thrd, not a child. Zero out branch before calling
942 * or call on thrd->next and worry about thrd separately.
943 * Ok to call it on top-level thread which has no branch already.
946 count_flags_in_thread(MAILSTREAM
*stream
, PINETHRD_S
*thrd
, long int flags
)
948 unsigned long count
= 0;
949 PINETHRD_S
*nthrd
, *bthrd
;
952 if(!thrd
|| !stream
|| thrd
->rawno
< 1L || thrd
->rawno
> stream
->nmsgs
)
956 nthrd
= fetch_thread(stream
, thrd
->next
);
958 count
+= count_flags_in_thread(stream
, nthrd
, flags
);
962 bthrd
= fetch_thread(stream
, thrd
->branch
);
964 count
+= count_flags_in_thread(stream
, bthrd
, flags
);
967 mc
= (thrd
&& thrd
->rawno
> 0L && stream
&& thrd
->rawno
<= stream
->nmsgs
)
968 ? mail_elt(stream
, thrd
->rawno
) : NULL
;
969 if(mc
&& mc
->valid
&& FLAG_MATCH(flags
, mc
, stream
))
977 * Count how many of this local flag in this thread subtree.
978 * If flags == MN_NONE then we just count the messages instead of whether
979 * the messages have a flag set.
981 * Watch out when calling this. The thrd->branch is not part of thrd.
982 * Branch is a sibling to thrd, not a child. Zero out branch before calling
983 * or call on thrd->next and worry about thrd separately.
984 * Ok to call it on top-level thread which has no branch already.
987 count_lflags_in_thread(MAILSTREAM
*stream
, PINETHRD_S
*thrd
, MSGNO_S
*msgmap
, int flags
)
989 unsigned long count
= 0;
990 PINETHRD_S
*nthrd
, *bthrd
;
992 if(!thrd
|| !stream
|| thrd
->rawno
< 1L || thrd
->rawno
> stream
->nmsgs
)
996 nthrd
= fetch_thread(stream
, thrd
->next
);
998 count
+= count_lflags_in_thread(stream
, nthrd
, msgmap
, flags
);
1002 bthrd
= fetch_thread(stream
, thrd
->branch
);
1004 count
+= count_lflags_in_thread(stream
, bthrd
, msgmap
,flags
);
1007 if(flags
== MN_NONE
)
1010 count
+= get_lflag(stream
, msgmap
, mn_raw2m(msgmap
, thrd
->rawno
), flags
);
1017 * Special-purpose for performance improvement.
1020 thread_has_some_visible(MAILSTREAM
*stream
, PINETHRD_S
*thrd
)
1022 PINETHRD_S
*nthrd
, *bthrd
;
1024 if(!thrd
|| !stream
|| thrd
->rawno
< 1L || thrd
->rawno
> stream
->nmsgs
)
1027 if(get_lflag(stream
, NULL
, thrd
->rawno
, MN_HIDE
) == 0)
1031 nthrd
= fetch_thread(stream
, thrd
->next
);
1032 if(nthrd
&& thread_has_some_visible(stream
, nthrd
))
1037 bthrd
= fetch_thread(stream
, thrd
->branch
);
1038 if(bthrd
&& thread_has_some_visible(stream
, bthrd
))
1047 mark_msgs_in_thread(MAILSTREAM
*stream
, PINETHRD_S
*thrd
, MSGNO_S
*msgmap
)
1050 PINETHRD_S
*nthrd
, *bthrd
;
1053 if(!thrd
|| !stream
|| thrd
->rawno
< 1L || thrd
->rawno
> stream
->nmsgs
)
1057 nthrd
= fetch_thread(stream
, thrd
->next
);
1059 count
+= mark_msgs_in_thread(stream
, nthrd
, msgmap
);
1063 bthrd
= fetch_thread(stream
, thrd
->branch
);
1065 count
+= mark_msgs_in_thread(stream
, bthrd
, msgmap
);
1068 if(stream
&& thrd
->rawno
>= 1L && thrd
->rawno
<= stream
->nmsgs
&&
1069 (mc
= mail_elt(stream
,thrd
->rawno
))
1071 && !mc
->private.msg
.env
){
1081 * This sets or clears flags for the messages at this node and below in
1084 * Watch out when calling this. The thrd->branch is not part of thrd.
1085 * Branch is a sibling to thrd, not a child. Zero out branch before calling
1086 * or call on thrd->next and worry about thrd separately.
1087 * Ok to call it on top-level thread which has no branch already.
1090 set_thread_lflags(MAILSTREAM
*stream
, PINETHRD_S
*thrd
, MSGNO_S
*msgmap
, int flags
, int v
)
1094 /* flags to set or clear */
1097 unsigned long msgno
;
1098 PINETHRD_S
*nthrd
, *bthrd
;
1100 if(!thrd
|| !stream
|| thrd
->rawno
< 1L || thrd
->rawno
> stream
->nmsgs
)
1103 msgno
= mn_raw2m(msgmap
, thrd
->rawno
);
1105 set_lflag(stream
, msgmap
, msgno
, flags
, v
);
1108 * Careful, performance hack. This should logically be a separate
1109 * operation on the thread but it is convenient to stick it in here.
1111 * When we back out of viewing a thread to the separate-thread-index
1112 * we may leave behind some cached hlines that aren't quite right
1113 * because they were collapsed. In particular, the plus_col character
1114 * may be wrong. Instead of trying to figure out what it should be just
1115 * clear the cache entries for the this thread when we come back in
1118 if(msgno
> 0L && flags
== MN_CHID2
&& v
== 1)
1119 clear_index_cache_ent(stream
, msgno
, 0);
1122 nthrd
= fetch_thread(stream
, thrd
->next
);
1124 set_thread_lflags(stream
, nthrd
, msgmap
, flags
, v
);
1128 bthrd
= fetch_thread(stream
, thrd
->branch
);
1130 set_thread_lflags(stream
, bthrd
, msgmap
, flags
, v
);
1136 status_symbol_for_thread(MAILSTREAM
*stream
, PINETHRD_S
*thrd
, IndexColType type
)
1139 unsigned long save_branch
, cnt
, tot_in_thrd
;
1141 if(!thrd
|| !stream
|| thrd
->rawno
< 1L || thrd
->rawno
> stream
->nmsgs
)
1144 save_branch
= thrd
->branch
;
1145 thrd
->branch
= 0L; /* branch is a sibling, not part of thread */
1148 * This is D if all of thread is deleted,
1149 * else A if all of thread is answered,
1150 * else F if all of thread is forwarded,
1151 * else N if any unseen and not deleted,
1154 if(type
== iStatus
){
1155 tot_in_thrd
= count_flags_in_thread(stream
, thrd
, F_NONE
);
1157 if(count_flags_in_thread(stream
, thrd
, F_DEL
) == tot_in_thrd
)
1160 else if(count_flags_in_thread(stream
, thrd
, F_ANS
) == tot_in_thrd
)
1163 else if(count_flags_in_thread(stream
, thrd
, F_FWD
) == tot_in_thrd
)
1165 /* or any new and not deleted */
1166 else if((!IS_NEWS(stream
)
1167 || F_ON(F_FAKE_NEW_IN_NEWS
, ps_global
))
1168 && count_flags_in_thread(stream
, thrd
, F_UNDEL
|F_UNSEEN
))
1171 else if(type
== iFStatus
){
1172 if(!IS_NEWS(stream
) || F_ON(F_FAKE_NEW_IN_NEWS
, ps_global
)){
1173 tot_in_thrd
= count_flags_in_thread(stream
, thrd
, F_NONE
);
1174 cnt
= count_flags_in_thread(stream
, thrd
, F_UNSEEN
);
1176 status
= (cnt
== tot_in_thrd
) ? 'N' : 'n';
1179 else if(type
== iIStatus
|| type
== iSIStatus
){
1180 tot_in_thrd
= count_flags_in_thread(stream
, thrd
, F_NONE
);
1182 /* unseen and recent */
1183 cnt
= count_flags_in_thread(stream
, thrd
, F_RECENT
|F_UNSEEN
);
1185 status
= (cnt
== tot_in_thrd
) ? 'N' : 'n';
1187 /* unseen and !recent */
1188 cnt
= count_flags_in_thread(stream
, thrd
, F_UNSEEN
);
1190 status
= (cnt
== tot_in_thrd
) ? 'U' : 'u';
1192 /* seen and recent */
1193 cnt
= count_flags_in_thread(stream
, thrd
, F_RECENT
|F_SEEN
);
1195 status
= (cnt
== tot_in_thrd
) ? 'R' : 'r';
1200 thrd
->branch
= save_branch
;
1207 * Symbol is * if some message in thread is important,
1208 * + if some message is to us,
1209 * - if mark-for-cc and some message is cc to us, else blank.
1212 to_us_symbol_for_thread(MAILSTREAM
*stream
, PINETHRD_S
*thrd
, int consider_flagged
)
1215 char branch_to_us
= ' ';
1216 PINETHRD_S
*nthrd
, *bthrd
;
1219 if(!thrd
|| !stream
|| thrd
->rawno
< 1L || thrd
->rawno
> stream
->nmsgs
)
1223 nthrd
= fetch_thread(stream
, thrd
->next
);
1225 to_us
= to_us_symbol_for_thread(stream
, nthrd
, consider_flagged
);
1228 if(((consider_flagged
&& to_us
!= '*') || (!consider_flagged
&& to_us
!= '+'))
1230 bthrd
= fetch_thread(stream
, thrd
->branch
);
1232 branch_to_us
= to_us_symbol_for_thread(stream
, bthrd
, consider_flagged
);
1234 /* use branch to_us symbol if it has higher priority than what we have so far */
1236 if(branch_to_us
== '-' || branch_to_us
== '+' || branch_to_us
== '*')
1237 to_us
= branch_to_us
;
1239 else if(to_us
== '-'){
1240 if(branch_to_us
== '+' || branch_to_us
== '*')
1241 to_us
= branch_to_us
;
1243 else if(to_us
== '+'){
1244 if(branch_to_us
== '*')
1245 to_us
= branch_to_us
;
1249 if((consider_flagged
&& to_us
!= '*') || (!consider_flagged
&& to_us
!= '+')){
1250 if(consider_flagged
&& thrd
&& thrd
->rawno
> 0L
1251 && stream
&& thrd
->rawno
<= stream
->nmsgs
1252 && (mc
= mail_elt(stream
, thrd
->rawno
))
1253 && FLAG_MATCH(F_FLAG
, mc
, stream
))
1255 else if(to_us
!= '+' && !IS_NEWS(stream
)){
1260 memset(&idata
, 0, sizeof(INDEXDATA_S
));
1261 idata
.stream
= stream
;
1262 idata
.rawno
= thrd
->rawno
;
1263 idata
.msgno
= mn_raw2m(sp_msgmap(stream
), idata
.rawno
);
1264 if(idata
.rawno
> 0L && stream
&& idata
.rawno
<= stream
->nmsgs
1265 && (mc
= mail_elt(stream
, idata
.rawno
))){
1266 idata
.size
= mc
->rfc822_size
;
1267 index_data_env(&idata
,
1268 pine_mail_fetchenvelope(stream
, idata
.rawno
));
1273 for(addr
= fetch_to(&idata
); addr
; addr
= addr
->next
)
1274 if(address_is_us(addr
, ps_global
)){
1279 if(to_us
!= '+' && resent_to_us(&idata
))
1282 if(to_us
== ' ' && F_ON(F_MARK_FOR_CC
,ps_global
))
1283 for(addr
= fetch_cc(&idata
); addr
; addr
= addr
->next
)
1284 if(address_is_us(addr
, ps_global
)){
1296 * This sets or clears flags for the messages at this node and below in
1297 * a tree. It doesn't just blindly do it, perhaps it should. Instead,
1298 * when un-hiding a subtree it leaves the sub-subtree hidden if a node
1301 * Watch out when calling this. The thrd->branch is not part of thrd.
1302 * Branch is a sibling to thrd, not a child. Zero out branch before calling
1303 * or call on thrd->next and worry about thrd separately.
1304 * Ok to call it on top-level thread which has no branch already.
1307 set_thread_subtree(MAILSTREAM
*stream
, PINETHRD_S
*thrd
, MSGNO_S
*msgmap
, int v
, int flags
)
1312 /* flags to set or clear */
1315 unsigned long msgno
;
1316 PINETHRD_S
*nthrd
, *bthrd
;
1318 hiding
= (flags
== MN_CHID
) && v
;
1320 if(!thrd
|| !stream
|| thrd
->rawno
< 1L || thrd
->rawno
> stream
->nmsgs
)
1323 msgno
= mn_raw2m(msgmap
, thrd
->rawno
);
1325 set_lflag(stream
, msgmap
, msgno
, flags
, v
);
1327 if(thrd
->next
&& (hiding
|| !get_lflag(stream
,NULL
,thrd
->rawno
,MN_COLL
))){
1328 nthrd
= fetch_thread(stream
, thrd
->next
);
1330 set_thread_subtree(stream
, nthrd
, msgmap
, v
, flags
);
1334 bthrd
= fetch_thread(stream
, thrd
->branch
);
1336 set_thread_subtree(stream
, bthrd
, msgmap
, v
, flags
);
1342 * View a thread. Move from the thread index screen to a message index
1343 * screen for the current thread.
1345 * set_lflags - Set the local flags appropriately to start viewing
1346 * the thread. We would not want to set this if we are
1347 * already viewing the thread (and expunge or new mail
1348 * happened) and we want to preserve the collapsed state
1349 * of the subthreads.
1352 view_thread(struct pine
*state
, MAILSTREAM
*stream
, MSGNO_S
*msgmap
, int set_lflags
)
1354 PINETHRD_S
*thrd
= NULL
;
1355 unsigned long rawno
, cur
;
1357 if(!any_messages(msgmap
, NULL
, "to View"))
1360 if(!(stream
&& msgmap
))
1363 rawno
= mn_m2raw(msgmap
, mn_get_cur(msgmap
));
1365 thrd
= fetch_thread(stream
, rawno
);
1367 if(thrd
&& thrd
->top
&& thrd
->top
!= thrd
->rawno
)
1368 thrd
= fetch_thread(stream
, thrd
->top
);
1374 * Clear hidden and collapsed flag for this thread.
1376 * Don't have to worry about there being a branch because
1377 * this is a toplevel thread.
1380 set_thread_lflags(stream
, thrd
, msgmap
, MN_COLL
| MN_CHID
, 0);
1381 set_thread_lflags(stream
, thrd
, msgmap
, MN_CHID2
, 1);
1385 * If this is one of those wacky users who like to sort backwards
1386 * they would probably prefer that the current message be the last
1387 * one in the thread (the one highest up the screen).
1389 if(mn_get_revsort(msgmap
)){
1390 cur
= mn_get_cur(msgmap
);
1391 while(cur
> 1L && get_lflag(stream
, msgmap
, cur
-1L, MN_CHID2
))
1394 if(cur
!= mn_get_cur(msgmap
))
1395 mn_set_cur(msgmap
, cur
);
1398 /* first message in thread might be hidden if zoomed */
1399 if(any_lflagged(msgmap
, MN_HIDE
)){
1400 cur
= mn_get_cur(msgmap
);
1401 while(get_lflag(stream
, msgmap
, cur
, MN_HIDE
))
1404 if(cur
!= mn_get_cur(msgmap
))
1405 mn_set_cur(msgmap
, cur
);
1408 msgmap
->top
= mn_get_cur(msgmap
);
1409 sp_set_viewing_a_thread(stream
, 1);
1411 state
->mangled_screen
= 1;
1412 setup_for_index_index_screen();
1419 unview_thread(struct pine
*state
, MAILSTREAM
*stream
, MSGNO_S
*msgmap
)
1421 PINETHRD_S
*thrd
= NULL
, *topthrd
= NULL
;
1422 unsigned long rawno
;
1424 if(!(stream
&& msgmap
))
1427 rawno
= mn_m2raw(msgmap
, mn_get_cur(msgmap
));
1429 thrd
= fetch_thread(stream
, rawno
);
1431 if(thrd
&& thrd
->top
)
1432 topthrd
= fetch_thread(stream
, thrd
->top
);
1437 /* hide this thread */
1438 set_thread_lflags(stream
, topthrd
, msgmap
, MN_CHID
, 1);
1440 /* clear special CHID2 flags for this thread */
1441 set_thread_lflags(stream
, topthrd
, msgmap
, MN_CHID2
, 0);
1443 /* clear CHID for top-level message and set COLL */
1444 set_lflag(stream
, msgmap
, mn_raw2m(msgmap
, topthrd
->rawno
), MN_CHID
, 0);
1445 set_lflag(stream
, msgmap
, mn_raw2m(msgmap
, topthrd
->rawno
), MN_COLL
, 1);
1447 mn_set_cur(msgmap
, mn_raw2m(msgmap
, topthrd
->rawno
));
1448 sp_set_viewing_a_thread(stream
, 0);
1449 setup_for_thread_index_screen();
1456 find_thread_by_number(MAILSTREAM
*stream
, MSGNO_S
*msgmap
, long int target
, PINETHRD_S
*startthrd
)
1458 PINETHRD_S
*thrd
= NULL
;
1460 if(!(stream
&& msgmap
))
1465 if(!thrd
|| !(thrd
->prevthd
|| thrd
->nextthd
))
1466 thrd
= fetch_thread(stream
, mn_m2raw(msgmap
, mn_get_cur(msgmap
)));
1468 if(thrd
&& !(thrd
->prevthd
|| thrd
->nextthd
) && thrd
->head
)
1469 thrd
= fetch_thread(stream
, thrd
->head
);
1472 /* go forward from here */
1473 if(thrd
->thrdno
< target
){
1475 if(thrd
->thrdno
== target
)
1478 if(mn_get_revsort(msgmap
) && thrd
->prevthd
)
1479 thrd
= fetch_thread(stream
, thrd
->prevthd
);
1480 else if(!mn_get_revsort(msgmap
) && thrd
->nextthd
)
1481 thrd
= fetch_thread(stream
, thrd
->nextthd
);
1486 /* back up from here */
1487 else if(thrd
->thrdno
> target
1488 && (mn_get_revsort(msgmap
)
1489 || (thrd
->thrdno
- target
) < (target
- 1L))){
1491 if(thrd
->thrdno
== target
)
1494 if(mn_get_revsort(msgmap
) && thrd
->nextthd
)
1495 thrd
= fetch_thread(stream
, thrd
->nextthd
);
1496 else if(!mn_get_revsort(msgmap
) && thrd
->prevthd
)
1497 thrd
= fetch_thread(stream
, thrd
->prevthd
);
1502 /* go forward from head */
1503 else if(thrd
->thrdno
> target
){
1505 thrd
= fetch_thread(stream
, thrd
->head
);
1507 if(thrd
->thrdno
== target
)
1511 thrd
= fetch_thread(stream
, thrd
->nextthd
);
1524 * Set search bit for every message in a thread.
1526 * Watch out when calling this. The thrd->branch is not part of thrd.
1527 * Branch is a sibling to thrd, not a child. Zero out branch before calling
1528 * or call on thrd->next and worry about thrd separately. Top-level threads
1529 * already have a branch equal to zero.
1531 * If msgset is non-NULL, then only set the search bit for a message if that
1532 * message is included in the msgset.
1535 set_search_bit_for_thread(MAILSTREAM
*stream
, PINETHRD_S
*thrd
, SEARCHSET
**msgset
)
1537 PINETHRD_S
*nthrd
, *bthrd
;
1539 if(!(stream
&& thrd
))
1542 if(thrd
->rawno
> 0L && thrd
->rawno
<= stream
->nmsgs
1543 && (!(msgset
&& *msgset
) || in_searchset(*msgset
, thrd
->rawno
)))
1544 mm_searched(stream
, thrd
->rawno
);
1547 nthrd
= fetch_thread(stream
, thrd
->next
);
1549 set_search_bit_for_thread(stream
, nthrd
, msgset
);
1553 bthrd
= fetch_thread(stream
, thrd
->branch
);
1555 set_search_bit_for_thread(stream
, bthrd
, msgset
);