home *** CD-ROM | disk | FTP | other *** search
/ Usenet 1994 January / usenetsourcesnewsgroupsinfomagicjanuary1994.iso / sources / unix / volume23 / trn / part08 / rt-rn.c < prev    next >
Encoding:
C/C++ Source or Header  |  1991-08-22  |  21.0 KB  |  967 lines

  1. /* $Header: rt-rn.c,v 4.3.3.1 90/07/28 18:07:55 davison Trn $
  2. **
  3. ** $Log:    rt-rn.c,v $
  4. ** Revision 4.3.3.1  90/07/28  18:07:55  davison
  5. ** Initial Trn Release
  6. ** 
  7. */
  8.  
  9. #include "EXTERN.h"
  10. #include "common.h"
  11. #include "term.h"
  12. #include "final.h"
  13. #include "util.h"
  14. #include "bits.h"
  15. #include "artio.h"
  16. #include "ng.h"
  17. #include "ngdata.h"
  18. #include "search.h"
  19. #include "artstate.h"
  20. #include "backpage.h"
  21. #include "rthreads.h"
  22.  
  23. #ifdef USETHREADS
  24.  
  25. static void find_depth(), cache_tree(), display_tree();
  26. static char letter();
  27.  
  28. /* Find the article structure information based on article number.
  29. */
  30. void
  31. find_article( artnum )
  32. ART_NUM artnum;
  33. {
  34.     register PACKED_ARTICLE *article;
  35.     register int i;
  36.  
  37.     if( !p_articles ) {
  38.     p_art = Nullart;
  39.     return;
  40.     }
  41.  
  42.     if( !p_art ) {
  43.     p_art = p_articles;
  44.     }
  45.     /* Start looking for the article num from our last known spot in the array.
  46.     ** That way, if we already know where we are, we run into ourselves right
  47.     ** away.
  48.     */
  49.     for( article=p_art, i=p_art-p_articles; i < total.article; article++,i++ ) {
  50.     if( article->num == artnum ) {
  51.         p_art = article;
  52.         return;
  53.     }
  54.     }
  55.     /* Didn't find it, so search the ones before our current position.
  56.     */
  57.     for( article = p_articles; article != p_art; article++ ) {
  58.     if( article->num == artnum ) {
  59.         p_art = article;
  60.         return;
  61.     }
  62.     }
  63.     p_art = Nullart;
  64. }
  65.  
  66. static char tree_indent[] = {
  67.     ' ', 0,
  68.     ' ', ' ', ' ', ' ', 0,   ' ', ' ', ' ', ' ', 0,
  69.     ' ', ' ', ' ', ' ', 0,   ' ', ' ', ' ', ' ', 0,
  70.     ' ', ' ', ' ', ' ', 0,   ' ', ' ', ' ', ' ', 0,
  71.     ' ', ' ', ' ', ' ', 0,   ' ', ' ', ' ', ' ', 0,
  72.     ' ', ' ', ' ', ' ', 0,   ' ', ' ', ' ', ' ', 0,
  73.     ' ', ' ', ' ', ' ', 0,   ' ', ' ', ' ', ' ', 0,
  74.     ' ', ' ', ' ', ' ', 0,   ' ', ' ', ' ', ' ', 0,
  75.     ' ', ' ', ' ', ' ', 0,   ' ', ' ', ' ', ' ', 0,
  76.     ' ', ' ', ' ', ' ', 0,   ' ', ' ', ' ', ' ', 0,
  77.     ' ', ' ', ' ', ' ', 0,   ' ', ' ', ' ', ' ', 0,
  78.     ' ', ' ', ' ', ' ', 0,   ' ', ' ', ' ', ' ', 0,
  79.     ' ', ' ', ' ', ' ', 0,   ' ', ' ', ' ', ' ', 0,
  80.     ' ', ' ', ' ', ' ', 0,   ' ', ' ', ' ', ' ', 0,
  81.     ' ', ' ', ' ', ' ', 0,   ' ', ' ', ' ', ' ', 0
  82. };
  83.  
  84. char letters[] = "123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz?";
  85.  
  86. static PACKED_ARTICLE *tree_article;
  87.  
  88. static int max_depth, max_line = -1;
  89. static int first_depth, first_line;
  90. static int my_depth, my_line;
  91. static bool node_on_line;
  92. static int node_line_cnt;
  93.  
  94. static int line_num;
  95. static int header_indent;
  96.  
  97. static char *tree_lines[11];
  98. static char tree_buff[128], *str;
  99.  
  100. /* Prepare tree display for inclusion in the article header.
  101. */
  102. void
  103. init_tree()
  104. {
  105.     register PACKED_ARTICLE *article;
  106.  
  107. #if 000            /* don't do this, since read-status may change */
  108.     if( curr_p_art == tree_article ) {
  109.     return;
  110.     }
  111. #endif
  112.     while( max_line >= 0 ) {        /* free any previous tree data */
  113.     free( tree_lines[max_line--] );
  114.     }
  115.     tree_article = curr_p_art;
  116.  
  117.     if( !curr_p_art ) {
  118.     return;
  119.     }
  120.     article = p_articles + p_roots[curr_p_art->root].articles;
  121.  
  122.     max_depth = max_line = my_depth = my_line = node_line_cnt = 0;
  123.     find_depth( article, 0 );
  124.  
  125.     if( max_depth <= 5 ) {
  126.     first_depth = 0;
  127.     } else {
  128.     if( my_depth+2 > max_depth ) {
  129.         first_depth = max_depth - 5;
  130.     } else if( (first_depth = my_depth - 3) < 0 ) {
  131.         first_depth = 0;
  132.     }
  133.     max_depth = first_depth + 5;
  134.     }
  135.     if( --max_line < max_tree_lines ) {
  136.     first_line = 0;
  137.     } else {
  138.     if( my_line + max_tree_lines/2 > max_line ) {
  139.         first_line = max_line - (max_tree_lines-1);
  140.     } else if( (first_line = my_line - (max_tree_lines-1)/2) < 0 ) {
  141.         first_line = 0;
  142.     }
  143.     max_line = first_line + max_tree_lines-1;
  144.     }
  145.  
  146.     str = tree_buff;        /* initialize first line's data */
  147.     *str++ = ' ';
  148.     node_on_line = FALSE;
  149.     line_num = 0;
  150.     /* cache our portion of the tree */
  151.     cache_tree( article, 0, tree_indent );
  152.  
  153.     max_depth = (max_depth-first_depth) * 5;    /* turn depth into char width */
  154.     max_line -= first_line;            /* turn max_line into count */
  155.     /* shorten tree if lower lines aren't visible */
  156.     if( node_line_cnt < max_line ) {
  157.     max_line = node_line_cnt + 1;
  158.     }
  159. }
  160.  
  161. /* A recursive routine to find the maximum tree extents and where we are.
  162. */
  163. static void
  164. find_depth( article, depth )
  165. PACKED_ARTICLE *article;
  166. {
  167.     if( depth > max_depth ) {
  168.     max_depth = depth;
  169.     }
  170.     for( ;; ) {
  171.     if( article == tree_article ) {
  172.         my_depth = depth;
  173.         my_line = max_line;
  174.     }
  175.     if( article->child_cnt ) {
  176.         find_depth( article+1, depth+1 );
  177.     } else {
  178.         max_line++;
  179.     }
  180.     if( !article->siblings ) {
  181.         break;
  182.     }
  183.     article += article->siblings;
  184.     }
  185. }
  186.  
  187. /* Place the tree display in a maximum of 11 lines x 6 nodes.
  188. */
  189. static void
  190. cache_tree( article, depth, cp )
  191. PACKED_ARTICLE *article;
  192. int depth;
  193. char *cp;
  194. {
  195.     int depth_mode;
  196.  
  197.     cp[1] = ' ';
  198.     if( depth >= first_depth && depth <= max_depth ) {
  199.     cp += 5;
  200.     depth_mode = 1;
  201.     } else if( depth+1 == first_depth ) {
  202.     depth_mode = 2;
  203.     } else {
  204.     cp = tree_indent;
  205.     depth_mode = 0;
  206.     }
  207.     for( ;; ) {
  208.     switch( depth_mode ) {
  209.     case 1: {
  210.         char ch;
  211.  
  212.         *str++ = (article->flags & ROOT_ARTICLE)? ' ' : '-';
  213.         if( article == tree_article ) {
  214.         *str++ = '*';
  215.         }
  216.         if( was_read( article->num ) ) {
  217.         *str++ = '(';
  218.         ch = ')';
  219.         } else {
  220.         *str++ = '[';
  221.         ch = ']';
  222.         }
  223.         if( article == recent_p_art && article != tree_article ) {
  224.         *str++ = '@';
  225.         }
  226.         *str++ = letter( article );
  227.         *str++ = ch;
  228.         if( article->child_cnt ) {
  229.         *str++ = (article->child_cnt == 1)? '-' : '+';
  230.         }
  231.         if( article->siblings ) {
  232.         *cp = '|';
  233.         } else {
  234.         *cp = ' ';
  235.         }
  236.         node_on_line = TRUE;
  237.         break;
  238.     }
  239.     case 2:
  240.         *tree_buff = (!article->child_cnt)? ' ' :
  241.         (article->child_cnt == 1)? '-' : '+';
  242.         break;
  243.     default:
  244.         break;
  245.     }
  246.     if( article->child_cnt ) {
  247.         cache_tree( article+1, depth+1, cp );
  248.         cp[1] = '\0';
  249.     } else {
  250.         if( !node_on_line && first_line == line_num ) {
  251.         first_line++;
  252.         }
  253.         if( line_num >= first_line ) {
  254.         if( str[-1] == ' ' ) {
  255.             str--;
  256.         }
  257.         *str = '\0';
  258.         tree_lines[line_num-first_line]
  259.             = safemalloc( str-tree_buff + 1 );
  260.         strcpy( tree_lines[line_num - first_line], tree_buff );
  261.         if( node_on_line ) {
  262.             node_line_cnt = line_num - first_line;
  263.         }
  264.         }
  265.         line_num++;
  266.         node_on_line = FALSE;
  267.     }
  268.     if( !article->siblings || line_num > max_line ) {
  269.         break;
  270.     }
  271.     article += article->siblings;
  272.     if( !article->siblings ) {
  273.         *cp = '\\';
  274.     }
  275.     if( !first_depth ) {
  276.         tree_indent[5] = ' ';
  277.     }
  278.     strcpy( tree_buff, tree_indent+5 );
  279.     str = tree_buff + strlen( tree_buff );
  280.     }
  281. }
  282.  
  283. /* Output a header line with possible tree display on the right hand side.
  284. ** Does automatic wrapping of lines that are too long.
  285. */
  286. int
  287. tree_puts( orig_line, header_line, use_underline )
  288. char *orig_line;
  289. ART_LINE header_line;
  290. int use_underline;
  291. {
  292.     char *buf;
  293.     register char *line, *cp, *end;
  294.     int pad_cnt, wrap_at;
  295.     ART_LINE start_line = header_line;
  296.     int i;
  297.     char ch;
  298.  
  299.     /* Make a modifiable copy of the line */
  300.     buf = safemalloc( strlen( orig_line ) + 2 );  /* yes, I mean "2" */
  301.     strcpy( buf, orig_line );
  302.     line = buf;
  303.  
  304.     /* Change any embedded control characters to spaces */
  305.     for( end = line; *end && *end != '\n'; end++ ) {
  306.     if( (unsigned char)*end < ' ' ) {
  307.         *end = ' ';
  308.     }
  309.     }
  310.     *end = '\0';
  311.  
  312.     if( !*line ) {
  313.     strcpy( line, " " );
  314.     end = line+1;
  315.     }
  316.  
  317.     /* If this is the first subject line, output it with a preceeding [1] */
  318.     if( use_underline && curr_p_art && (unsigned char)*line > ' ' ) {
  319. #ifdef NOFIREWORKS
  320.     no_sofire();
  321. #endif
  322.     standout();
  323.     putchar( '[' );
  324.     putchar( letter( curr_p_art ) );
  325.     putchar( ']' );
  326.     un_standout();
  327.     putchar( ' ' );
  328.     header_indent = 4;
  329.     line += 9;
  330.     i = 0;
  331.     } else {
  332.     if( *line != ' ' ) {
  333.         /* A "normal" header line -- output keyword and set header_indent
  334.         ** _except_ for the first line, which is a non-standard header.
  335.         */
  336.         if( !header_line || !(cp = index( line, ':' )) || *++cp != ' ' ) {
  337.         header_indent = 0;
  338.         } else {
  339.         *cp = '\0';
  340.         fputs( line, stdout );
  341.         putchar( ' ' );
  342.         header_indent = ++cp - line;
  343.         line = cp;
  344.         }
  345.         i = 0;
  346.     } else {
  347.         /* Skip whitespace of continuation lines and prepare to indent */
  348.         while( *++line == ' ' ) {
  349.         ;
  350.         }
  351.         i = header_indent;
  352.     }
  353.     }
  354.     for( ; *line; i = header_indent ) {
  355. #ifdef CLEAREOL
  356.     maybe_eol();
  357. #endif
  358.     if( i ) {
  359.         putchar( '+' );
  360.         while( --i ) {
  361.         putchar( ' ' );
  362.         }
  363.     }
  364.     /* If no (more) tree lines, wrap at COLS-1 */
  365.     if( max_line < 0 || header_line > max_line+1 ) {
  366.         wrap_at = COLS-1;
  367.     } else {
  368.         wrap_at = COLS - max_depth - 5 - 3;
  369.     }
  370.     /* Figure padding between header and tree output, wrapping long lines */
  371.     pad_cnt = wrap_at - (end - line + header_indent);
  372.     if( pad_cnt <= 0 ) {
  373.         cp = line + wrap_at - header_indent - 1;
  374.         pad_cnt = 1;
  375.         while( cp > line && *cp != ' ' ) {
  376.         if( *--cp == ',' || *cp == '.' || *cp == '-' || *cp == '!' ) {
  377.             cp++;
  378.             break;
  379.         }
  380.         pad_cnt++;
  381.         }
  382.         if( cp == line ) {
  383.         cp += wrap_at - header_indent;
  384.         pad_cnt = 0;
  385.         }
  386.         ch = *cp;
  387.         *cp = '\0';
  388.         /* keep rn's backpager happy */
  389.         vwtary( artline, vrdary( artline - 1 ) );
  390.         artline++;
  391.     } else {
  392.         cp = end;
  393.         ch = '\0';
  394.     }
  395.     if( use_underline ) {
  396.         underprint( line );
  397.     } else {
  398.         fputs( line, stdout );
  399.     }
  400.     *cp = ch;
  401.     /* Skip whitespace in wrapped line */
  402.     while( *cp == ' ' ) {
  403.         cp++;
  404.     }
  405.     line = cp;
  406.     /* Check if we've got any tree lines to output */
  407.     if( wrap_at != COLS-1 && header_line <= max_line ) {
  408.         char *cp1, *cp2;
  409.  
  410.         do {
  411.         putchar( ' ' );
  412.         } while( pad_cnt-- );
  413.         /* Check string for the '*' flagging our current node
  414.         ** and the '@' flagging our prior node.
  415.         */
  416.         cp = tree_lines[header_line];
  417.         cp1 = index( cp, '*' );
  418.         cp2 = index( cp, '@' );
  419.         if( cp1 != Nullch ) {
  420.         *cp1 = '\0';
  421.         }
  422.         if( cp2 != Nullch ) {
  423.         *cp2 = '\0';
  424.         }
  425.         fputs( cp, stdout );
  426.         /* Handle standout output for '*' and '@' marked nodes, then
  427.         ** continue with the rest of the line.
  428.         */
  429.         while( cp1 || cp2 ) {
  430.         standout();
  431.         if( cp1 && (!cp2 || cp1 < cp2) ) {
  432.             cp = cp1;
  433.             cp1 = Nullch;
  434.             *cp++ = '*';
  435.             putchar( *cp++ );
  436.             putchar( *cp++ );
  437.         } else {
  438.             cp = cp2;
  439.             cp2 = Nullch;
  440.             *cp++ = '@';
  441.         }
  442.         putchar( *cp++ );
  443.         un_standout();
  444.         if( *cp ) {
  445.             fputs( cp, stdout );
  446.         }
  447.         }/* while */
  448.     }/* if */
  449.     putchar( '\n' ) FLUSH;
  450.     header_line++;
  451.     }/* for remainder of line */
  452.  
  453.     /* free allocated copy of line */
  454.     free( buf );
  455.  
  456.     /* return number of lines displayed */
  457.     return header_line - start_line;
  458. }
  459.  
  460. /* Output any parts of the tree that are left to display.  Called at the
  461. ** end of each header.
  462. */
  463. int
  464. finish_tree( last_line )
  465. ART_LINE last_line;
  466. {
  467.     ART_LINE start_line = last_line;
  468.  
  469.     while( last_line <= max_line ) {
  470.     artline++;
  471.     last_line += tree_puts( "+", last_line, 0 );
  472.     vwtary( artline, artpos );    /* keep rn's backpager happy */
  473.     }
  474.     return last_line - start_line;
  475. }
  476.  
  477. /* Output the entire article tree for the user.
  478. */
  479. void
  480. entire_tree()
  481. {
  482.     int j, root;
  483.  
  484.     if( check_page_line() ) {
  485.     return;
  486.     }
  487.     if( !p_art ) {
  488. #ifdef VERBOSE
  489.     IF( verbose )
  490.         fputs( "\nNo article tree to display.\n", stdout );
  491.     ELSE
  492. #endif
  493. #ifdef TERSE
  494.         fputs( "\nNo tree.\n", stdout );
  495. #endif
  496.     } else {
  497.     root = p_art->root;
  498. #ifdef NOFIREWORKS
  499.     no_sofire();
  500. #endif
  501.     standout();
  502.     printf( "T%ld:\n", (long)p_roots[root].root_num );
  503.     un_standout();
  504.     if( check_page_line() ) {
  505.         return;
  506.     }
  507.     putchar( '\n' );
  508.     for( j = 0; j < p_roots[root].subject_cnt; j++ ) {
  509.         sprintf( buf, "[%c] %s\n", letters[j > 9+26+26 ? 9+26+26 : j],
  510.         subject_ptrs[root_subjects[root]+j] );
  511.         if( check_page_line() ) {
  512.         return;
  513.         }
  514.         fputs( buf, stdout );
  515.     }
  516.     if( check_page_line() ) {
  517.         return;
  518.     }
  519.     putchar( '\n' );
  520.     if( check_page_line() ) {
  521.         return;
  522.     }
  523.     putchar( ' ' );
  524.     buf[3] = '\0';
  525.     display_tree( p_articles+p_roots[p_art->root].articles, tree_indent );
  526.  
  527.     if( check_page_line() ) {
  528.         return;
  529.     }
  530.     putchar( '\n' );
  531.     }
  532. }
  533.  
  534. /* A recursive routine to output the entire article tree.
  535. */
  536. static void
  537. display_tree( article, cp )
  538. PACKED_ARTICLE *article;
  539. char *cp;
  540. {
  541.     if( cp - tree_indent > COLS || page_line < 0 ) {
  542.     return;
  543.     }
  544.     cp[1] = ' ';
  545.     cp += 5;
  546.     for( ;; ) {
  547.     putchar( (article->flags & ROOT_ARTICLE)? ' ' : '-' );
  548.     if( was_read( article->num ) ) {
  549.         buf[0] = '(';
  550.         buf[2] = ')';
  551.     } else {
  552.         buf[0] = '[';
  553.         buf[2] = ']';
  554.     }
  555.     buf[1] = letter( article );
  556.     if( article == curr_p_art ) {
  557.         standout();
  558.         fputs( buf, stdout );
  559.         un_standout();
  560.     } else if( article == recent_p_art ) {
  561.         putchar( buf[0] );
  562.         standout();
  563.         putchar( buf[1] );
  564.         un_standout();
  565.         putchar( buf[2] );
  566.     } else {
  567.         fputs( buf, stdout );
  568.     }
  569.  
  570.     if( article->siblings ) {
  571.         *cp = '|';
  572.     } else {
  573.         *cp = ' ';
  574.     }
  575.     if( article->child_cnt ) {
  576.         putchar( (article->child_cnt == 1)? '-' : '+' );
  577.         display_tree( article+1, cp );
  578.         cp[1] = '\0';
  579.     } else {
  580.         putchar( '\n' ) FLUSH;
  581.     }
  582.     if( !article->siblings ) {
  583.         break;
  584.     }
  585.     article += article->siblings;
  586.     if( !article->siblings ) {
  587.         *cp = '\\';
  588.     }
  589.     tree_indent[5] = ' ';
  590.     if( check_page_line() ) {
  591.         return;
  592.     }
  593.     fputs( tree_indent+5, stdout );
  594.     }
  595. }
  596.  
  597. int
  598. check_page_line()
  599. {
  600.     if( page_line < 0 ) {
  601.     return -1;
  602.     }
  603.     if( page_line >= LINES || int_count ) {
  604.       register int cmd = -1;
  605.     if( int_count || (cmd = get_anything()) ) {
  606.         page_line = -1;        /* disable further printing */
  607.         if( cmd > 0 ) {
  608.         pushchar( cmd );
  609.         }
  610.         return cmd;
  611.     }
  612.     }
  613.     page_line++;
  614.     return 0;
  615. }
  616.  
  617. /* Calculate the subject letter representation.  "Place-holder" nodes
  618. ** are marked with a ' ', others get a letter in the sequence:
  619. **    ' ', '1'-'9', 'A'-'Z', 'a'-'z', '?'
  620. */
  621. static char
  622. letter( article )
  623. PACKED_ARTICLE *article;
  624. {
  625.     register int subj = article->subject;
  626.  
  627.     if( subj < 0 ) {
  628.     return ' ';
  629.     }
  630.     subj -= root_subjects[article->root];
  631.     if( subj < 9+26+26 ) {
  632.     return letters[subj];
  633.     }
  634.     return '?';
  635. }
  636.  
  637. /* Find the first unread article in the (possibly selected) root order.
  638. */
  639. void
  640. first_art()
  641. {
  642.     register int r;
  643.  
  644.     if( !ThreadedGroup ) {
  645.     art = firstart;
  646.     return;
  647.     }
  648.     p_art = Nullart;
  649.     art = lastart+1;
  650.     follow_thread( 'n' );
  651. }
  652.  
  653. /* Perform a command over all or a section of the article tree.  Most of
  654. ** the option letters match commands entered from article mode:
  655. **   n - find the next unread article after current article.
  656. **  ^N - find the next unread article with the same subject.
  657. **   N - goto the next article in the thread.
  658. **   J - junk the entire thread.
  659. **   k - junk all articles with this same subject.
  660. **   K - kill all this article's descendants (we know that the caller
  661. **     killed the current article on the way here).
  662. **   u - mark entire thread as "unread".
  663. **   U - mark this article and its descendants as "unread".
  664. **   f - follow the thread (like 'n'), but don't attempt to find a new thread
  665. **     if we run off the end.
  666. */
  667. void
  668. follow_thread( cmd )
  669. char cmd;
  670. {
  671.     int curr_subj = -1, sel;
  672.     PACKED_ARTICLE *root_limit, *p_art_old = Nullart;
  673.     bool subthread_flag;
  674.  
  675.     reread = FALSE;
  676.  
  677.     if( !p_art ) {
  678.     if( ThreadedGroup && art > lastart ) {
  679.         p_art = root_limit = p_articles;
  680.         goto follow_root;
  681.     }
  682.     art++;
  683.     return;
  684.     }
  685.     if( cmd == 'k' || cmd == Ctl('n') ) {
  686.     if( (curr_subj = p_art->subject) == -1) {
  687.         return;
  688.     }
  689.     p_art_old = p_art;
  690.     }
  691.     sel = (selected_roots[p_art->root] & 1);
  692.     if( cmd == 'U' || cmd == 'K' ) {
  693.     subthread_flag = TRUE;
  694.     p_art_old = p_art;
  695.     } else {
  696.     subthread_flag = FALSE;
  697.     }
  698.     /* The current article is already marked as read for 'K' */
  699.     if( cmd == 'k' || cmd == 'J' || cmd == 'u' ) {
  700.     p_art = p_articles + p_roots[p_art->root].articles;
  701.     art = p_art->num;
  702.     if( cmd == 'u' ) {
  703.         p_art_old = p_art;
  704.         cmd = 'U';
  705.     } else {
  706.         if( !was_read( art )
  707.          && (curr_subj < 0 || curr_subj == p_art->subject) ) {
  708.         set_read( art, sel );
  709.         }
  710.         cmd = 'K';
  711.     }
  712.     }
  713.     if( cmd == 'U' ) {
  714.     if( p_art->subject != -1 ) {
  715.         set_unread( art, sel );
  716.     }
  717.     root_article_cnts[p_art->root] = 1;
  718.     scan_all_roots = FALSE;
  719.     }
  720.   follow_again:
  721.     sel = (selected_roots[p_art->root] & 1);
  722.     root_limit = upper_limit( p_art, subthread_flag );
  723.     for( ;; ) {
  724.     if( ++p_art == root_limit ) {
  725.         break;
  726.     }
  727.     if( !(art = p_art->num) ) {
  728.         continue;
  729.     }
  730.     if( cmd == 'K' || p_art->subject == -1 ) {
  731.         if( !was_read( art )
  732.          && (curr_subj < 0 || curr_subj == p_art->subject) ) {
  733.         set_read( art, sel );
  734.         }
  735.     } else if( cmd == 'U' ) {
  736.         set_unread( art, sel );
  737.     } else if( !was_read( art )
  738.         && (curr_subj < 0 || curr_subj == p_art->subject) ) {
  739.         return;
  740.     } else if( cmd == 'N' ) {
  741.         reread = TRUE;
  742.         return;
  743.     }
  744.     }/* for */
  745.     if( p_art_old ) {
  746.     p_art = p_art_old;
  747.     if( cmd == 'U' && p_art->subject != -1 ) {
  748.         art = p_art->num;
  749.         return;
  750.     }
  751.     p_art_old = Nullart;
  752.     cmd = 'n';
  753.     curr_subj = -1;
  754.     subthread_flag = FALSE;
  755.     goto follow_again;
  756.     }
  757.     if( cmd == 'f' ) {
  758.     p_art = Nullart;
  759.     art = lastart+1;
  760.     return;
  761.     }
  762.   follow_root:
  763.     if( root_limit != p_articles + total.article ) {
  764.     register int r;
  765.  
  766.     for( r = p_art->root; r < total.root; r++ ) {
  767.         if( !selected_root_cnt || selected_roots[r] ) {
  768.         p_art = p_articles + p_roots[r].articles;
  769.         art = p_art->num;
  770.         if( p_art->subject == -1 || (cmd != 'N' && was_read( art )) ) {
  771.             if( cmd != 'N' ) {
  772.             cmd = 'n';
  773.             }
  774.             curr_subj = -1;
  775.             subthread_flag = FALSE;
  776.             goto follow_again;
  777.         }
  778.         return;
  779.         }
  780.     }
  781.     }
  782.     if( !count_roots( FALSE ) && unthreaded ) {
  783.     /* No threaded articles left -- blow everything else away */
  784.     for( art = firstart; art <= lastart; art++ ) {
  785.         oneless( art );
  786.     }
  787.     unthreaded = 0;
  788.     }
  789.     p_art = Nullart;
  790.     art = lastart+1;
  791. }
  792.  
  793. /* Go backward in the article tree.  Options match commands in article mode:
  794. **    p - previous unread article.
  795. **   ^P - previous unread article with same subject.
  796. **    P - previous article.
  797. */
  798. void
  799. backtrack_thread( cmd )
  800. char cmd;
  801. {
  802.     int curr_subj = -1, sel;
  803.     PACKED_ARTICLE *root_limit, *p_art_old = Nullart;
  804.  
  805.     if( art > lastart ) {
  806.     p_art = p_articles + total.article - 1;
  807.     root_limit = Nullart;
  808.     goto backtrack_root;
  809.     }
  810.     if( !p_art ) {
  811.     art--;
  812.     return;
  813.     }
  814.     if( cmd == Ctl('p') ) {
  815.     if( (curr_subj = p_art->subject) == -1) {
  816.         return;
  817.     }
  818.     p_art_old = p_art;
  819.     }
  820.   backtrack_again:
  821.     sel = (selected_roots[p_art->root] & 1);
  822.     root_limit = p_articles + p_roots[p_art->root].articles;
  823.     for( ;; ) {
  824.     if( p_art-- == root_limit ) {
  825.         break;
  826.     }
  827.     if( !(art = p_art->num) ) {
  828.         continue;
  829.     }
  830.     if( p_art->subject == -1 ) {
  831.         set_read( art, sel );
  832.     } else if( !was_read( art )
  833.         && (curr_subj < 0 || curr_subj == p_art->subject) ) {
  834.         return;
  835.     } else if( cmd == 'P' ) {
  836.         reread = TRUE;
  837.         return;
  838.     }
  839.     }/* for */
  840.     if( p_art_old ) {
  841.     p_art = p_art_old;
  842.     p_art_old = Nullart;
  843.     curr_subj = -1;
  844.     goto backtrack_again;
  845.     }
  846.   backtrack_root:
  847.     if( root_limit != p_articles ) {
  848.     register int r;
  849.  
  850.     for( r = p_art->root; r >= 0; r-- ) {
  851.         if( !selected_root_cnt || selected_roots[r] ) {
  852.         art = p_art->num;
  853.         if( cmd != 'P' && was_read( art ) ) {
  854.             goto backtrack_again;
  855.         }
  856.         return;
  857.         }
  858.         p_art = p_articles + p_roots[r].articles - 1;
  859.     }
  860.     }
  861.     p_art = Nullart;
  862.     art = absfirst-1;
  863. }
  864.  
  865. /* Find the next root (first if p_art == NULL).  If roots are selected,
  866. ** only choose from selected roots.
  867. */
  868. void
  869. next_root()
  870. {
  871.     register int r;
  872.  
  873.     reread = FALSE;
  874.  
  875.     if( p_art ) {
  876.     r = p_art->root+1;
  877.     } else {
  878.     r = 0;
  879.     }
  880.     for( ; r < total.root; r++ ) {
  881.     if( !selected_root_cnt || selected_roots[r] ) {
  882.       try_again:
  883.         p_art = p_articles + p_roots[r].articles;
  884.         art = p_art->num;
  885.         if( p_art->subject == -1 || (!reread && was_read( art )) ) {
  886.         follow_thread( reread ? 'N' : 'f' );
  887.         if( art == lastart+1 ) {
  888.             if( scan_all_roots || selected_root_cnt
  889.              || root_article_cnts[r] ) {
  890.             reread = TRUE;
  891.             goto try_again;
  892.             }
  893.             continue;
  894.         }
  895.         }
  896.         return;
  897.     }
  898.     }
  899.     p_art = Nullart;
  900.     art = lastart+1;
  901.     forcelast = TRUE;
  902. }
  903.  
  904. /* Find previous root (or last if p_art == NULL).  If roots are selected,
  905. ** only choose from selected roots.
  906. */
  907. void
  908. prev_root()
  909. {
  910.     register int r;
  911.  
  912.     reread = FALSE;
  913.  
  914.     if( p_art ) {
  915.     r = p_art->root - 1;
  916.     } else {
  917.     r = total.root - 1;
  918.     }
  919.     for( ; r >= 0; r-- ) {
  920.     if( !selected_root_cnt || selected_roots[r] ) {
  921.       try_again:
  922.         p_art = p_articles + p_roots[r].articles;
  923.         art = p_art->num;
  924.         if( p_art->subject == -1 || (!reread && was_read( art )) ) {
  925.         follow_thread( reread ? 'N' : 'f' );
  926.         if( art == lastart+1 ) {
  927.             if( scan_all_roots || selected_root_cnt
  928.              || root_article_cnts[r] ) {
  929.             reread = TRUE;
  930.             goto try_again;
  931.             }
  932.             continue;
  933.         }
  934.         }
  935.         return;
  936.     }
  937.     }
  938.     p_art = Nullart;
  939.     art = lastart+1;
  940.     forcelast = TRUE;
  941. }
  942.  
  943. /* Return a pointer value that we will equal when we've reached the end of
  944. ** the current (sub-)thread.
  945. */
  946. PACKED_ARTICLE *
  947. upper_limit( artp, subthread_flag )
  948. PACKED_ARTICLE *artp;
  949. bool subthread_flag;
  950. {
  951.     if( subthread_flag ) {
  952.     for( ;; ) {
  953.         if( artp->siblings ) {
  954.         return artp + artp->siblings;
  955.         }
  956.         if( !artp->parent ) {
  957.         break;
  958.         }
  959.         artp += artp->parent;
  960.     }
  961.     }
  962.     return p_articles + (artp->root == total.root-1 ?
  963.     total.article : p_roots[artp->root+1].articles);
  964. }
  965.  
  966. #endif /* USETHREADS */
  967.