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