/*
 *	(c) Copyright 1990, Kim Fabricius Storm.  All rights reserved.
 *
 * 	selection mode menu
 */

#include "config.h"
#include "articles.h"
#include "keymap.h"
#include "nn_term.h"
#include "menu.h"
#include "regexp.h"

/* menu.c */

#ifdef __STDC__
struct menu_info;
#endif

extern void xterm_mouse_on();
extern void xterm_mouse_off();

static article_number root_article __APROTO((register article_number root));
static article_number next_root_article __APROTO((register article_number root));
static void set_root_if_closed __APROTO((void));
static article_number thread_counters __APROTO((article_number art));
static void cursor_at_id __APROTO((void));
static attr_type closed_attr __APROTO((register struct menu_info *mi, char *cbuf));
static void mark __APROTO((void));
static void toggle __APROTO((void));
static do_auto_kill __APROTO((void));
static int do_auto_select __APROTO((regexp *re, int mode));
static quit_preview __APROTO((int cmd));
static void count_selected_articles __APROTO((void));
static show_articles __APROTO((void));
static int get_k_cmd_1 __APROTO((void));
static int get_k_cmd __APROTO((void));
static repl_attr __APROTO((register article_number first, register article_number last, int old, int new, int update));
static repl_attr_subject __APROTO((int old, int new, int update));
static repl_attr_all __APROTO((int old, int new, int update));
static void get_purpose __APROTO((char *purpose));
static do_consolidation __APROTO((void));

export int  echo_prefix_key = 1; /* echo prefix keys */
export int  preview_window = 0;	/* size of preview window */
export int  fmt_linenum    = 1; /* menu line format */
export int  fmt_rptsubj    = 0; /* repeat identical subjects if !0 */
export int  novice	   = 1; /* novice mode -- use extended prompts */
export int  long_menu	   = 0; /* don't put empty lines around menu lines */
export int  delay_redraw   = 0; /* prompt again if :-command clears screen */
export int  slow_mode	   = 0;	/* mark selected articles with *s */
export int  re_layout      = 0; /* Re: format presentation on menus */
export int  collapse_subject = 25; /* collapse long subjects at position */
export int  conf_group_entry = 0; /* ask whether group should be entered */
export int  conf_entry_limit = 0; /* ask only if more than .. unread */
export int  mark_read_skip = 4; /* effect of X command */
export int  mark_read_return = 0; /* effect of Z command */
export int  mark_next_group = 0; /* effect of N command */
export int  show_purpose_mode = 1; /* 0: never, 1: new, 2: always */
export int  read_ret_next_page = 0; /* Z returns to next page */

export int  consolidated_menu = 0; /* show only root articles */
export int  save_closed_mode = 13; /* ask how to save closed subj, dflt all */
export int  auto_select_rw = 0; /* select subject read or written */
export int  auto_select_closed = 1; /* select all in closed subject */
export int  menu_spacing = 0;	    /* number of screen lines per menu line */
export char *counter_delim_left  = "[";
export char *counter_delim_right = "] ";
export int  counter_padding = 5; /* counters are padded to align subjects */

export int  auto_preview_mode = 0; /* preview rather than select */
export int  preview_continuation = 12; /* what to do after preview */
export int  preview_mark_read = 1; /* previewed articles are A_READ */
export int  select_on_sender = 0; /* find command selects on sender */
export int  auto_select_subject = 0; /* auto select articles with same subj. */
export int  auto_read_limit = 0; /* ignore auto_read_mode if less articles */

export char delayed_msg[100] = "";	/* give to msg() after redraw */

export int  flush_typeahead = 0;

import int also_read_articles;
import int merged_menu;
import int case_fold_search;
import int ignore_fancy_select;
import int kill_file_loaded;
import int new_read_prompt;
import int any_message;
import int enable_stop;
import int alt_cmd_key, in_menu_mode;
import int mouse_y,mouse_x;                /*mouse event position */
import int mouse_state;
extern key_type erase_key;
extern int get_from_macro;

extern group_completion();

static regexp *regular_expr = NULL;

static int firstl;	/* first menu line */

static article_number firsta;	/* first article on menu (0 based) */
static article_number nexta;	/* first article on next menu */
static int cura;	/* current article */
static int next_cura;	/* article to become cura if >= 0 */
static int numa;	/* no of articles on menu - 1 */
static attr_type last_attr;

#define INTERVAL1	('z' - 'a' + 1)
#define INTERVAL2	('9' - '0' + 1)

char ident[] = "abcdefghijklmnopqrstuvwxyz0123456789";

char attributes[30] = " .,+=#! **"; /* Corresponds to A_XXXX in data.h */

static int menu_length;		/* current no of line on menu */
static int menu_articles;	/* current no of articles on menu */

static struct menu_info {	/* info for each menu line */
    int mi_cura;	/* cura corresponding to this menu line */
    int mi_total;	/* total number of articles with this subject */
    int mi_unread;	/* no of unread articles with this subject */
    int mi_selected;	/* no of selected articles with this subject */
    int mi_left;	/* no of articles marked for later viewing */
    int mi_art_id;	/* article id (for mark()) */
} menu_info[INTERVAL1+INTERVAL2];

static struct menu_info *art_id_to_mi[INTERVAL1+INTERVAL2];

#define IS_VISIBLE(ah)	(((ah)->flag & (A_CLOSED | A_ROOT_ART)) != A_CLOSED)

int
prt_replies(level)
int level;
{
    int re;

    if (level == 0) return 0;
    re = level & 0x80;
    level &= 0x7f;

    switch (re_layout) {
     case 1:
	if (!re) return 0;
	so_printf(">");
	return 1;
     case 2:
	switch (level) {
	 case 0:
	    return 0;
	 case 1:
	    so_printf(">");
	    return 1;
	 default:
	    so_printf("%d>", level);
	    return level < 10 ? 2 : 3;
	}
     case 3:
	so_printf("Re: ");
	return 4;
     case 4:
	if (level == 0 && re) level++;
	break;
    }

    if (level < 10) {
	so_printf("%-.*s", level, ">>>>>>>>>");
	return level;
    }

    so_printf(">>>%3d >>>>", level);
    return 11;
}

static article_number root_article(root)
register article_number root;
{
    register article_header *ah = articles[root];

    if (ah->flag & A_ROOT_ART)
	return root;

    while (root > 0) {
	if (articles[root]->flag & A_ROOT_ART) break;
	root--;
    }
    return root;
}

static article_number next_root_article(root)
register article_number root;
{
    while (++root < n_articles)
	if (articles[root]->flag & A_ROOT_ART) break;
    return root;
}

static void
set_root_if_closed()
{
    if (articles[firsta + cura]->flag & A_CLOSED)
	cura = root_article(firsta + cura) - firsta;
}

/*
 *	count info for thread containing article #art (must be on menu!)
 *	returns article number for next root article
 */

static article_number thread_counters(art)
article_number art;
{
    register struct menu_info *mi;
    register article_number n;
    int total, unread, selected, left;
    
    if (!(articles[art]->flag & A_CLOSED)) return art + 1;
    
    total = unread = selected = left = 0;
    n = art = root_article(art);

    while (n < n_articles) {
	if (articles[n]->attr == 0) unread++;
	else if (articles[n]->attr & A_SELECT) selected++; 
	else if (articles[n]->attr == A_LEAVE_NEXT) left++;
	total++;
	if (++n == n_articles) break;
	if (articles[n]->flag & A_ROOT_ART) break;
    }
    unread += selected;

    mi = menu_info + articles[art]->menu_line;
    mi->mi_total = total;
    mi->mi_unread = unread;
    mi->mi_selected = selected;
    mi->mi_left = left;
    return n;
}

static void
cursor_at_id()
{
    gotoxy(0, firstl + articles[firsta + cura]->menu_line);
    fl; /* place cursor at current article id */
    save_xy();
}

static attr_type closed_attr(mi, cbuf)
register struct menu_info *mi;
char *cbuf;
{
    char lft[10], sel[10], unr[10];
    attr_type cattr;
    
    if (mi->mi_total == mi->mi_left)
	cattr = A_LEAVE_NEXT;
    else if (mi->mi_unread == 0)
	cattr = A_READ;
    else if (mi->mi_total == mi->mi_selected)
	cattr = A_SELECT;
    else if (auto_select_closed == 1 && mi->mi_unread == mi->mi_selected)
	cattr = A_SELECT;
    else if (mi->mi_selected)
	cattr = A_KILL;	/* pseudo flag -> highlight cbuf */
    else
	cattr = 0;

    lft[0] = sel[0] = unr[0] = NUL;
    if (mi->mi_left && mi->mi_left < mi->mi_unread)
	sprintf(lft, "%d,", mi->mi_left);
    if (mi->mi_selected && mi->mi_selected < mi->mi_unread)
	sprintf(sel, "%d/", mi->mi_selected);
    if (mi->mi_unread && mi->mi_unread < mi->mi_total)
	sprintf(unr, "%d:", mi->mi_unread);
    sprintf(cbuf, "%s%s%s%d", lft, sel, unr, mi->mi_total);

    return cattr;
}

static int subj_indent;

static void
mark()
{
    register article_header *ah;
    register struct menu_info *mi;
    int lno, lnum, lsubj, lname;
    int pad;
    char cbuf[80];
    attr_type cattr = 0;

    ah = articles[firsta + cura];
    if (!IS_VISIBLE(ah)) return;
    
    last_attr = ah->attr;
    if (ah->disp_attr == A_NOT_DISPLAYED) {
	mi = &menu_info[ah->menu_line];
	lno = firstl + ah->menu_line;
	gotoxy(0, lno);
	tputc(ident[mi->mi_art_id]);
	cattr = closed_attr(mi, cbuf);
	goto print_line;
    }

    if (cura < 0 || cura > numa) return;

    lno = firstl + ah->menu_line;

    if (ah->flag & A_CLOSED) {
	struct menu_info old;
	char oldctr[80];

	mi = &menu_info[ah->menu_line];
	old = *mi;
	thread_counters(firsta + cura);
	if (old.mi_total == mi->mi_total &&
	    old.mi_selected == mi->mi_selected &&
	    old.mi_left == mi->mi_left &&
	    old.mi_unread == mi->mi_unread) return;

	cattr = closed_attr(mi, cbuf);

	if (!slow_mode) goto print_line;
	closed_attr(&old, oldctr);
	if (strcmp(cbuf, oldctr)) goto print_line;
	last_attr = cattr;
    }

    if (last_attr == ah->disp_attr) return;

    /* A_AUTO_SELECT will not occur here! */

    if (!slow_mode)
	if (last_attr == A_SELECT) {
	    if ((ah->disp_attr & A_SELECT) == 0) goto print_line;
	} else {
	    if (ah->disp_attr & A_SELECT) goto print_line;
	}

    gotoxy(1, lno);
    tputc(attributes[ah->attr]);
    goto out;

 print_line:
    /* menu line formats:
                1  3    8 10     20 22        xx
		:  :    :  :      :  :        :
       -1	id name:8 subject
       0	id name           subject     +lines
       1	id name       lines  subject
       2	id  lines  subject
       3	id subject
       4	id   subject  (or as 1 if short subject)
     */

    if ((ah->flag & A_CLOSED) == 0) {
	cattr = ah->attr;
	cbuf[0] = NUL;
    }

    if (fmt_linenum > 4) fmt_linenum = 1;

    if (!slow_mode && (cattr & A_SELECT)) {
	if (so_gotoxy(1, lno, 1) == 0)
	    tputc(attributes[A_SELECT]);
    } else {
    	gotoxy(1, lno);
	tputc(cattr == A_KILL ? ' ' : attributes[cattr]);
    }

    if (ah->lines <    10) lnum = 1; else
    if (ah->lines <   100) lnum = 2; else
    if (ah->lines <  1000) lnum = 3; else
    if (ah->lines < 10000) lnum = 4; else lnum = 5;

    lsubj = Columns - cookie_size - 2; /* ident char + space */

    switch (fmt_linenum) {

     case -1:
	lsubj -= 9;
	so_printf("%-8.8s ", ah->sender);
	goto no_counters;
	
     case 0:
	lsubj -= NAME_LENGTH + 1 + 2 + lnum;  /* name. .subj. +.lines */
	so_printf("%-*s ", NAME_LENGTH, ah->sender);
	break;

     case 4:
	if ((int)ah->subj_length > (lsubj - NAME_LENGTH - 5))
	    if (fmt_rptsubj || lno == firstl || (ah->flag & A_SAME) == 0) {
		so_printf("  ");
		lsubj -= 2;
		break;
	    }
	/* else use layout 1, so fall thru */

	/* FALL THRU */
     case 1:
	lsubj -= NAME_LENGTH + 5;
	/* name.lines.  .subj (name may be shortened) */
	lname = NAME_LENGTH + 2 - lnum;
	so_printf("%-*.*s ", lname, lname, ah->sender);
	so_printf(ah->lines >= 0 ? "%d  " : "?  ", ah->lines);
	break;

     case 2:
	lsubj -= 6;
	so_printf(ah->lines >=0 ? "%5d " : "    ? ", ah->lines);
	break;

     case 3:
	break;
    }

    if (cbuf[0]) {
	so_printf("%s", counter_delim_left);
	if (cattr == A_KILL) so_gotoxy(-1, -1, 0);
	so_printf("%s", cbuf);
	if (cattr == A_KILL) so_end();
	so_printf("%s", counter_delim_right);
	pad = strlen(cbuf) + strlen(counter_delim_left) + strlen(counter_delim_right);
	if (cattr == A_KILL) pad += cookie_size * 2;
	lsubj -= pad > subj_indent ? pad : subj_indent;
	pad = subj_indent - pad;
    } else {
	lsubj -= subj_indent;
	pad = subj_indent;
    }
    if (pad > 0) {
	if (pad > 20) pad = 20;
	so_printf("                    "+20-pad);
    }

 no_counters:
    if (!fmt_rptsubj && lno > firstl && ah->flag & A_SAME) {
	if (ah->replies == 0 || prt_replies(ah->replies) == 0)
	    so_printf("-");
    } else {
	lsubj -= prt_replies(ah->replies);
	if (lsubj >= (int)ah->subj_length)
	    so_printf("%s", ah->subject);
	else
	if (collapse_subject < 0)
	    so_printf("%-.*s", lsubj, ah->subject);
	else {
	    if (collapse_subject > 0)
		so_printf("%-.*s", collapse_subject, ah->subject);
	    so_printf("<>%s", ah->subject + ah->subj_length - lsubj + collapse_subject + 2);
	}
    }

    if (fmt_linenum == 0)
	so_printf(ah->lines >= 0 ? " +%d" : " +?", ah->lines);

    so_end();
    if ((ah->flag & A_CLOSED) && lsubj > (int)ah->subj_length)
        clrline_noflush();

 out:
    ah->disp_attr = last_attr;
    return;
}

static void
toggle()
{
    last_attr = articles[firsta + cura]->attr =
	articles[firsta + cura]->attr & A_SELECT ? 0 : A_SELECT;
}

static int
do_auto_kill()
{
    register article_number i;
    register article_header *ah, **ahp;
    int any = 0;

    for (i = 0, ahp = articles; i < n_articles; i++, ahp++) {
	ah = *ahp;
	if (auto_select_article(ah, 0)) {
	    ah->attr = A_KILL;
	    any = 1;
	}
    }
    return any;
}

/*
 *	perform auto selections that are not already selected
 *	if article is in range firsta..firsta+numa (incl) mark article
 */

static int
do_auto_select(re, mode)
regexp *re;
int mode;
{
    register article_number i;
    register article_header *ah, **ahp;
    int count = 0, o_cura, should_mark;

    if (mode == 1 && re == NULL) 
	if (!kill_file_loaded && !init_kill()) return 0;

    o_cura = cura;
    should_mark = 0;

    /*
     * note: this code assumes that a visible article will be found
     * before anything is marked
     */
    for (i = 0, ahp = articles; i < n_articles; i++, ahp++) {
	ah = *ahp;
	if (IS_VISIBLE(ah)) {
	    if (should_mark) {
		mark();
		should_mark = 0;
	    }
	    cura = i - firsta;
	}
	if (re != NULL) {
	    if (!regexec_cf(re, select_on_sender ? ah->sender : ah->subject)) continue;
	} else
	    if (!auto_select_article(ah, mode)) continue;

	count++;
	if (ah->attr & A_SELECT) continue;
	ah->attr = A_SELECT;
	if (firsta <= i && i <= (firsta+numa))
	    should_mark = 1;
    }

    if (should_mark) mark();

    if (count)
	msg("Selected %d article%s", count, plural((long)count));
    else
	msg("No selections");
    
    cura = o_cura;
    return 0;
}

static int
quit_preview(cmd)
int cmd;
{
    int op;

    if ((firsta + cura) >= n_articles) return 1;
    op = preview_continuation;
    if (cmd == MC_PREVIEW_NEXT)	op /= 10;
    op %= 10;
    switch (op) {
     case 0:
	return 1;
     case 1:
	return 0;
     case 2:
	return articles[firsta+cura]->flag & A_ROOT_ART;
    }
    return 0;
}

export long n_selected;
export int show_art_next_invalid;

static void
count_selected_articles()
{
    register article_number cur;

    n_selected = 0;
    for (cur = 0; cur < n_articles; cur++) {
	if (articles[cur]->attr & A_SELECT) n_selected++;
    }
}

static int
show_articles()
{
    register article_number cur, next;
    register article_header *ah;
    article_number elim_list[1];
    register int mode;
    int cmd, prev = -1, again;
    attr_type o_attr;

    do {
	for (cur = 0; cur < n_articles; cur++) {
	    if (articles[cur]->attr & A_SELECT) break;
	}

	while (cur < n_articles) {

	    for (next = cur+1; next < n_articles; next++) {
		if (articles[next]->attr & A_SELECT) break;
	    }
	    show_art_next_invalid = 0;

	 show:
	    ah = articles[cur];
	    o_attr = ah->attr;
	    ah->attr = 0;

            if (auto_select_rw && !ignore_fancy_select &&
			!auto_select_article(ah,1))
		enter_kill_file(current_group,ah->subject,6,30);

	    if (new_read_prompt)
		count_selected_articles();

	    mode = 0;
	    if (prev >= 0) mode |= MM_PREVIOUS;
	    if (next == n_articles) mode |= MM_LAST_SELECTED;
	    if ((cur + 1) >= n_articles) mode |= MM_LAST_ARTICLE;
	    if (cur == 0) mode |= MM_FIRST_ARTICLE;

	    cmd = more(ah, mode, 0);

	    switch (cmd) {

	     case MC_DO_KILL:
		if (do_auto_kill()) {
		    elim_list[0] = cur;
		    elim_articles(elim_list, 1);
		    cur = elim_list[0];
		    if (cur >= n_articles || cur < 0)
			cur = n_articles;
		    else {
			for (prev = cur; prev >= 0; --prev)
			    if ((articles[prev]->attr & A_READ) != 0)
				break;
			for (; cur < n_articles; ++cur)
			    if ((articles[cur]->attr & A_SELECT) != 0)
				break;
		    }
		    continue;
		}
		break;

	     case MC_DO_SELECT:
		break;

	     case MC_PREV:
		ah->attr = o_attr;
		if (prev == next) break;

		next = cur; cur = prev; prev = next;
		goto show;

	     case MC_NEXTSUBJ:
		ah->attr = A_READ;
		for (next = cur+1; next < n_articles; next++) {
		    if ((ah = articles[next])->flag & A_ROOT_ART) break;
		    ah->attr = A_READ;
		}
		for (; next < n_articles; next++) {
		    if (articles[next]->attr & A_SELECT) break;
		}
		break;

	     case MC_ALLSUBJ:
		ah->attr = A_READ;
		for (next = cur+1; next < n_articles; next++) {
		    ah = articles[next];
		    if (ah->flag & A_ROOT_ART) break;
		    ah->attr = A_SELECT;
		}
		for (next = cur+1; next < n_articles; next++)
		    if (articles[next]->attr & A_SELECT) break;
		break;

	     case MC_MENU:
		ah->attr = o_attr;
		if (nexta - firsta < n_articles) {
			/* Keep a little article context by making the */
			/* current article go on menu line 6 if possible */
			if (IS_VISIBLE(articles[cur]))
				firsta = cur;
			else
				firsta = root_article(cur);
			for (next = 0; firsta > 0 && next < 5; next++) {
				firsta--;
				if (!IS_VISIBLE(articles[firsta]))
					firsta = root_article(firsta);
			}
		}
		next_cura = cur - firsta;

		return MC_MENU;

	     case MC_NEXT:
		if (ah->attr == 0)	/* Not set by more (sufficient ?!?!) */
		    ah->attr = A_READ;
		break;

	     case MC_BACK_ART:
		ah->attr = o_attr ? o_attr : A_SEEN;
		next = cur - 1;
		break;

	     case MC_FORW_ART:
		ah->attr = o_attr ? o_attr : A_SEEN;
		next = cur + 1;
		break;

	     case MC_NEXTGROUP:
	     case MC_REENTER_GROUP:
	     case MC_QUIT:
		ah->attr = o_attr;
		return cmd;

	     case MC_READGROUP:
		for (cur = 0; cur < n_articles; cur++) {
		    ah = articles[cur];
		    if (ah->attr == 0 || (ah->attr & A_SELECT))
			ah->attr = A_READ;
		}
		return MC_NEXTGROUP;
	    }

	    if (show_art_next_invalid)
		for (next = cur+1; next < n_articles; next++) {
		    if (articles[next]->attr & A_SELECT) break;
		}
	    prev = cur; cur = next;
	}

	for (cur = 0; cur < n_articles; cur++)
	    if (articles[cur]->attr & A_SELECT) break;

	again = 0;
	if (cur < n_articles) continue;
	for (cur = 0; cur < n_articles; cur++) {
	    ah = articles[cur];
	    if (ah->attr == A_LEAVE) {
		if (again == 0) {
		    prompt("Show left over articles again now? ");
		    if (yes(0) <= 0) break;
		}
		ah->attr = A_SELECT;
		again++;
	    }
	}

	if (again > 1)
	    sprintf(delayed_msg, "Showing %d articles again", again);
    } while (again);

    return MC_READGROUP;
}

static int article_id;
static int cur_key;
static int is_k_select;	/* set when K_ARTICLE_ID was really K_SELECT */

#define MOUSE   (mouse_y >= 0)

static int mouse_map(map)
int map;
{
    /* remap mouse functions back into normal functions */
    if (map == K_M_SELECT) {
	map = K_ARTICLE_ID;
    } else
    if (map == K_M_PREVIEW) {
	map = K_PREVIEW;
    } else
    if (map == K_M_SELECT_SUBJECT) {
	map = K_SELECT_SUBJECT;
    } else
    if (map == K_M_SELECT_RANGE) {
	map = K_SELECT_RANGE;
    } else
	return map;

     /* deal with mouse article selection */
    if (MOUSE) {
       if ((mouse_y >= firstl) && (mouse_y <= firstl + menu_length - 1)){
           article_id = mouse_y - firstl; 
           article_id = art_id_to_mi[article_id]->mi_cura;
       }
    }
    return map;
}

static int get_k_cmd_1()
{
    register int c, map;
    int *key_map = menu_key_map;
 
    if (flush_typeahead) flush_input();

 loop:

    article_id = -1;

    if ((c = get_c()) & GETC_COMMAND) {
	cur_key = K_interrupt;
	map = c & ~GETC_COMMAND;
    } else {
	cur_key = c;
	map = key_map[c];
    }
    if (s_hangup) map = K_QUIT;

    if (map & K_PREFIX_KEY) {
	key_map = keymaps[map & ~K_PREFIX_KEY].km_map;
	if (echo_prefix_key) msg("%s", key_name(cur_key));
	goto loop;
    }

    is_k_select = 0;
    if (map == K_SELECT) {
	map = K_ARTICLE_ID;
	article_id = cura;
	is_k_select = 1;
    } else
    if (map & K_ARTICLE_ID) {
	article_id = map & ~K_ARTICLE_ID;
	map = K_ARTICLE_ID;

	if (article_id < 0 || article_id >= menu_articles) {
	    ding();
	    goto loop;
	}
	article_id = art_id_to_mi[article_id]->mi_cura;
    } else 
        map = mouse_map(map);

    if (any_message && map != K_LAST_MESSAGE) clrmsg(-1);
    return map;
}

static int get_k_cmd()
{
    register int map;

    map = get_k_cmd_1();
    if (map & K_MACRO)
	map = orig_menu_map[cur_key];
    return map;
}


char *pct(start, end, first, last)
long start, end, first, last;
{
    long n = end - start;
    static char buf[16];
    char *fmt;

    if (first <= start || n <= 0)
	if (last >= end || n <= 0)
	    return "All";
	else
	    fmt = "Top %d%%";
    else
	if (last >= end)
	    return "Bot";
	else
	    fmt = "%d%%";

    sprintf(buf, fmt, ((last - start) * 100)/n);
    return buf;
}

static int
repl_attr(first, last, old, new, update)
register article_number first, last;
register attr_type old, new;
int update;
{
    int any;
    register article_header *ah;
    article_number ocura = cura;

    if (new == old) return 0;
    if (new == A_KILL) update = 0;

    any = 0;
    cura = -1;
    while (first < last) {
	ah = articles[first];
	if (cura >= 0 && ah->flag & A_ROOT_ART) {
	    set_root_if_closed();
	    mark();
	    cura = -1;
	}
	if (old == A_KILL || ah->attr == old) {
	    ah->attr = new;
	    if (update && first >= firsta && first < nexta) {
		cura = first - firsta;
		if ((ah->flag & A_CLOSED) == 0) {
		    mark();
		    cura = -1;
		}
	    }
	    any = 1;
	}

	first++;
    }

    if (cura >= 0) {
	set_root_if_closed();
	mark();
    }
    cura = update ? last - firsta : ocura;
    return any;
}

static int
repl_attr_subject(old, new, update)
attr_type old, new;
int update;
{
    int	f, l;

    f = root_article(firsta+cura);
    l = next_root_article(firsta+cura);
    if (old == A_SELECT)
	(void) repl_attr(f, l, A_AUTO_SELECT, A_SELECT, update);
    return repl_attr(f, l, old, new, update);
}

static int
repl_attr_all(old, new, update)
attr_type old, new;
int update;
{
    return repl_attr((article_number)0, n_articles, old, new, update);
}

static void
get_purpose(purpose)
char *purpose;
{
#ifdef	CACHE_PURPOSE
    register char *cp;
    extern char *purp_lookup();

    cp = purp_lookup(current_group->group_name);
    strncpy(purpose, cp, 76);
    return;

#else
    FILE *f;
    char line[256], *group;
    register char *cp, *pp;
    register int len;

    if (current_group == NULL) return;
    if ((current_group->master_flag & M_VALID) == 0) return;
    if (current_group->group_flag & G_FAKED) return;
    
    if ((f = open_purpose_file()) == NULL) return;

    group = current_group->group_name;
    len = current_group->group_name_length;

    while (fgets(line, 256, f) != NULL) {
	if (!isascii(line[len]) || !isspace(line[len])) continue;
	if (strncmp(line, group, len)) continue;
	cp = line + len;
	while (*cp && isspace(*cp)) cp++;
	for (pp = purpose, len = 76; --len >= 0 && *cp && *cp != NL; )
	    *pp++ = *cp++;
	*pp = NUL;
    }
#endif	/* CACHE_PURPOSE */
}

/*
 * bypass_consolidation may be set to temporarily overrule the global
 * consolidated_menu variable:
 *	1:	don't consolidate	(e.g. for G=...  )
 *	2:	do consolidate
 *	3:	use consolidated_mode
 */

export int bypass_consolidation = 0;
static int cur_bypass = 0;	/* current bypass status (see below) */

static int
do_consolidation()
{
    int consolidate;

    switch (bypass_consolidation) {
     case 0:
	break;
     case 1:
	cur_bypass = -1;	/* no consolidation */
	break;
     case 2:
	cur_bypass = 1;		/* force consolidation */
	break;
     case 3:
	cur_bypass = 0;		/* reset bypass to use consolidated_menu */
	break;
    }
    bypass_consolidation = 0;

    if (cur_bypass)
	consolidate = cur_bypass > 0;
    else
	consolidate = consolidated_menu;

    if (consolidate)
	for (nexta = 0; nexta < n_articles; nexta++)
	    articles[nexta]->flag |= A_CLOSED;
    else
	for (nexta = 0; nexta < n_articles; nexta++)
	    articles[nexta]->flag &= ~A_CLOSED;

    return consolidate;
}

int
menu(print_header)
fct_type print_header;
{
    register 		k_cmd, cur_k_cmd;
    register		article_header *ah;
    register struct menu_info *mi;
    int			consolidate, o_bypass;
    int			last_k_cmd;
    int 		menu_cmd = 0, temp;
    int 		save_selected;
    article_number	last_save;
    attr_type		orig_attr, junk_attr;
    int			doing_unshar, did_unshar, junk_prompt;
    char 		*fname, *savemode;
    int 		maxa;	/* max no of articles per menu page */
    article_number	o_firsta, temp1 = 0, temp2;
    int			o_mode;	/* for recursive calls */
    static		menu_level = 0;
    char		purpose[80], pr_fmt[60];
    article_number	elim_list[3];
    int			entry_check;
    int			auto_read;
    long		o_selected;

#define	menu_return(cmd) \
    { menu_cmd = (cmd); goto menu_exit; }

    flush_input();

    o_firsta = firsta;
    o_mode = in_menu_mode;
    o_selected = n_selected;

    in_menu_mode = 1;

    menu_level++;

    if (menu_level == 1) {
	if ((current_group->group_flag & G_COUNTED)
	    && n_articles != current_group->unread_count) {
	    add_unread(current_group, -1);
	    current_group->unread_count = n_articles;
	    add_unread(current_group, 0);
	}
	entry_check = conf_group_entry && n_articles > conf_entry_limit;
	auto_read = auto_read_limit < 0 || n_articles <= auto_read_limit;
    } else {
	entry_check = 0;
	auto_read = 0;
    }

    sprintf(pr_fmt,
	    menu_level == 1 ?
	      "\1\2-- SELECT %s-----%%s-----\1" :
	      "\1\2-- SELECT %s-----%%s-----<%s%d>--\1",
	      novice ? "-- help:? " : "",
	      novice ? "level " : "",
	      menu_level);

    purpose[0] = NUL;
    if (!merged_menu)
	switch (show_purpose_mode) {
	 case 0: break;
	 case 1: if ((current_group->group_flag & G_NEW) == 0) break;
	 case 2: get_purpose(purpose);
	         if (purpose[0]) strcpy(delayed_msg, purpose);
	}

    o_bypass = cur_bypass;
    cur_bypass = 0;
    
    consolidate = do_consolidation();

    firsta = 0;
    while (firsta < n_articles && articles[firsta]->attr == A_SEEN)
	firsta++;

    if (firsta == n_articles) firsta = 0;

    next_cura = -1;
    cur_k_cmd = K_UNBOUND;

#ifdef HAVE_JOBCONTROL
#define	REDRAW_CHECK	if (s_redraw) goto do_redraw

 do_redraw:
    /* safe to clear here, because we are going to redraw anyway */
    s_redraw = 0;
#else
#define REDRAW_CHECK
#endif

 redraw:
    s_keyboard = 0;

 empty_menu_hack:	/* do: "s_keyboard=1; goto empty_menu_hack;" */
    if (!slow_mode) s_keyboard = 0;

    nexta = firsta;

    clrdisp();

    if (entry_check) {
	prompt_line = firstl = CALL(print_header)();
	prompt("\1Enter?\1 ");
	if ((temp = yes(0)) <= 0) {
	    if (temp < 0) {
		prompt("\1Mark as read?\1 ");
		if ((temp = yes(0)) < 0) menu_return(ME_QUIT);
		if (temp > 0) repl_attr_all(A_KILL, A_READ, 0);
	    }
	    menu_return(ME_NEXT);
	}

	gotoxy(0, firstl);
	clrline();
    }

    if (auto_read) {
	auto_read = entry_check = 0;
	if (repl_attr_all(0, A_AUTO_SELECT, 0)) {
	    k_cmd = K_READ_GROUP_UPDATE;
	    if (purpose[0])
		strcpy(delayed_msg, purpose);
	    else
	    sprintf(delayed_msg, "Entering %s, %ld articles",
		    current_group->group_name, (long)n_articles);
	    goto do_auto_read;
	}
    }

    if (!entry_check)
	firstl = CALL(print_header)();
    entry_check = 0;


    maxa = Lines - preview_window - firstl - 2;
    if (!long_menu) firstl++, maxa -= 2;

    if (maxa > (INTERVAL1 + INTERVAL2))
	maxa = INTERVAL1 + INTERVAL2;

 nextmenu:

    no_raw();
    gotoxy(0, firstl);
    clrpage();

    if (articles[nexta]->flag & A_CLOSED)
	nexta = root_article(nexta);
    firsta = nexta;
    cura = 0;

    REDRAW_CHECK;

    menu_length = 0;
    menu_articles = 0;
    goto draw_menu;
    
 partial_redraw:
    next_cura = cura;
 partial_redraw_nc:
    nexta = firsta + cura;
    menu_length = articles[nexta]->menu_line;
    menu_articles = menu_info[menu_length].mi_art_id;
    gotoxy(0, firstl + menu_length);
    clrpage();

 draw_menu:
    subj_indent = consolidate ? counter_padding : 0;

    if (!s_keyboard) {
	int first_menu_line = menu_length;

	mi = menu_info + menu_length;
	while (nexta < n_articles) {
	    REDRAW_CHECK;

	    ah = articles[nexta];
	    if (ah->flag & A_HIDE) {
		ah->menu_line = menu_length;	/* just in case.... */
		continue;
	    }
	    if (menu_length > first_menu_line) {
		switch (menu_spacing) {
		 case 0:
		    break;
		 case 1:
		    if ((ah->flag & A_ROOT_ART) == 0) break;
		    /* XXX: bug? Another fall? */
		 case 2:
		    menu_length++;
		    mi++;
		    break;
		}
		if (menu_length >= maxa) break;
	    }
	    
	    ah->menu_line = menu_length;
	    art_id_to_mi[menu_articles] = mi;
	    mi->mi_art_id = menu_articles++;
	    
	    mi->mi_cura = cura = nexta - firsta;
	    if (ah->flag & A_CLOSED) {	/* skip rest of thread */
		nexta = thread_counters(nexta);
		if (nexta == firsta + cura + 1)
		    ah->flag &= ~A_CLOSED;
		else {
		    article_number tmpa = firsta + cura;
		    while (++tmpa < nexta)
			articles[tmpa]->menu_line = menu_length;
		}
	    } else
		nexta++;
	    ah->disp_attr = A_NOT_DISPLAYED;
	    mark();
	    if (++menu_length >= maxa) break;
	    mi++;
	}
    }

    if (menu_length > maxa) menu_length = maxa;

    fl;
    s_keyboard = 0;

    prompt_line = firstl + menu_length;
    if (!long_menu || menu_length < maxa) prompt_line++;

    numa = nexta - firsta - 1;
     if (numa < 0) prompt_line++;

/* Changed by Stefan Schwarz (stefans@bauv.unibw-muenchen.de Nov. 17, 1992 */
     if (firsta + next_cura >= nexta) next_cura = -1;
/* End of changes*/

     if (next_cura >= 0) {
	 cura = next_cura;
	 set_root_if_closed();
	 next_cura = -1;
     } else {
	 cura = 0;
	 for (article_id = firsta; cura < numa; article_id++, cura++)
	 {
	     if ((articles[article_id]->attr & A_SELECT) == 0) break;	/*???*/
	     if (!IS_VISIBLE(articles[article_id])) continue;
	 }
	 if (!IS_VISIBLE(articles[article_id]))
	     cura = root_article(article_id) - firsta;
     }

  build_prompt:

     nn_raw();

  Prompt:

     prompt(pr_fmt,
	    pct(0L, (long)(n_articles-1), (long)firsta, (long)(firsta+numa)));

     if (delayed_msg[0] != NUL) {
	 msg(delayed_msg);
	 delayed_msg[0] = NUL;
     }

  same_prompt:

    if (cura < 0 || cura > numa) cura = 0;

    if (!IS_VISIBLE(articles[firsta+cura])) {
	cura = next_root_article(firsta + cura) - firsta;
	if (cura > numa) cura = 0;
    }

     if (numa >= 0) {
	 cursor_at_id();
     }

     last_k_cmd = cur_k_cmd;

  get_next_k_cmd:
     k_cmd = get_k_cmd_1();

  alt_key:
    if (k_cmd & K_MACRO) {
	m_invoke(k_cmd & ~K_MACRO);
	goto get_next_k_cmd;
    }

#define STATE(state)  {k_cmd = state; goto new_state;} 

/* mouse clicks on top or bottom prompt bars  */
#define MOUSE_MENU					\
	 if (MOUSE) {					\
	     if (mouse_y > firstl + menu_length ) {     \
		 STATE(K_CONTINUE);			\
	     } else if (mouse_y == firstl + menu_length ) { \
		 STATE(K_UNBOUND);			\
	     } else if (mouse_y < firstl - 1) {		\
		 if (firsta > 0) {			\
		     STATE(K_PREV_PAGE);		\
		} else {				\
		     STATE(K_PREVIOUS);			\
		}					\
	     } else if (mouse_y == firstl - 1) {		\
		 STATE(K_UNBOUND);			\
	     }						\
	}

new_state:
     switch (cur_k_cmd = k_cmd) {

      case K_UNBOUND:
	 ding();
	 flush_input();

	 /* FALL THRU */
      case K_INVALID:
	 goto same_prompt;

      case K_REDRAW:
	 next_cura = cura;
	 goto redraw;

      case K_LAST_MESSAGE:
	 msg((char *)NULL);
	 goto same_prompt;

      case K_HELP:
	 if (numa < 0)  goto nextmenu;	/* give specific help here */
	 display_help("menu");
	 goto redraw;

      case K_SHELL:
	 if (group_file_name) *group_file_name = NUL;
	 if (shell_escape()) goto redraw;
	 goto Prompt;

      case K_VERSION:
	 prompt(P_VERSION);
	 goto same_prompt;

      case K_EXTENDED_CMD:
	 temp = consolidated_menu;
	 switch (alt_command()) {

	  case AC_UNCHANGED:
	     goto same_prompt;

	  case AC_QUIT:
	     menu_return( ME_QUIT );
	     break;	/* for lint. we can't actually get here */

	  case AC_PROMPT:
	     goto Prompt;

	  case AC_REORDER:
	     firsta = 0;
	     consolidate = do_consolidation();
	     goto redraw;

	  case AC_REDRAW:
	     if (temp != consolidated_menu)
		 consolidate = do_consolidation();
	     goto redraw;

	  case AC_KEYCMD:
	     k_cmd = alt_cmd_key;
	     goto alt_key;

	  case AC_REENTER_GROUP:
	     menu_return(ME_REENTER_GROUP);
	 }
	 /* XXX: bug? fall */

      case K_QUIT:
	 menu_return(ME_QUIT);
	 break;	/* for lint. we can't actually get here */

      case K_M_TOGGLE:
	 if (mouse_state) {
	     xterm_mouse_off();
	     mouse_state = 0;
	 } else {
	     xterm_mouse_on();
	     mouse_state = 1;
	 }
      goto Prompt;

      case K_CANCEL:
	 savemode = "Cancel";
	 fname = "";
	 goto cancel1;

      case K_SAVE_NO_HEADER:
      case K_SAVE_SHORT_HEADER:
      case K_SAVE_FULL_HEADER:
      case K_SAVE_HEADER_ONLY:
      case K_PRINT:
      case K_UNSHAR:
      case K_PATCH:
      case K_UUDECODE:

	 if (numa < 0) goto nextmenu;

	 fname = init_save(k_cmd, &savemode);
	 if (fname == NULL) goto Prompt;
 
      cancel1:
	 enable_stop = 0;
	 save_selected = 0;
	 doing_unshar = k_cmd == K_UNSHAR || k_cmd == K_PATCH;
	 did_unshar = 0;

	 m_startinput();

	 if (novice)
	     msg(" * selected articles on this page, + all selected articles");

	 while (!save_selected && !did_unshar) {
	     prompt("\1%s\1 %.*s Article (* +): ",
		    savemode, Columns - 25, fname);

	     k_cmd = get_k_cmd();

	     if (k_cmd == K_SELECT_SUBJECT) {
		 save_selected = 1;
		 cura = 0;
		 article_id = firsta;
		 last_save = firsta + numa;
	     } else
	     if (k_cmd == K_AUTO_SELECT) {
		 save_selected = 1;
		 cura = -firsta;
		 article_id = 0;
		 last_save = n_articles - 1;
	     } else
	     if (k_cmd == K_ARTICLE_ID) {
		 cura = article_id;
		 article_id += firsta;
		 last_save = article_id;
		 if (articles[article_id]->flag & A_CLOSED) {
		     int n = save_closed_mode % 10;
		     
		     if (article_id == n_articles - 1) 
			 goto save_it;
		     if (articles[article_id + 1]->flag & A_ROOT_ART)
			 goto save_it;

		     if (save_closed_mode >= 10) {
			 prompt("\1%s thread\1 (r)oot (s)elected (u)nread (b)oth (a)ll  (%c)",
				savemode, "rsuba"[n]);
			 switch (get_c()) {
			  case 'r':	n = 0; break;
			  case 's':	n = 1; break;
			  case 'u':	n = 2; break;
			  case 'b':	n = 3; break;
			  case 'a':	n = 4; break;
			  case SP:
			  case CR:
			  case NL:	break;
			  default:	ding(); goto Prompt;
			 }
		     }
		     switch (n) {
		      case 0:		/* save root only */
			 break;
		      case 1:		/* save all selected */
			 save_selected = 1;
			 break;
		      case 2:		/* save all unread */
			 save_selected = 2;
			 break;
		      case 3:		/* save all selected + unread */
			 save_selected = 3;
			 break;
		      case 4: 		/* save all articles */
			 break;
		     }
		     if (n) last_save = next_root_article(article_id) - 1;
		     save_selected |= 8;	/* closed subject */
		     temp1 = cura;
		 }
	     } else
		 break;

	  save_it:
	     for ( ; article_id <= last_save ; article_id++, cura++) {
		 ah = articles[article_id];
		 switch (save_selected & 0x3) {
		  case 0:
		     break;
		  case 3:
		     if (ah->attr == 0) break;
		     /* XXX: check fall */
		  case 1:
		     if ((ah->attr & A_SELECT) == 0) continue;
		     break;
		  case 2:
		     if (ah->attr == 0) break;
		     continue;
		 }		     

		 if (cur_k_cmd == K_CANCEL) {
		     if (current_group->group_flag & G_FOLDER) {
			 fcancel(ah);
		     } else
			 switch (cancel(ah)) {
			  case -1:
			     did_unshar = 1;
			     continue;
			  case 0:
			     ah->attr = A_CANCEL;
			     break;
			  default:
			     continue;
			 }

		     if (!did_unshar)
			 mark();

		     continue;
		 }

		 if (doing_unshar) {
		     did_unshar++;
		 } else
		 if (ah->subject != NULL)
		     prompt("Processing '%.50s'...", ah->subject);
		 else if (cura >= 0 && cura <= numa)
		     prompt("Processing %c...", ident[cura]);
		 else
		     prompt("Processing entry %d...", article_id);

		 if (save(ah)) {
		     ah->attr = A_READ;
		     if (doing_unshar) continue;

		     if (cura >= 0 && cura <= numa)
			 mark();
		 }
	     }
	     if (save_selected & 8) {
		 save_selected = 0;	/* select closed */
		 cura = temp1;
		 /* mark(); */
	     }
	 }

	 if (save_selected) cura = 0;

	 m_endinput();

	 enable_stop = 1;
	 if (cur_k_cmd != K_CANCEL)
	     end_save();

	 if (did_unshar) {
	     tprintf("\r\n");
	     any_key(0);
	     goto redraw;
	 }
	 goto Prompt;

      case K_FOLLOW_UP:
      case K_REPLY:
	 if (numa < 0) goto nextmenu;

	 prompt(k_cmd == K_REPLY ?
		"\1Reply to author\1 of article: " :
		"\1Follow Up\1 to article: ");

	 if (get_k_cmd() == K_ARTICLE_ID)
	     if (answer(articles[firsta+article_id], k_cmd, -1))
		 goto redraw;

	 goto Prompt;

      case K_POST:
	 if (post_menu()) goto redraw;
	 goto Prompt;

      case K_MAIL_OR_FORWARD:
	 if (numa < 0) goto nextmenu;

	 prompt("\1Article to be forwarded\1 (SP if none): ");

	 if ((k_cmd = get_k_cmd()) == K_ARTICLE_ID) {
	     if (answer(articles[firsta+article_id], K_MAIL_OR_FORWARD, 1))
		 goto redraw;
	 } else
	 if (k_cmd == K_CONTINUE)
	     if (answer((article_header *)NULL, K_MAIL_OR_FORWARD, 0))
		 goto redraw;

	 goto Prompt;
/*
      case K_CANCEL:
	 if (numa < 0) goto nextmenu;

	 if (current_group->group_flag & G_FOLDER) {
	     prompt("\1Cancel Folder\1 Article: ");
	     if (get_k_cmd() == K_ARTICLE_ID) {
		 cura = article_id;
		 fcancel(articles[firsta+article_id]);
		 mark();
	     }
	     goto Prompt;
	 }

	 prompt("\1Cancel\1 Article: ");

	 if (get_k_cmd() == K_ARTICLE_ID)
	     if (cancel(articles[firsta+article_id]) & 1) goto redraw;
	 goto Prompt;
*/
      case K_UNSUBSCRIBE:
	 if (unsubscribe(current_group)) {
	     if (current_group->group_flag & G_UNSUBSCRIBED)
		 menu_return(ME_NEXT);
	     home();
	     CALL(print_header)();
	 }
	 goto Prompt;

      case K_GROUP_OVERVIEW:
	 group_overview(-1);
	 goto redraw;

      case K_KILL_HANDLING:
	 switch (kill_menu((article_header *)NULL)) {
	  case 0:		/* select */
	     do_auto_select((regexp *)NULL, 2);
	     break;
	  case 1:		/* kill */
	     if (!do_auto_kill()) break;
	     goto junk_killed_articles;
	  default:
	     break;
	 }
	 goto Prompt;

      case K_CONTINUE:	/* goto next menu page or show the articles */
	 repl_attr(firsta, nexta, 0, A_SEEN, 0);
	 /* FALL THRU */
      case K_CONTINUE_NO_MARK:	/* but don't mark unselected articles */
	 if (nexta < n_articles) goto nextmenu;
	 break;

      case K_READ_GROUP_THEN_SAME:
	 if ((temp = mark_read_return)) goto do_marked_by;
	 break;

      case K_NEXT_GROUP_NO_UPDATE:
	 if ((temp = mark_next_group)) goto do_marked_by;
	 menu_return(ME_NEXT);
	 break;	/* for lint. we can't actually get here */

      case K_READ_GROUP_UPDATE:
	 if ((temp = mark_read_skip) == 0) break;
      do_marked_by:
	 temp1 = 0; temp2 = n_articles;
	 switch (temp) {
	  case 1:
	     temp1 = firsta;
	     /* FALL THRU */
	  case 3:
	     temp2 = nexta;
	     break;
	  case 2:
	     temp2 = firsta - 1;
	     break;
	  case 4:
	     break;
	 }
	 repl_attr(temp1, temp2, 0, A_SEEN, 0);
	 if (k_cmd != K_NEXT_GROUP_NO_UPDATE) break;
	 menu_return(ME_NEXT);
	 break;	/* for lint. we can't actually get here */

      case K_PREVIOUS:
	 menu_return(ME_PREV);
	 break;	/* for lint. we can't actually get here */

      case K_ADVANCE_GROUP:
      case K_BACK_GROUP:
	 if (merged_menu) {
	     msg("No possible on merged menu");
	     goto same_prompt;
	 }

	 /* FALL THRU */
      case K_GOTO_GROUP:
	 temp1 = n_articles;

	 switch (goto_group(k_cmd, (article_header *)NULL, (flag_type)0)) {

	  case ME_REDRAW:
	     firsta = 0;
	     if (temp1 != n_articles && consolidate)
		 consolidate = do_consolidation();
	     goto redraw;

	  case ME_NO_ARTICLES:
	     msg("No selections made.");

	     /* FALL THRU */
	  case ME_NO_REDRAW:
	     goto Prompt;

	  case ME_QUIT:
	     menu_return( ME_QUIT );
	     break;	/* for lint. we can't actually get here */

	  case ME_PREV:
	     goto redraw;

	  case ME_NEXT:
	     s_keyboard = 1;
	     goto empty_menu_hack;
	 }

	 /* XXX: Fall ? */
      case K_OPEN_SUBJECT:
	 if (numa < 0) goto nextmenu;
	 prompt("\1Open subject\1");

	 k_cmd = get_k_cmd();
	 if (k_cmd != K_ARTICLE_ID) {
	     if (k_cmd != cur_k_cmd) goto Prompt;
	     article_id = cura;
	 }

      open_subject:
	 ah = articles[firsta+article_id];
	 if (!(ah->flag & A_CLOSED) ||
	     (ah->flag & (A_ROOT_ART | A_NEXT_SAME)) == A_ROOT_ART)
	     goto Prompt;

	 cura = article_id = root_article(firsta + article_id) - firsta;
	 while (cura + firsta < n_articles) {
	     ah = articles[firsta+cura];
	     if (cura > article_id && ah->flag & A_ROOT_ART)
		 break;
	     ah->flag &= ~A_CLOSED;
	     cura++;
	 }
	 cura = article_id;
	 goto partial_redraw;
	 
      case K_CLOSE_SUBJECT:
	 if (numa < 0) goto nextmenu;
	 prompt("\1Close subject\1");

	 k_cmd = get_k_cmd();
	 if (k_cmd != K_ARTICLE_ID) {
	     if (k_cmd != cur_k_cmd) goto Prompt;
	     article_id = cura;
	 }

	 ah = articles[firsta+article_id];
	 if ((ah->flag & A_CLOSED) ||
	     (ah->flag & (A_ROOT_ART | A_NEXT_SAME)) == A_ROOT_ART) {
	     cura = next_root_article(firsta + cura) - firsta;
	     goto Prompt;
	 }

	 cura = article_id = root_article(firsta + article_id) - firsta;
	 while (cura + firsta < n_articles) {
	     ah = articles[firsta+cura];
	     if (cura > article_id && ah->flag & A_ROOT_ART)
		 break;
	     ah->flag |= A_CLOSED;
	     cura++;
	 }
	 cura = article_id;
	 next_cura = next_root_article(firsta + cura) - firsta;
	 if (next_cura < 0 || next_cura > numa) next_cura = 0;
	 if (cura >= 0) goto partial_redraw_nc;
	 articles[cura + firsta]->menu_line = articles[firsta]->menu_line;
	 firsta += cura;
	 goto redraw;

      case K_LEAVE_NEXT:
      case K_JUNK_ARTICLES:
	 junk_prompt = cur_k_cmd == K_JUNK_ARTICLES ? 1 : 5;

	 for (;;) {
	     switch (junk_prompt) {
	      case 1:
		 if (novice) msg("Use J repeatedly to select other functions");
		 prompt("\1Mark read\1 S)een U)nmarked A)ll *+)selected a-z . [LN]");
		 junk_attr = A_READ;
		 break;
	      case 2:
		 prompt("\1Unmark\1 S)een R)ead a-z [*+LAN.J] ");
		 junk_attr = 0;
		 break;
	      case 3:
		 prompt("\1Select\1 L)eft-over, N(leave-next) [USRa-z.J]");
		 junk_attr = A_SELECT;
		 break;
	      case 4:
		 prompt("\1Kill\1 R)ead S)een [LANU*+a-z.J]");
		 junk_attr = A_KILL;
		 break;
	      case 5:
		 prompt("\1Leave\1 a-z .,/ * + U)nmarked [LANRSJ]");
		 junk_attr = A_LEAVE_NEXT;
		 break;
	      default:
		 junk_prompt = 1;
		 continue;
	     }

	  junk_another:
	     if (cura < 0 || cura > numa) cura = 0;
	     cursor_at_id();

	     switch (get_k_cmd()) {
	      case K_JUNK_ARTICLES:
		 junk_prompt++;	/* can be 0 */
		 continue;

	      case K_ARTICLE_ID:
		 cura = article_id;
		 if (junk_attr == A_KILL) junk_attr = A_READ;
		 if (auto_select_closed > 0 && articles[firsta + cura]->flag & A_CLOSED)
		     repl_attr_subject(A_KILL, junk_attr, 1);
		 else {
		     articles[firsta + cura]->attr = junk_attr;
		     mark();
		     cura++;
		 }
		 goto junk_another;

	      case K_NEXT_LINE:
		 cura++;
		 goto junk_another;
		 
	      case K_PREV_LINE:
		 --cura;
		 goto junk_another;

	      case K_SELECT_SUBJECT:
		 if (junk_attr == A_KILL) junk_attr = A_READ;
		 repl_attr(firsta, nexta, A_AUTO_SELECT, A_SELECT, 0);
		 repl_attr(firsta, nexta, A_SELECT, junk_attr, 1);
		 goto Prompt;

	      case K_AUTO_SELECT:
		 repl_attr_all(A_AUTO_SELECT, A_SELECT, 0);
		 orig_attr = A_SELECT;
		 break;

	      default:
		 switch (cur_key) {
		  case 'S':
		     orig_attr = A_SEEN;
		     break;

		  case 'U':
		     orig_attr = 0;
		     break;

		  case 'L':
		     if (junk_attr == A_KILL) junk_attr = A_READ;
		     orig_attr = A_LEAVE;
		     break;

		  case 'A':
		     orig_attr = A_KILL;
		     break;

		  case 'N':
		     orig_attr = A_LEAVE_NEXT;
		     break;

		  case 'R':		/* kill read articles */
		     orig_attr = A_READ;
		     break;

		  default:
		     goto Prompt;
		 }
		 break;
	     }
	     break;
	 }
	 if (nexta - firsta < n_articles) {
	     prompt("On all menu pages? ");
	     switch (yes(1)) {
	      case -1:
		 goto Prompt;
	      case 0:
		 if (!repl_attr(firsta, nexta, orig_attr, junk_attr, 1))
		     goto Prompt;
		 break;
	      case 1:
		 if (!repl_attr_all(orig_attr, junk_attr, 1))
		     goto Prompt;
		 break;
	     }
	 } else
	     if (!repl_attr(firsta, nexta, orig_attr, junk_attr, 1))
		 goto Prompt;

	 if (junk_attr != A_KILL) goto Prompt;

      junk_killed_articles:
	 elim_list[0] = firsta;
	 elim_list[1] = firsta + cura;
	 elim_list[2] = nexta;
	 if (elim_articles(elim_list, 3)) {
	     firsta = elim_list[0];
	     goto redraw;
	 }
	 firsta = elim_list[0];
	 cura   = elim_list[1] - firsta;
	 nexta  = elim_list[2];
	 goto Prompt;

      case K_ARTICLE_ID:
	 if (numa < 0) goto nextmenu;

	 MOUSE_MENU;
	     
	 if (!is_k_select && auto_preview_mode) goto auto_preview;

	 cura = article_id;
	 if (!auto_select_closed || !(articles[firsta+cura]->flag & A_CLOSED)) {
	     toggle();
	     if (!is_k_select && auto_select_subject) goto select_subject;
	     mark();
	     cura++;
	     goto same_prompt;
	 }
	 
	 if (auto_select_closed < 0) {
	     article_id = cura;
	     goto open_subject;
	 }

	 mi = menu_info + articles[firsta+cura]->menu_line;
	 if (mi->mi_unread == 0)
	     repl_attr_subject(A_KILL, A_SELECT, 1);
	 else if (mi->mi_selected == mi->mi_unread)
	     repl_attr_subject(auto_select_closed == 2 ? A_KILL : A_SELECT, 0, 1);
	 else
	     repl_attr_subject(auto_select_closed == 2 ? A_KILL : 0, A_SELECT, 1);
	 goto same_prompt;

      case K_SELECT_INVERT:
	 if (numa < 0) goto nextmenu;

	 temp = cura;

	 no_raw();	/* for x-on/x-off */
	 for (cura = 0; cura <= numa; cura++) {
	     toggle();
	 }
	 for (cura = 0; cura <= numa; cura++) {
	     if (IS_VISIBLE(articles[firsta+cura])) mark();
	 }
	 fl;

	 REDRAW_CHECK;
	 nn_raw();

	 cura = temp;
	 goto same_prompt;

      case K_UNSELECT_ALL:
	 if (last_k_cmd == K_UNSELECT_ALL)
	     repl_attr_all(A_SELECT, 0, 1);
	 else
	     repl_attr_all(A_AUTO_SELECT, 0, 1);
	 fl;
	 cura = 0;
	 goto same_prompt;

      case K_NEXT_LINE:
	 if (numa < 0) goto nextmenu;

	 cura++;
	 goto same_prompt;

      case K_PREV_LINE:
	 if (numa < 0) goto nextmenu;

	 if (--cura < 0) cura = numa;
	 set_root_if_closed();
	 goto same_prompt;

      case K_SELECT_SUBJECT:
	 if (numa < 0) goto nextmenu;

	 MOUSE_MENU;

	 if (MOUSE)
	      cura = article_id;

	 if (last_k_cmd != K_ARTICLE_ID)
	     toggle();

      select_subject:
	 repl_attr_subject(A_KILL, last_attr, 1);
	 goto same_prompt;

      case K_SELECT_RANGE:
	 if (numa < 0) goto nextmenu;

	 if (last_k_cmd == K_ARTICLE_ID) {
	     cura--;
	     if (cura < 0) cura = numa;
	 } else
	     last_attr = (articles[firsta+cura]->attr & A_SELECT) ? 0 : A_SELECT;

      range_again:

	 if (MOUSE) {
             if ((mouse_y < firstl) || (mouse_y > firstl + menu_length - 1))  
		goto same_prompt;
	     /* stop selection when down click changes groups */
	     if (last_k_cmd == K_INVALID) 
		goto same_prompt;
	 } else {
	     prompt("\1%select range\1 %c-", last_attr ? "S" : "Des", ident[cura]);

	     k_cmd = get_k_cmd();
	     if (k_cmd == K_SELECT_RANGE) {
	         last_attr = last_attr ? 0 : A_SELECT;
	         goto range_again;
	     }

	     if (k_cmd != K_ARTICLE_ID) goto Prompt;
	 
	 }

	 if (article_id > cura) {
	     article_number tmp = cura;
	     cura  = article_id;
	     article_id = tmp;
	 }

	 repl_attr(firsta+article_id, firsta+cura+1, A_KILL, last_attr, 1);
	 goto Prompt;

      case K_AUTO_SELECT:
	 do_auto_select((regexp *)NULL, 1);
	 goto same_prompt;

     case K_GOTO_MATCH:
	 prompt("\1Select regexp\1 ");
	 if ((fname = get_s(NONE, NONE, NONE, NULL_FCT)) == NULL)
	     goto Prompt;

	 if (*fname != NUL) {
	     if (regular_expr) freeobj(regular_expr);
	     if (case_fold_search) fold_string(fname);
	     regular_expr = regcomp(fname);
	 }

	 if (regular_expr == NULL)
	     msg("No previous expression");
	 else
	     do_auto_select(regular_expr, 2);

	 goto Prompt;

      case K_NEXT_PAGE:
	 if (nexta < n_articles) goto nextmenu;
	 if (firsta == 0) goto same_prompt;

	 nexta = 0;
	 goto nextmenu;

      case K_PREV_PAGE:
	 if (firsta == 0 && nexta == n_articles) goto same_prompt;

      prevmenu:
	 nexta = (firsta > 0 ? firsta : n_articles);
	 
	 for (menu_length = maxa; menu_length > 0 && --nexta >= 0; ) {
	    if (!IS_VISIBLE(articles[nexta])) continue;
	    if (--menu_length > 0) {
		switch (menu_spacing) {
		 case 0:
		    break;
		 case 1:
		    if ((articles[nexta]->flag & A_ROOT_ART) == 0) break;
		    /* XXX: fall? */
		 case 2:
		    --menu_length;
		    break;
		}
	    }
	 }
	 if (nexta < 0) nexta = 0;
	 goto nextmenu;

      case K_FIRST_PAGE:
	 if (firsta == 0) goto same_prompt;

	 nexta = 0;
	 goto nextmenu;

      case K_LAST_PAGE:
	 if (nexta == n_articles) goto same_prompt;
	 firsta = 0;
	 goto prevmenu;

      case K_PREVIEW:
	 if (numa < 0) goto nextmenu;

      preview_other:

	 MOUSE_MENU;
	 if (! MOUSE) {
	     prompt("\1Preview article\1");
	     k_cmd = get_k_cmd();

	 if (k_cmd != K_ARTICLE_ID) {
	     if (k_cmd != K_PREVIEW)
	         goto Prompt;
	     article_id = cura;
	     }
        }

      auto_preview:
	 temp = prompt_line;

      preview_next:
	 cura = article_id;
	 ah = articles[firsta+cura];

	 no_raw();
	 orig_attr = ah->attr;
	 ah->attr = 0;
	 menu_cmd = more(ah, MM_PREVIEW, prompt_line);
	 if (menu_cmd == MC_MENU) {
	     if (ah->attr == 0) ah->attr = orig_attr;
	     if (prompt_line < 0) {
		next_cura = cura;
		goto redraw;
	     }
	     mark();
	     prompt_line = temp;
	     next_cura = -1;
	     goto build_prompt;
	 }

	 if (ah->attr == 0)
	     ah->attr = preview_mark_read ? A_READ : orig_attr;

	 if (prompt_line >= 0)
	     mark();
	 next_cura = ++cura;

	 switch (menu_cmd) {

	  case MC_DO_KILL:
	     if (!do_auto_kill()) break;
	     elim_list[0] = firsta;
	     elim_list[1] = firsta + cura;
	     elim_articles(elim_list, 2);
	     firsta = elim_list[0];
	     next_cura = elim_list[1] - firsta;
	     goto redraw;

	  case MC_DO_SELECT:
	     if (prompt_line >= 0) { /* not redrawn */
		 do_auto_select((regexp *)NULL, 2);
		 break;
	     }
	     numa = -1;
	     do_auto_select((regexp *)NULL, 2);

	     /* FALL THRU */
	  case MC_QUIT:
	     menu_return( ME_QUIT );
	     break;	/* for lint. we can't actually get here */

	  case MC_REENTER_GROUP:
	     menu_return( ME_REENTER_GROUP );
	     break;	/* for lint, we can't actually get here */

	  case MC_NEXT:
	  case MC_PREVIEW_NEXT:
	     if (prompt_line < 0) {	/* redrawn screen ! */
		 if (quit_preview(menu_cmd)) goto redraw;
		 prompt_line = Lines;
	     } else {
/*		 if (ah->attr == A_LEAVE || ah->attr == A_LEAVE_NEXT) {
		     cura--;
		     mark();
		     cura++;
		 }
*/		 if (quit_preview(menu_cmd)) {
		     next_cura = -1;
		     break;
		 }
		 prompt_line = temp;
	     }
	     article_id = cura;
	     goto preview_next;

	  case MC_PREVIEW_OTHER:
	     prompt_line = temp;
	     nn_raw();
	     goto preview_other;

	  default:
	     if (prompt_line < 0) goto redraw;
	     break;
	 }

	 prompt_line = temp;
	 goto build_prompt;

      case K_LAYOUT:
	 if (++fmt_linenum > 4) fmt_linenum = 0;
	 goto redraw;

      default:
	 msg("Command %d not supported", k_cmd);
	 goto same_prompt;
     }

    no_raw();

 do_auto_read:
    switch (show_articles()) {

     case MC_MENU:
	goto redraw;

     case MC_READGROUP:
	if (k_cmd == K_READ_GROUP_THEN_SAME) {
	    if (read_ret_next_page && nexta < n_articles)
		firsta = nexta;
	    goto redraw;
	}
	/* XXX: fall? */
     case MC_NEXTGROUP:
	menu_cmd = ME_NEXT;
	break;

     case MC_REENTER_GROUP:
	menu_cmd = ME_REENTER_GROUP;
	break;

     case MC_QUIT:
	menu_cmd = ME_QUIT;
	break;

     default:
	sys_error("show_articles returned improper value");
    }

 menu_exit:

    cur_bypass = o_bypass;
    n_selected = o_selected;
    firsta = o_firsta;
    in_menu_mode = o_mode;
    menu_level--;

    no_raw();
    return menu_cmd;
}


/*
 *	return article header for article on menu
 */

article_header *get_menu_article()
{
    register article_header *ah;

    tprintf(" from article: "); fl;

    if (get_k_cmd() == K_ARTICLE_ID) {
	ah = articles[firsta + article_id];
	if (ah->a_group) init_group(ah->a_group);
	return ah;
    }

    return NULL;
}



/*
 *	read command from command line
 */

int
alt_command()
{
    int ok_val, macro_cmd;
    char *cmd, brkchars[10];

    if (get_from_macro)
	ok_val = AC_UNCHANGED;
    else {
	prompt(":");
	ok_val = AC_PROMPT;
    }

 again:

    sprintf(brkchars, "?%c ", erase_key);

    cmd = get_s(NONE, NONE, brkchars, alt_completion);
    if (cmd == NULL ||
	*cmd == NUL || *cmd == SP || (key_type)*cmd == erase_key)
	return ok_val;

    macro_cmd = get_from_macro;

    if (*cmd == '?') {
	display_file("help.extended", CLEAR_DISPLAY);
	ok_val = AC_REDRAW;
	goto new_prompt;
    }

    ok_val = parse_command(cmd, ok_val, (FILE *)NULL);
    if (ok_val != AC_REDRAW || !delay_redraw) return ok_val;

 new_prompt:
    if (macro_cmd) return ok_val;

    prompt_line = -1;
    tprintf("\n\r:");
    fl;
    goto again;
}
