[alt.sources] lq-text Full Text Retrieval Database Part 09/13

lee@sq.sq.com (Liam R. E. Quin) (03/04/91)

: cut here --- cut here --
: To unbundle, sh this file
#! /bin/sh
: part 09
echo x - lq-text/src/menu/menu.c 1>&2
sed 's/^X//' >lq-text/src/menu/menu.c <<'@@@End of lq-text/src/menu/menu.c'
X/* menu.c -- curses based windowing interface
X * Liam Quin, July 1989
X *
X * $Id: menu.c,v 1.3 90/10/13 03:06:36 lee Rel1-10 $
X *
X * $Log:	menu.c,v $
X * Revision 1.3  90/10/13  03:06:36  lee
X * After Sabre.
X * 
X * Revision 1.2  90/10/04  16:28:19  lee
X * SysV compat improved.
X * 
X * Revision 1.1  90/08/29  21:50:42  lee
X * Initial revision
X * 
X * Revision 2.1  89/08/07  13:50:09  lee
X * First fully working (V.3.2 only) release;
X * this is the baseline for future development.
X * 
X * Revision 1.4  89/08/04  17:58:31  lee
X * Fully working with Basic Functionality.
X * Scrolling menubar, scrolling menus, moveable Info windows.
X * 
X * Revision 1.3  89/08/02  17:12:21  lee
X * gernally working.
X * You can scroll up/down in menus.
X * 
X * Revision 1.2  89/07/28  18:20:17  lee
X * Pull-down menus now scroll if the items don't all fit on the screen.
X * The info-boxes pop up nearer to the highlighted item.
X * Help on menu-bar items.
X * In infoshow(), arrow keys return, so you can move from one item to
X * another and have the info boxes displayed foe each.
X * 
X * Revision 1.1  89/07/27  11:38:23  lee
X * Initial revision
X * 
X * Revision 1.1  89/07/24  18:34:43  lee
X * Initial revision
X * 
X *
X */
X
X#ifndef lint
Xstatic char rcsid[] = "@(#) $Id: menu.c,v 1.3 90/10/13 03:06:36 lee Rel1-10 $";
X#endif
X
X#ifdef ultrix
X# include <cursesX.h>
X#else
X# include <curses.h>
X#endif
X#include <malloc.h>
X
X#ifndef A_STANDOUT
X# include "oldcurses.h"
X#endif
X
X#include "menu.h"
X#include "internal.h"
X#include "error.h"
X
Xvoid SetStringBoxSize();
X
X/* Some Shorthand */
X#define BL (MENUTOP - 1)
X/* VSO is Vertical Scroll Offset... */
X#define VSO (Menu->TopLineOnScreen)
X
X
X/* Appearance of menus:
X * Each menu is boxed with the terminal line drawing characters.  It will
X * use '|' if you don't have VLINE; ACS_SSBS looks like the upper-case T
X * that will join the HLINE of the Menu Line to the menu box itself.
X * See /usr/include/curses.h for an explanation of these names.  On pre-
X * System V, curses often uses + - and | to draw lines, which is ugly but
X * doeas at least work.  It might be possible to define variables with
X * the appropriate names (ACS_VLINE, etc), and to put appropriate things
X * into termcap.  Uniplex used to do this, for example.
X *
X * If not compiled with -DSAVEMENULINE, to save a line on the screen,
X * the first line of each menu over-writes the Menu line, so you get:
X * File  Edit  Method  Position  Adults  Children  Animals
X * -------------------------------------+ Daniel  +-----------------------
X *					| Simon   |
X *					| Lorella |
X *					| Carol   |
X *					+---------+
X * Otherwise, you get this:
X * File  Edit  Method  Position  Adults  Children  Animals
X * -------------------------------------+---------+-----------------------
X *					| Daniel  |
X *					| Simon   |
X *					| Lorella |
X *					| Carol   |
X *					+---------+
X * which might look neater, but uses an extra line on the screen.
X * It saves the menu line on each redraw, hence SAVEMENULINE.
X *
X * Most of this is wired into the Show* routines -- ShowMenuBar() and
X * ShowMenu().
X */
X
Xstatic int DisplayedMenu = 0;
Xstatic int NextMenuId = 1;
X
Xt_Menu *
XNewMenu(Name)
X    char *Name;
X{
X    static int MCount = 0;
X
X    t_Menu *M = (t_Menu *) malloc(sizeof(t_Menu));
X
X    if (!M) {
X	error(ERR_FATAL, "Not enough memory for new menu");
X    }
X    M->MenuStyle = 0; /* default */
X    M->TopLineOnScreen = 0;
X    M->Width = -1; /*i.e. not calculated yet */
X    M->NameLength = strlen(Name);
X    M->Name = malloc(M->NameLength + 1);
X    if (!M->Name) {
X	error(ERR_FATAL, "Not enough memory for new menu name");
X    }
X    (void) strcpy(M->Name, Name);
X    M->SelectedLine = 0;
X    M->MenuId = MCount++;
X
X    return M;
X}
X
XShowMenuBar(MenuBar)
X    t_MenuBar *MenuBar;
X{
X    /* draw a menu bar... */
X    int barpos = 0;
X    int i;
X    int SelectedLength;
X    int EndOfMenu = 0;
X
X    /* Clear the top two lines. */
X    (void) wmove(stdscr, 0, 0);
X    (void) wclrtoeol(stdscr);
X    (void) wmove(stdscr, 1, 0);
X    (void) wclrtoeol(stdscr);
X    /* draw the menu line */
X    for (i = 0; i < COLS - 1; i++) {
X	(void) mvwaddch(stdscr, 1, i, (chtype) ACS_HLINE);
X    }
X
X    /* On really slow terminals it might be worth doing a refresh()
X     * at this point, so the clear can happen at the same time as we
X     * are working out the size of the menu bar, etc.
X     */
X
X#define HSO (MenuBar->ScrollOffset) /* Horiz. scroll offset */
X
X    /** Horizontal scrolling of long menus... **/
X
X    /* Check for sanity! */
X    if (MenuBar->ScrollOffset < 0) {
X	MenuBar->ScrollOffset = 0;
X    }
X
X    /* Find where to start displaying...
X     * keep adding the menu widths until the selected menu is on screen
X     */
X
X    /* First, work out the widths.  It might be worth pre-computing
X     * these strlen()s....
X     */
X    for (i = 0; i < MenuBar->HowManyMenus; i++) {
X	barpos = (EndOfMenu += 2);
X	MenuBar->Menus[i]->PositionInBar = barpos;
X	if (MenuBar->Menus[i]->NameLength <= 0) {
X	    MenuBar->Menus[i]->NameLength = strlen(MenuBar->Menus[i]->Name);
X	}
X	EndOfMenu += MenuBar->Menus[i]->NameLength;
X    }
X    /* Now, we can work out how to get SelectedMenu on screen! */
X    /* First, check we're in range: */
X    if (MenuBar->SelectedMenu == 0) {
X	MenuBar->ScrollOffset = 0;
X    }
X    if (MenuBar->ScrollOffset > EndOfMenu) {
X	MenuBar->ScrollOffset = EndOfMenu;
X    }
X    /* Now, check that SelectedMenu and scrolloffset both fit on the screen
X     */
X    barpos = MenuBar->Menus[MenuBar->SelectedMenu]->PositionInBar;
X    if (MenuBar->ScrollOffset > barpos) {
X	MenuBar->ScrollOffset = barpos - 2;
X    }
X    /* 2      9           20         28 ........... barpos = 28ish
X     * File |<Implement   Severity  *Duration*  Location   |
X     *|<--->|ScrollOffset ............. MenuBar->ScrollOffset = 5ish
X     */
X    /* The +6 is
X     * 2 for the extreme left
X     * 2 for the extreme righht
X     * 2 spare, so you can see the menu
X     */
X    SelectedLength = MenuBar->Menus[MenuBar->SelectedMenu]->NameLength;
X
X    if (barpos - MenuBar->ScrollOffset >= COLS - SelectedLength) {
X	/* it's off the right-hand edge of the screen.  The +2
X	 * is 1 for a space at the end of the name, leaving room for
X	 * a '>' if the bar continues, and one for a space to leave room
X	 * for the right edge of the menu bar if it gets pulled down. */
X	MenuBar->ScrollOffset = barpos - (COLS - SelectedLength - 2);
X    }
X    /* Horizontal scrolling of long menus... */
X    if (MenuBar->ScrollOffset < 0) {
X	MenuBar->ScrollOffset = 0;
X    }
X
X    /* Now the menu bar itself */
X
X    EndOfMenu = barpos = 0;
X
X    for (i = 0; i < MenuBar->HowManyMenus; i++) {
X	register char *q;
X	
X	/* leave 2 spaces before each menu so the box lines up
X	 * when it's selected.
X	 */
X	EndOfMenu += 2;
X
X	/* Draw the spaces -- EndOfMenu represents the end of the
X	 * menubar, in characters, ignoring HSO --- so, it can be
X	 * bigger than COLS.
X	 */
X	while (barpos < EndOfMenu) {
X	    if (barpos >= HSO && barpos < HSO + COLS) {
X		(void) mvwaddch(stdscr, 0, barpos - HSO, (chtype) ' ');
X	    }
X	    ++barpos;
X	}
X
X	/* This is really for ShowMenu() */
X	MenuBar->Menus[i]->PositionInBar = barpos - 2 - HSO;
X
X
X	if (i == MenuBar->SelectedMenu) {
X	    (void) attrset(A_STANDOUT);
X	}
X
X	for (q = MenuBar->Menus[i]->Name; *q; q++) {
X	    /* Only write characters onto the screen in the range
X	     * HSO ... HSO + COLS
X	     */
X	    if (barpos >= HSO && barpos < HSO + COLS) {
X		(void) mvwaddch(stdscr,  0, barpos - HSO, (chtype) *q);
X	    }
X	    barpos++;
X	}
X
X	if (i == MenuBar->SelectedMenu) {
X	    (void) attrset(0);
X	}
X
X	if (barpos > COLS + HSO) {
X	    break; /* we can give up now! */
X	}
X
X	EndOfMenu = barpos;
X    }
X
X    if (barpos == 0) {
X	error(ERR_FATAL, "UseMenuBar: The Menus must have names");
X    }
X
X    if (MenuBar->ScrollOffset > 0) {
X	(void) mvwaddch(stdscr, 0, 0, ACS_LARROW); /* left arrow */
X    }
X
X    if (i < MenuBar->HowManyMenus) {
X	/* not all the menuse were shown */
X	(void) mvwaddch(stdscr, 0, COLS - 1, ACS_RARROW); /* right arrow */
X    }
X}
X
Xint
XUseMenuBar(MenuBar)
X    t_MenuBar *MenuBar;
X{
X    int SelectedChar;
X    extern int MenuUsed;
X    int InfoBoxOnScreen = 0;
X
X    MenuBar->ScrollOffset = 0;
X    MenuBar->SelectedMenu = 0;
X
X    do {
X	int mval;
X	int InMenu;
X
X	InMenu = 0;
X
X	ShowMenuBar(MenuBar);
X	(void) refresh();
X	SelectedChar = getch();
X
X	switch (SelectedChar) {
X	case 'q':
X	case 'f':
X	case 'Q':
X	case 'F':
X	    return -2;
Xinfo_key:
X	case KEY_HELP:
X	case '?':
X	case 'x': /* "eXplain" */
X	case 'X': /* "eXplain" */
X	case 'i': /* "info" */
X	case 'I': /* "Info" */
X	    {
X		char *p = MenuBar->Menus[MenuBar->SelectedMenu]->Description;
X		int tlx = MenuBar->Menus[MenuBar->SelectedMenu]->PositionInBar;
X		tlx += 4; /* a little to the right.. */
X		if (!p) {
X		    p = "No information available about this menu.";
X		}
X		SelectedChar = ShowInfo(p, (WINDOW *) 0, tlx, 2);
X		(void) touchwin(stdscr);
X		(void) wnoutrefresh(stdscr);
X		InfoBoxOnScreen = (SelectedChar != ' ');
X		if (SelectedChar == 0 || SelectedChar == ' ') {
X		    continue;
X		}
X		break;
X	    }
X	}
X	/* The character was changed if ShowInfo was called... 
X	 * The next switch() deals with selecting a menu, and also
X	 * canonicalises the input for the subsequent switch().
X	 */
X	switch (SelectedChar) {
X	case ' ':
X	    /* ShowInfo() returns ' 'to disable the info box. */
X	    if (InfoBoxOnScreen) {
X		InfoBoxOnScreen = 0;
X		break;
X	    }
X	case '\r':
X	case '\n':
X	case 'j':
X	case KEY_DOWN:
Xfire_menu:
X	    if (InfoBoxOnScreen) {
X		InfoBoxOnScreen = 0;
X	    }
X	    InMenu = 1;
X	    mval = UseMenu(MenuBar->Menus[MenuBar->SelectedMenu]);
X	    /* clear(); */
X	    if (mval > MenuBar->Menus[MenuBar->SelectedMenu]->HowManyItems ||
X		mval < 0) {
X		SelectedChar = mval;
X		if (mval == -2) {
X		    SelectedChar = '\004';
X		    continue;
X		}
X	    } else {
X		/* If we're supposed to print the final selection on
X		 * exit, we need to know which menu was selected ---
X		 * see the end of the example main().
X		 */
X		MenuUsed = MenuBar->SelectedMenu;
X
X		if (mval >= 0 && MenuBar->Menus[MenuUsed]->Items[mval].Function != 0) {
X		    return (* MenuBar->Menus[MenuUsed]->Items[mval].Function)(MenuBar->Menus[MenuUsed], mval);
X		} else {
X		    /* No function to call */
X		    return mval;
X		}
X	    }
X	    break;
X	case 'h':
X	    SelectedChar = KEY_LEFT;
X	    break;
X	case 'l': /* lower case ell */
X	    SelectedChar = KEY_RIGHT;
X	    break;
X	}
X	/* The submenu might have been left with KEY_LEFT (say), so
X	 * we check again here.
X	 */
X	switch (SelectedChar) {
X	case KEY_HOME:
X	    MenuBar->ScrollOffset = 0;
X	    MenuBar->SelectedMenu = 0;
X	    break;
X	case '$':
X	    MenuBar->SelectedMenu = MenuBar->HowManyMenus - 1;
X	    break;
X	case KEY_LEFT:
X	    if (MenuBar->SelectedMenu > 0) {
X		(MenuBar->SelectedMenu)--;
X		ShowMenuBar(MenuBar);
X	    } else {
X		(void) beep();
X	    }
X	    break;
X	case KEY_RIGHT:
X	    if (MenuBar->SelectedMenu + 1 < MenuBar->HowManyMenus) {
X		MenuBar->SelectedMenu++;
X		ShowMenuBar(MenuBar);
X	    } else {
X		(void) beep();
X	    }
X	    break;
X	case 'R' ^ 64: /* control-R */
X	    (void) clearok(stdscr, TRUE);
X	    (void) refresh();
X	    break;
X	case ' ':
X	case '\004': /* used internally */
X	    break;
X	default:
X	    (void) beep();
X	}
X	if (InfoBoxOnScreen) {
X	    goto info_key; /* sorry */
X	}
X	if (InMenu) {
X	    /* clear(); */
X	    ShowMenuBar(MenuBar);
X	    (void) refresh();
X	    goto fire_menu;
X	}
X    } while (SelectedChar != EOF && SelectedChar != 'q');
X    /*NOTREACHED*/
X    error(ERR_FATAL|ERR_INTERNAL, "/*NOTREACHED*/ in UseMenuBar()");
X    /*NOTREACHED*/
X    return -1;
X}
X
X/* UseMenu() -- display a menu, allow the user to select from it,
X * then undisplay it and redraw the screen
X */
Xint
XUseMenu(Menu)
X    t_Menu *Menu;
X{
X    WINDOW *MenuWin;
X    int tlx, bly; /* top left x/bot. right y of menu box */
X    static int InfoBoxOnScreen = 0;
X
X    if (InfoBoxOnScreen) {
X	InfoBoxOnScreen++; /* force the box to be redrawn */
X    }
X    /* Ensure that the menu has a MenuId --
X     * This is the only module which knows about MenuId...
X     */
X    if (Menu->MenuId <= 0) { /* a new menu */
X	Menu->MenuId = NextMenuId++;
X    }
X    /* If the menu is not currently displayed */
X    if (DisplayedMenu <= 0) {
X	DisplayedMenu = -1;
X    }
X    if (Menu->SelectedLine < 0) {
X	Menu->SelectedLine = 0;
X	Menu->TopLineOnScreen = 0;
X    }
X    tlx = Menu->PositionInBar;
X    if ((bly = Menu->HowManyItems + 2) > LINES - 2) {
X	/* -2 -- one for the menu bar, one because top line is 0 */
X	bly = LINES - 2;
X    }
X
X    /* Ensure that the width has been calculated */
X    if (Menu->Width <= 0) {
X	(void) FindMenuWidth(Menu);
X    }
X
X    /* We leave 1 space either side, + 1 for the box */
X    if ((MenuWin = newwin(bly, Menu->Width + 4, 1, tlx)) == (WINDOW *) 0) {
X	(void) beep();
X	error(ERR_FATAL|ERR_INTERNAL, "No more memory");
X    }
X
X    /* spl6(); */
X    DisplayedMenu = Menu->MenuId;
X    /* spl0(); */
X
X    for (;;) {
X	int ch;
X
X	ShowMenu(Menu, MenuWin);
X	(void) wrefresh(MenuWin);
X
X	ch = (InfoBoxOnScreen > 1) ? KEY_HELP : getch();
X
X	if (InfoBoxOnScreen > 1) InfoBoxOnScreen = 1;
X
X	switch (ch) {
X	    char *p;
X
X	case 'q':
X	case 'Q': /* return immediately */
X	case 'f':
X	case 'F': /* Finish */
X	    InfoBoxOnScreen = 0;
X	    (void) delwin(MenuWin);
X	    (void) touchwin(stdscr);
X	    return -2;
X	case '?': /* "huhn?" */
X	case 'x': /* "eXplain" */
X	case 'X': /* "eXplain" */
X	case 'i': /* "info" */
X	case 'I': /* "info" */
X	case KEY_HELP:
Xkey_info:
X	    p = Menu->Items[Menu->SelectedLine].Description;
X
X	    if (!p) {
X		p = "No information available for this selection.";
X	    }
X
X	    ch = ShowInfo(p, MenuWin, tlx + 4, Menu->SelectedLine + 3 - VSO);
X
X	    (void) touchwin(stdscr);
X	    (void) wnoutrefresh(stdscr);
X	    (void) touchwin(MenuWin);
X	    (void) wrefresh(MenuWin);
X	    /* ShowInfo returns SPACE if the user wanted to get
X	     * rid of the box; otherwise, it returns the appropriate
X	     * key.
X	     * The box went away anyway, but we might put another
X	     * one there.
X	     */
X	    InfoBoxOnScreen = (ch != ' ');
X
X	    if (ch == 0 || ch == ' ') continue;
X	    break;
X	}
X
X	/* So either we are looking at the original character,
X	 * or the user pressed the Info key...
X	 */
X
X	switch (ch) {
X	case ' ':
X	case '\r':
X	case '\n':
X	    (void) delwin(MenuWin);
X	    (void) touchwin(stdscr);
X	    return Menu->SelectedLine;
X	case KEY_HOME:
X	    if (Menu->SelectedLine > 0) {
X		Menu->SelectedLine = 0;
X		if (InfoBoxOnScreen) {
X		    ShowMenu(Menu, MenuWin);
X		    (void) wrefresh(MenuWin);
X		    goto key_info; /* sorry */
X		}
X	    } else {
X		(void) delwin(MenuWin);
X		(void) touchwin(stdscr);
X		return KEY_HOME;
X	    }
X	case 'k':
X	case KEY_UP:
X	    if (Menu->SelectedLine > 0) {
X		Menu->SelectedLine--;
X	    } else {
X		InfoBoxOnScreen = 0;
X		(void) delwin(MenuWin);
X		(void) touchwin(stdscr);
X		return -2;
X	    }
X	    if (InfoBoxOnScreen) {
X		ShowMenu(Menu, MenuWin);
X		(void) wrefresh(MenuWin);
X		goto key_info; /* sorry */
X	    }
X	    break;
X	case 'j':
X	case KEY_DOWN:
X	    if (Menu->SelectedLine + 1 < Menu->HowManyItems) {
X		Menu->SelectedLine++;
X		if (Menu->SelectedLine - Menu->TopLineOnScreen >= COLS - 4) {
X		    Menu->TopLineOnScreen++;
X		}
X	    } else {
X		(void) beep();
X	    }
X	    if (InfoBoxOnScreen) {
X		ShowMenu(Menu, MenuWin);
X		(void) wrefresh(MenuWin);
X		goto key_info; /* sorry */
X	    }
X	    break;
X	case '$': /* last item */
X	    Menu->SelectedLine = Menu->HowManyItems - 1;
X	    break;
X	case 'h':
X	case KEY_LEFT:
X	    (void) delwin(MenuWin);
X	    (void) touchwin(stdscr);
X	    return KEY_LEFT;
X	case 'l':
X	case KEY_RIGHT:
X	    (void) delwin(MenuWin);
X	    (void) touchwin(stdscr);
X	    return KEY_RIGHT;
X	default:
X	    (void) beep();
X	    break;
X	}
X    }
X}
X
X/* FindMenuWidth() returns the length of the longest item in the menu.
X */
Xint
XFindMenuWidth(Menu)
X    t_Menu *Menu;
X{
X    int line;
X    if (Menu->Width <= 0) {
X	/* Include the width of the name of the menu.  I am not sure
X	 * whether this is right, but it looks a bit odd if the menu
X	 * box is narrower than the highlighted menu name on the Bar...
X	 */
X	if (Menu->NameLength <= 0) Menu->NameLength = strlen(Menu->Name);
X	Menu->Width = Menu->NameLength;
X
X	/* Now see if any of the items are wider... */
X	for (line = 0; line < Menu->HowManyItems; line++) {
X	    /* Individual length entries... */
X	    if (Menu->Items[line].NameLength == 0) {
X		Menu->Items[line].NameLength = strlen(Menu->Items[line].Name);
X	    }
X
X	    /* Overall widest... */
X	    if (Menu->Items[line].NameLength > Menu->Width) {
X		Menu->Width = Menu->Items[line].NameLength;
X	    }
X	}
X    }
X    return Menu->Width;
X}
X
XShowMenu(Menu, MenuWin)
X    t_Menu *Menu;
X    WINDOW *MenuWin;
X{
X    int line;
X    int pos = 0;
X    register int i;
X
X    /* It is possible to compile this code so that the menu bar-line is
X     * broken by the top item in a selected menu.  See the comments at
X     * the start of this file, and in menu.h
X     */
X#ifdef SAVEMENULINE
X    /* draw the box by hand, because the top line is a little special...
X     * SVR3V2 has a line drawing function mvwhline(), but it is very
X     * V.3.2 specific.  Sigh.
X     */
X    for (i = 1; i <= Menu->Width + 2; i++) {
X	(void) mvwaddch(MenuWin, 0, i, ACS_HLINE);
X    }
X
X    /* NOTE:
X     * the ACS_trbl characters are PC/vt100-style line characters,
X     * where the t,r,b and l can either be B for Blank or S for single;
X     * later versions of curses may also have D for double, etc.;
X     * hence a character with single lines joining left and top would
X     * be ACS_SBBS. with single lines.  If you have double lines, it
X     * might be good to use them for the menu bar, so as to give
X     *  Tools  Pain *Where* When  Whom
X     * ============+=======+=================
X     *		   | Hands |
X     *		   | Feet  |
X     *		   | etc.  |
X     *		   +-------+
X     */
X
X    /* T-pieces to join the menuline to the verticals of the menu */
X    (void) mvwaddch(MenuWin, 0, 0, ACS_BSSS);
X    /* The +3 in the next call to mvwaddch() comes from
X     * +2 for the box and space on the left
X     * +2 for the box and space on the right
X     * -1 because things start at 0
X     */
X    (void) mvwaddch(MenuWin, 0, Menu->Width + 3, ACS_BSSS);
X#else
X    /* top line gets overwritten... just add corners! */
X    (void) mvwaddch(MenuWin, 0, 0, ACS_BBSS);
X    /* See above for the +3 */
X    (void) mvwaddch(MenuWin, 0, Menu->Width + 3, ACS_BSSB);
X#endif /*SAVEMENULINE*/
X
X    if (Menu->TopLineOnScreen > Menu->HowManyItems) {
X	Menu->TopLineOnScreen = Menu->HowManyItems - (LINES - 3);
X	(void) touchwin(stdscr);
X	(void) wnoutrefresh(stdscr);
X    }
X
X    if (Menu->SelectedLine < Menu->TopLineOnScreen) {
X	Menu->TopLineOnScreen = Menu->SelectedLine;
X    } else if (Menu->SelectedLine - Menu->TopLineOnScreen > LINES - 4) {
X	Menu->TopLineOnScreen = Menu->SelectedLine - (LINES - 4);
X    }
X
X    /* Final sanity check */
X    if (Menu->TopLineOnScreen < 0) {
X	Menu->TopLineOnScreen = 0;
X    }
X
X    /* Now the lines of the menu, one at a time */
X    for (line = 0; line < LINES; line++) {
X	char *p;
X	int w;
X
X	if (line + VSO >= Menu->HowManyItems) break;
X
X	p = ((Menu->Items)[line + VSO]).Name;
X
X	/* A neat horizontal line on the left */
X	if (line+BL > 0) {
X	    (void) mvwaddch(MenuWin, line+BL, 0, ACS_VLINE);
X	}
X	(void) mvwaddch(MenuWin, line+BL, 1, (chtype) ' ');
X
X	/* line is {1..N}, Selected is {0..N-1}.  This simplifies
X	 * its use elsewhere, at the expense of making this routine
X	 * a little more complex.
X	 */
X	if (line + VSO == Menu->SelectedLine) {
X	    (void) wattrset(MenuWin, A_STANDOUT);
X	}
X
X	/* On terminals without standout mode, one could use stars:
X	 *   Tools  Pain *Where* When  Whom
X	 * =============+=======+=================
X	 *		| Hands |
X	 *		|*Feet**|
X	 *		| etc.  |
X	 *		+-------+
X	 * I wonder how I can tell if standout mode is working?
X	 */
X
X	/* Add the actual menu item */
X	(void) mvwaddstr(MenuWin, line+BL, 2, p);
X
X	/* check how long it was */
X	w = (Menu->Items)[line + VSO].NameLength;
X
X	/* Add spaces where to pad it out
X	 * These might be inverse video spaces.  Of course, not all
X	 * terminals make stand-out spaces different from
X	 * ordinary ones.  On really dumb ttys, could use '*' here
X	 * instead of ' '.
X	 */
X
X	for (i = w; i <= Menu->Width; i++) {
X	    (void) mvwaddch(MenuWin, line+BL, i + 2, (chtype)' ');
X	}
X
X	/* Turn bold/inverse off */
X	if (line + VSO == Menu->SelectedLine) {
X	    (void) wattrset(MenuWin, 0);
X	}
X
X	/* The extra space is really defense against magic cookies.
X	 * See terminfo/termcap manuals...
X	 */
X	(void) mvwaddch(MenuWin, line+BL, i + 1, (chtype) ' ');
X
X	/* And a neat vertical line on the right. */
X	if (line+BL > 0) {
X	    (void) mvwaddch(MenuWin, line+BL, i + 2, (chtype) ACS_VLINE);
X	}
X    }
X
X    /* draw a line along the bottom only if it all fitted */
X    if (Menu->HowManyItems - Menu->TopLineOnScreen <= LINES - 2) {
X	for (pos = i + 1; pos > 0; pos--) {
X	    (void) mvwaddch(MenuWin, line+BL, pos, (chtype) ACS_HLINE);
X	}
X	/* Lower left and lower right corner pieces join up neatly;
X	 * Curses uses a '+' sign if it or the tty is too old...
X	 * But the vt100-style character looks much better.
X	 */
X	(void) mvwaddch(MenuWin, line+BL, 0, ACS_LLCORNER);
X	(void) mvwaddch(MenuWin, line+BL, i + 2, ACS_LRCORNER);
X    }
X}
X
Xint
XShowInfo(String, MenuWin, Itlx, Itly)
X    char *String;
X    WINDOW *MenuWin;
X    int Itlx;
X    int Itly;
X	/* Itlx and Itly control the place at which the pop-up window
X	 * pops up... the top left corner of the new window is at
X	 * (Itlx, Itly).  By default, this is as near to the selected
X	 * item as possible.
X	 * So, down a bit and to the right a little.
X	 */
X{
X    int RetVal = 0;
X    int ch;
X    t_StringBox *StringBox, *MakeStringBox();
X
X    /* If there is no information given, say so! */
X    if (String == (char *) 0) {
X	String = "No more information";
X    }
X
X    /* Call a function to calculate the derived parameters, create the
X     * window and put the text on it!
X     */
X    StringBox = MakeStringBox("Info", String, 0L);
X    if (Itlx < 0) Itlx = 0;
X    if (Itly < 0) Itly = 0;
X    StringBox->tlx = Itlx;
X    StringBox->tly = Itly;
X
X    /* Try to ensure that as much of the box is on screen as possible;
X     * if the user wants to move it off-screen, that's up to it, but
X     * we can at least ensure that it starts off OK!
X     */
X    if (StringBox->tlx + StringBox->Width >= COLS) {
X	if ((StringBox->tlx = COLS - StringBox->Width) < 0) {
X	    StringBox->tlx = 0;
X	}
X    }
X    if (StringBox->tly + StringBox->Height >= LINES - 3) {
X	if ((StringBox->tly = LINES - StringBox->Height) < MENUTOP) {
X	    /* MENUTOP is defined in internal.h to be the first row
X	     * on the screen below the menu bar.
X	     */
X	    StringBox->tly = MENUTOP;
X	}
X    }
X
X    do {
X	/* Add the text.  UseBold is set after the user asks for more help.
X	 * As there isn't any more, perhaps he/she hasn't noticed the info
X	 * box, so we highlight it.
X	 * (this feature seems to have been deleted -- Lee)
X	 */
X	(void) touchwin(stdscr);
X	(void) wnoutrefresh(stdscr);
X	if (MenuWin) (void) touchwin(MenuWin);
X	if (MenuWin) (void) wnoutrefresh(MenuWin);
X	ShowStringBox(StringBox);
X	(void) touchwin(StringBox->Window); /* now we're on top... */
X	(void) wrefresh(StringBox->Window);
X	ch = getch();
X
Xcase_place:	/* ComeFrom lower down, resize/move/scroll */
X
X	if (ch == '\r' || ch == '\n') ch = ' ';
X
X	switch (ch) {
X	case '?': case 'x': case 'X': /* x for explain */
X	case 'i': case 'I': /* i for info */
X	case KEY_HELP:
X	    (void) mvwaddch(StringBox->Window, 0, 0, (chtype) 'X');
X	    (void) wnoutrefresh(StringBox->Window);
X	    {
X		char *p = 
X"Info window:\n\
XPress SPACE or Q when you are done.\n\
XYou can Resize the window with R,\n\
XScroll the text inside it with S,\n\
Xand Move it with M.\n\
XHOME will first home the text, and\n\
Xthen put the window near the top\n\
Xleft hand corner of the screen.\n\
X\n\
X[press SPACE to continue]";
X		ch = ShowInfo(p, StringBox->Window, StringBox->tly + 2,
X						    StringBox->tlx + 2);
X		if (ch == ' ') ch = 'x';
X		else goto case_place; /* sigh */
X	    }
X	    break;
X	case 'm': /* Move window */
X	case 'M':
X	    ch = MoveStringBox(StringBox, MenuWin);
X	    if (ch == ' ') ch = 'x';
X	    else goto case_place;
X	    break;
X	case 'r':
X	case 'R':
X	    /* Resize */
X	    ch = ResizeStringBox(StringBox, MenuWin);
X	    if (ch == ' ') ch = 'x';
X	    else goto case_place;
X	    break;
X	case 's':
X	case 'S':
X	    /* Scroll */
X	    ch = ScrollStringBox(StringBox, MenuWin);
X	    if (ch == ' ') ch = 'x';
X	    else goto case_place;
X	    break;
X	case KEY_LEFT:
X	case 'h':
X	    RetVal = KEY_LEFT;
X	    ch = ' ';
X	    break;
X	case 'H':
X	case KEY_HOME:
X	    RetVal = KEY_HOME;
X	    ch = ' ';
X	    break;
X	case KEY_RIGHT:
X	case 'l':
X	    RetVal = KEY_RIGHT;
X	    ch = ' ';
X	    break;
X	case 'k':
X	case KEY_UP:
X	    RetVal = KEY_UP;
X	    ch = ' ';
X	    break;
X	case 'j':
X	case KEY_DOWN:
X	    RetVal = KEY_DOWN;
X	    ch = ' ';
X	    break;
X	default:
X	    (void) beep();
X	    break;
X	case 'q':
X	case 'Q': /* quit */
X	case 'f':
X	case 'F': /* finish */
X	    ch = ' ';
X	case ' ':
X	    RetVal = ' ';
X	    break;
X	}
X    } while (ch != EOF && ch != ' ');
X    /* Clean Up */
X    (void) delwin(StringBox->Window);
X    (void) free((char *) StringBox);
X    (void) touchwin(stdscr);
X    return RetVal;
X}
X
@@@End of lq-text/src/menu/menu.c
echo x - lq-text/src/menu/stringbox.c 1>&2
sed 's/^X//' >lq-text/src/menu/stringbox.c <<'@@@End of lq-text/src/menu/stringbox.c'
X/* Functions to deal with StringBoxes...
X *
X * $Header: /usr/src/cmd/lq-text/src/menu/RCS/stringbox.c,v 1.2 90/10/04 16:28:22 lee Rel1-10 $
X *
X * $Log:	stringbox.c,v $
X * Revision 1.2  90/10/04  16:28:22  lee
X * SysV compat improved.
X * 
X * Revision 1.1  90/08/29  21:50:55  lee
X * Initial revision
X * 
X * Revision 2.1  89/08/07  13:50:19  lee
X * First fully working (V.3.2 only) release;
X * this is the baseline for future development.
X * 
X *
X */
X
X#ifdef ultrix
X# include <cursesX.h>
X#else
X# include <curses.h>
X#endif
X
X#ifndef A_STANDOUT
X# include "oldcurses.h"
X#endif
X
X#include <malloc.h>
X#include "menu.h"
X#include "error.h"
X#include "internal.h"
X
Xvoid SetStringBoxSize();
X
X/* SetStringSize() --
X * Set *Height and *Width to the minimum dimensions which will
X * hold the given String.
X * String can contain embedded newlines and tabs.
X */
Xvoid
XSetStringSize(String, Height, Width)
X    char *String;
X    short *Height, *Width;
X{
X    int ThisLineWidth = 0;
X    register char *p;
X
X    *Height = 0;
X    *Width = 0;
X
X    if (!String || !*String) return;
X
X    for (p = String; *p; p++) {
X	if (*p == '\n') {
X	    if (ThisLineWidth > *Width) {
X		*Width = ThisLineWidth;
X	    }
X	    ThisLineWidth = 0;
X	    ++*Height;
X	} else {
X	    ThisLineWidth++; /* Tabs always cause motion */
X	    if (*p == '\t') {
X		ThisLineWidth |= 7;
X	    }
X	}
X    }
X    if (ThisLineWidth > 0) {
X	/* No trailing newline, so treat the partial line at the
X	 * end as if it were complete.
X	 */
X	if (ThisLineWidth > *Width) {
X	    *Width = ThisLineWidth;
X	}
X	++*Height;
X    }
X}
X
X/* PutStringInBox is a replacement for waddstr() that treats
X * newlines specially in order to facilitate scrolling.
X * The string
X *	"I am a\nhappy boy"
X * (assuming \n is actually a newline character)
X * gets put into the window as
X *	+-----------
X *	| I am a
X *	| happy boy
X * (assuming that the window is large enough).
X * If HScrollPos were 3, and VScrollPos 1, you would get
X *	+-----------
X *	| py boy
X */
X
X/* MakeStringBox() is called to create a StringBox. */
X
Xt_StringBox *
XMakeStringBox(Name, String, Flags)
X    unsigned long Flags;
X    char *Name;
X    char *String;
X{
X    t_StringBox *StringBox;
X
X    if ((StringBox = new(t_StringBox)) == (t_StringBox *) 0) {
X	error(ERR_FATAL|ERR_INTERNAL, "Not enough memory to create StringBox");
X	/*NOTREACHED*/
X    }
X    StringBox->Name = Name;
X    /** StringBox->Flags = Flags;**/
X    StringBox->Window = (WINDOW *) 0;
X    StringBox->tlx = StringBox->tly = 0;
X    StringBox->HScrollPos = StringBox->VScrollPos = 0;
X    StringBox->String = String;
X
X    SetStringBoxSize(StringBox);
X
X    return StringBox;
X}
X
X/* Put the string box on the screen */
XShowStringBox(StringBox)
X    t_StringBox *StringBox;
X{
X    register char *pp;
X    char *String = StringBox->String;
X    register int x = 1; /* start at (1, 1) */
X    int y = 1;
X    int AtStartOfLine = 1;
X
X    if (StringBox->Window == (WINDOW *) 0) {
X	if ((StringBox->Window =
X	     newwin(StringBox->Height, StringBox->Width,
X		    StringBox->tly, StringBox->tlx)) == (WINDOW *) 0) {
X	    
X	    error(ERR_FATAL|ERR_INTERNAL,
X	    "Not enough memory for StringBox window %dx%d",
X					    StringBox->Height,
X					    StringBox->Width);
X	}
X    }
X    /* Now we have a window of the right size in the right place... */
X
X    /* If vertical scrolling is in use, check it's in range */
X    if (StringBox->VScrollPos < 0) {
X	StringBox->VScrollPos = 0;
X    } else if (StringBox->VScrollPos >= StringBox->HowManyLines) {
X	StringBox->VScrollPos = StringBox->HowManyLines - 1;
X    } 
X
X    /* Ignore the first VScrollPos lines: */
X    String = StringBox->String;
X    for (y = 0; y < StringBox->VScrollPos; y++) {
X	extern char *strchr();
X
X	String = strchr(String, '\n');
X	/*CANTHAPPEN*/
X	if (*++String == '\0') {
X	    /* Not enough lines in the buffer.... */
X	    return;
X	}
X    }
X
X    y = 1; /* start the text on row 1, after the box */
X    AtStartOfLine = 1;
X    for (pp = String; *pp; pp++) {
X	if (AtStartOfLine) {
X	    int i;
X
X	    /* skip the initial portion of each line */
X	    for (i = 0; i < StringBox->HScrollPos; i++) {
X		if (!*pp) {
X		    pp--;
X		    break;
X		} else if (*pp == '\n') {
X		    break;
X		}
X		pp++;
X	    }
X	}
X	AtStartOfLine = (*pp == '\n');
X	if (*pp == '\n') { /* end of line.... */
X	    (void) wmove(StringBox->Window, y, x);
X	    (void) wclrtoeol(StringBox->Window);
X	    y++;
X	    x = 1;
X	    (void) wmove(StringBox->Window, y, x);
X	    (void) wclrtoeol(StringBox->Window);
X	} else { /* not a newline */
X	    if (*pp == '\t') {
X		x |= 7;
X		x++;
X		/* tabs always move.  Also, we started
X		 * at position 1, so increment after the x |= 7.
X		 */
X	    } else {
X		(void) mvwaddch(StringBox->Window, y, x, (chtype) *pp);
X		x++;
X	    }
X	}
X    } /* end for */
X    (void) wclrtoeol(StringBox->Window);
X    (void) wattrset(StringBox->Window, 0);
X
X    /* do the box second, in case the text over-ran */
X    (void) box(StringBox->Window, 0, 0);
X    if (StringBox->HScrollPos > 0) {
X	/* Put a symbol like   | to show that the message can
X	 * be scrolled to    --+ the right by pressing the right-
X	 * arrow or "l".       |
X	 */
X	(void) mvwaddch(StringBox->Window, 1, 0, ACS_SBSS);
X    }
X
X    if (StringBox->StringWidth - StringBox->HScrollPos > StringBox->Width - 2) {
X	/* Put a symbol like |   to show that the message can
X	 * be scrolled to    +-- the left by pressing the left-
X	 * arrow or "h".     |   Also, the HOME key will move the
X	 * text to the top left of the InfoWin, and put the window
X	 * near the top left of the screen.
X	 */
X	/* (the -1 is because numbering starts form zero) */
X	(void) mvwaddch(StringBox->Window, 1, StringBox->Width - 1, ACS_SSSB);
X    }
X
X    /* If you can go up... */
X    if (StringBox->VScrollPos > 0) {
X	/* ACS_trbl */
X	(void) mvwaddch(StringBox->Window, 0, 1, ACS_SSBS);
X    }
X
X    /* And if you can go down (the -2 is for the box): */
X    if (StringBox->HowManyLines >
X			StringBox->VScrollPos + StringBox->Height - 2) {
X	(void) mvwaddch(StringBox->Window, StringBox->Height - 1, 1, ACS_BSSS);
X	
X    }
X}
X
XResizeStringBox(StringBox, MenuWin)
X    t_StringBox *StringBox;
X    WINDOW *MenuWin;
X{
X    int ch;
X    int Changed = 0;
X
X    do {
X	if (Changed) {
X	    /* Recreate the window... */
X	    (void) delwin(StringBox->Window);
X	    StringBox->Window = (WINDOW *) 0;
X	    ShowStringBox(StringBox);
X	    (void) touchwin(stdscr);
X	    (void) wnoutrefresh(stdscr);
X	    if (MenuWin) (void) touchwin(MenuWin);
X	    if (MenuWin) (void) wnoutrefresh(MenuWin);
X	    (void) touchwin(StringBox->Window);
X	}
X
X	Changed = 0;
X
X	(void) mvwaddch(StringBox->Window, 0, 0, (chtype) 'R');
X	(void) wmove(StringBox->Window, StringBox->Height, StringBox->Width);
X	(void) wrefresh(StringBox->Window);
X
X	ch = getch();
X
X	switch(ch) {
X	case 'q': case 'Q':
X	case 'f': case 'F':
X	    ch = ' ';
X	case ' ':
X	    break;
X	case '?':
X	case 'x': case 'X':
X	case 'i': case 'I':
X	case KEY_HELP:
X	    {
X		char *p = 
X"Resize window: use the arrow keys\n\
Xto move the window about.  You can\n\
Xuse HOME, H, or the d key to set\n\
Xthe window to the default size.\n\
XPress SPACE when you are done.";
X		ch = ShowInfo(p, StringBox->Window, StringBox->tly + 2,
X						    StringBox->tlx + 2);
X		if (ch == ' ') ch = 'x';
X		else continue;
X	    }
X	    break;
X	case KEY_LEFT:
X	case 'h':
X	    if (StringBox->Width > 3) {
X		StringBox->Width--;
X		Changed = 1;
X	    } else {
X		(void) beep();
X	    }
X	    break;
X	case KEY_RIGHT:
X	case 'l':
X	    if (StringBox->Width + StringBox->tlx < COLS - 1) {
X		StringBox->Width++;
X		Changed = 1;
X	    } else {
X		(void) beep();
X	    }
X	    break;
X	case KEY_DOWN:
X	case 'j':
X	    if (StringBox->Height + StringBox->tly < LINES - 1) {
X		StringBox->Height++;
X		Changed = 1;
X	    } else {
X		(void) beep();
X	    }
X	    break;
X	case KEY_UP:
X	case 'k':
X	    if (StringBox->Height > 3) {
X		StringBox->Height--;
X		Changed = 1;
X	    } else {
X		(void) beep();
X	    }
X	    break;
X	case KEY_HOME: case 'H':
X	case 'd': case 'D':
X	    /* special case: restore to default size; may
X	     * also move the box, if it wouldn't fit on the screen in
X	     * the new size.
X	     */
X	    SetStringBoxSize(StringBox);
X	    Changed = 1;
X	    break;
X	case 's': case'S':
X	case 'm': case 'M': /* Sideways jump to other window functions... */
X	    return ch;
X	default:
X	    (void) beep();
X	    break;
X	}
X    } while (ch != EOF && ch != ' ');
X    return ch;
X}
X
XMoveStringBox(StringBox, MenuWin)
X    t_StringBox *StringBox;
X    WINDOW *MenuWin;
X{
X    int ch;
X    int Changed = 0;
X
X    do {
X	if (Changed) {
X	    /* Recreate the window... */
X	    (void) delwin(StringBox->Window);
X	    StringBox->Window = (WINDOW *) 0;
X	    (void) touchwin(stdscr);
X	    (void) wnoutrefresh(stdscr);
X	    if (MenuWin) (void) touchwin(MenuWin);
X	    if (MenuWin) (void) wnoutrefresh(MenuWin);
X	    ShowStringBox(StringBox);
X	}
X
X	Changed = 0;
X
X	(void) mvwaddch(StringBox->Window, 0, 0, (chtype) 'M');
X	(void) wmove(StringBox->Window, StringBox->Height, StringBox->Width);
X	(void) wrefresh(StringBox->Window);
X
X	ch = getch();
X
X	switch(ch) {
X	case 'q': case 'Q':
X	case 'f': case 'F':
X	    ch = ' ';
X	case ' ':
X	    break;
X
X	case '?': case 'x': case 'X': /* x for explain */
X	case 'i': case 'I': /* i for info */
X	case KEY_HELP:
X	    (void) mvwaddch(StringBox->Window, 0, 0, (chtype) 'X');
X	    (void) wnoutrefresh(StringBox->Window);
X	    {
X		char *p = 
X"Scrolling Text:\n\
XUse the arrow keys (or h j k and l)\n\
Xto move the window around.\n\
XYou can use the HOME key (or H) to\n\
Xmove it near the top left hand corner\n\
Xof the screen.\n\
XWhen you finish moving the window,\n\
Xor if you want to resize it,\n\
Xpress SPACE.\n\
XAfter you have finished, you can\n\
Xuse R to resize the window,\n\
Xand S to scroll the text in it.\n\
X\n\
X[press SPACE to continue]";
X		ch = ShowInfo(p, StringBox->Window, StringBox->tly + 2,
X						    StringBox->tlx + 2);
X		if (ch == ' ') ch = 'x';
X		else continue;
X	    }
X	    break;
X	case 'h':
X	case KEY_LEFT:
X	    if (StringBox->tlx > 0) {
X		StringBox->tlx--;
X		(void) mvwin(StringBox->Window, StringBox->tly, StringBox->tlx);
X		Changed = 1;
X	    } else {
X		(void) beep();
X	    }
X	    break;
X	case 'l':
X	case KEY_RIGHT:
X	    if (StringBox->tlx + StringBox->Width < COLS) {
X		StringBox->tlx++;
X		(void) mvwin(StringBox->Window, StringBox->tly, StringBox->tlx);
X		Changed = 1;
X	    } else {
X		(void) beep();
X	    }
X	    break;
X	case 'k':
X	case KEY_UP:
X	    if (StringBox->tly > 2) {
X		--StringBox->tly;
X		(void) mvwin(StringBox->Window, StringBox->tly, StringBox->tlx);
X		Changed = 1;
X	    } else {
X		(void) beep();
X	    }
X	    break;
X	case 'j':
X	case KEY_DOWN:
X	    if (StringBox->tly + StringBox->Height < COLS - 1) {
X		++StringBox->tly;
X		(void) mvwin(StringBox->Window, StringBox->tly, StringBox->tlx);
X		Changed = 1;
X	    } else {
X		(void) beep();
X	    }
X	    break;
X	case 'H':
X	case KEY_HOME:
X	    StringBox->tlx = 0;
X	    StringBox->tly = 2;
X	    (void) mvwin(StringBox->Window, StringBox->tly, StringBox->tlx);
X	    Changed = 1;
X	    break;
X	case 's': case'S':
X	case 'r': case 'R': /* Jump to other functions */
X	    return ch;
X	default:
X	    (void) beep();
X	}
X    } while (ch != EOF && ch != ' ');
X    return ch;
X}
X
XScrollStringBox(StringBox, MenuWin)
X    t_StringBox *StringBox;
X    WINDOW *MenuWin;
X{
X    int ch;
X    int Changed = 0;
X
X    do {
X	if (Changed) {
X	    /* Recreate the window... */
X	    (void) delwin(StringBox->Window);
X	    StringBox->Window = (WINDOW *) 0;
X	    (void) touchwin(stdscr);
X	    (void) wnoutrefresh(stdscr);
X	    if (MenuWin) (void) touchwin(MenuWin);
X	    if (MenuWin) (void) wnoutrefresh(MenuWin);
X	    ShowStringBox(StringBox);
X	}
X
X	Changed = 0;
X
X	(void) mvwaddch(StringBox->Window, 0, 0, (chtype) 'S');
X	(void) wmove(StringBox->Window, StringBox->Height, StringBox->Width);
X	(void) wrefresh(StringBox->Window);
X
X	ch = getch();
X
X	switch(ch) {
X	case 'q': case 'Q':
X	case 'f': case 'F':
X	    ch = ' ';
X	case ' ':
X	    break;
X
X	case '?': case 'x': case 'X': /* x for explain */
X	case 'i': case 'I': /* i for info */
X	case KEY_HELP:
X	    (void) mvwaddch(StringBox->Window, 0, 0, (chtype) 'X');
X	    (void) wnoutrefresh(StringBox->Window);
X	    {
X		char *p = 
X"Scrolling Text:\n\
XUse the arrow keys (or h j k and l)\n\
Xto move the text within the window.\n\
XYou can use the HOME key (or H) to\n\
Xmove to the start of the text.\n\
XWhen you finish scrolling the text,\n\
Xor if you want to move or resize the\n\
Xwindow, press SPACE.\n\
X\n\
X[press SPACE to continue]";
X		ch = ShowInfo(p, StringBox->Window, StringBox->tly + 2,
X						    StringBox->tlx + 2);
X		if (ch == ' ') ch = 'x';
X		else continue;
X	    }
X	    break;
X	case 'h':
X	case KEY_LEFT:
X	    /* Press |space to continue. |
X	     * <-----------------------> DescLen
X	     *       <-------------------> InfoWidth
X	     * <------> StringBox->HScrollPos
X	     * (Not allowed to go further left than this)
X	     */
X	    if (StringBox->HScrollPos > 0) {
X		StringBox->HScrollPos--;
X		Changed = 1;
X	    } else {
X		(void) beep();
X	    }
X	    break;
X	case 'l':
X	case KEY_RIGHT:
X	    if (StringBox->StringWidth - StringBox->HScrollPos >
X			StringBox->Width - 2) {
X		StringBox->HScrollPos++;
X		Changed = 1;
X	    } else {
X		beep();
X	    }
X	    break;
X	case 'k':
X	case KEY_UP:
X	    if (StringBox->VScrollPos) {
X		StringBox->VScrollPos--;
X	    Changed = 1;
X	    } else {
X		(void) beep();
X	    }
X	    break;
X	case 'j':
X	case KEY_DOWN:
X	    /* forbid the last line of the screen so as to avoid scrolling/
X	     * bottom-right-corner hassle, and also to avoid obscuring
X	     * soft function labels if they're there.
X	     */
X	    if (StringBox->Height - 2 + StringBox->VScrollPos <
X						StringBox->HowManyLines) {
X		StringBox->VScrollPos++;
X		Changed = 1;
X	    } else {
X		(void) beep();
X	    }
X	    break;
X	case 'H':
X	case KEY_HOME:
X	    /* Not testing for > 0 here means that if things get really
X	     * screwed up and {H,V}ScrollPos gets -ve, HOME will set
X	     * things right again.  Of course, the user won't know
X	     * that, but every little helps...
X	     */
X	    if (StringBox->HScrollPos || StringBox->VScrollPos) {
X		StringBox->HScrollPos = 0;
X		StringBox->VScrollPos = 0;
X		Changed = 1;
X	    }
X	    break;
X	case 'r': case 'R':
X	case 'm': case 'M': /* Jump to other functions: */
X	    return ch;
X	default:
X	    (void) beep();
X	}
X    } while (ch != EOF && ch != ' ');
X    return ch;
X}
X
Xvoid
XSetStringBoxSize(StringBox)
X    t_StringBox *StringBox;
X{
X    /* Measure the text */
X    SetStringSize(StringBox->String, &(StringBox->Height), &(StringBox->Width));
X    StringBox->HowManyLines = StringBox->Height;
X    StringBox->StringWidth = StringBox->Width;
X
X    /* Leave room for a box */
X    StringBox->Height += 2;
X    StringBox->Width += 2;
X
X    /* Check it's not too wide */
X    if (StringBox->Width >= COLS) {
X	StringBox->Width = COLS - 4;
X    }
X
X    /* Check it's not too tall */
X    if (StringBox->Height >= LINES - 4) {
X	StringBox->Height = LINES - 4;
X    }
X
X    /* check it's not too thin */
X    if (StringBox->Width < 3) { /* tiny screen, or no text at all... */
X	StringBox->Width = 3;
X    }
X
X    /* Check it's not too short */
X    if (StringBox->Height < 3) { /* tiny screen, or no text at all... */
X	StringBox->Height = 3;
X    }
X
X    /* Check it's on the screen */
X    if (StringBox->tlx + StringBox->Width >= COLS) {
X	/* right adjust if it didn't fit */
X	StringBox->tlx = (COLS - StringBox->Width) - 1;
X	/* ASSERT: StringBox->tlx > 0 because StringBox->Width < COLS */
X    }
X    if (StringBox->tly + StringBox->Height >= LINES - 3) {
X	/* avoid the bottom line of the screen */
X	StringBox->tly = (LINES - 3) - StringBox->Height;
X    }
X}
X
Xchar *
XAskForString(Message, MaxLen, MenuWin, Itlx, Itly)
X    char *Message;
X    int MaxLen;
X    WINDOW *MenuWin;
X    int Itlx;
X    int Itly;
X{
X    t_StringBox *Question;
X    char *Answer;
X    char *p;
X    char *Text;
X    int i;
X    int ch;
X
X    /* ensure that we allow at least 1 char of input! */
X    if (MaxLen < 1) MaxLen = 1;
X
X    if ((Text = malloc(strlen(Message) + MaxLen + 5)) == (char *) 0) {
X	error(ERR_FATAL|ERR_MEMORY, "Not enough mem for question \"%s\"\n",
X					Message);
X    }
X
X    (void) sprintf(Text, "%s\n : ", Message);
X    p = Answer = &Text[strlen(Text)]; /* i.e. pointing to the \0 */
X
X    /* Make a StringBox containing the question and a space for
X     * the answer
X     */
X    Question = MakeStringBox("Question", Text, (unsigned long) 0);
X
X    /* add space for the answer... */
X    if (Question->Width < 5) Question->Width = 5;
X
X    if (Itlx < 0) Itlx = 0;
X    if (Itly < 3) Itly = 3; /* avoid the menu bar */
X    
X    Question->tlx = Itlx;
X    Question->tly = Itly;
X	/* Itlx and Itly control the place at which the pop-up window
X	 * pops up... the top left corner of the new window is at
X	 * (Itlx, Itly).  By default, this is as near to the selected
X	 * item as possible.
X	 * So, down a bit and to the right a little.
X	 */
X
X    /* display the StringBox and wait for an answer.  If necessary,
X     * extend and then scroll the StringBox to make the answer fit
X     */
X
X    do {
X	for (i = 0;
X		i - Question->HScrollPos < Question->Width - 2 && i < MaxLen;
X		i++) {
X	    if (&Answer[i] >= p) {
X		Answer[i] = '_';
X		Answer[i + 1] = '\0';
X	    }
X	}
X
X	(void) touchwin(stdscr);
X	(void) wnoutrefresh(stdscr);
X	if (MenuWin) {
X	    (void) touchwin(MenuWin);
X	    (void) wnoutrefresh(MenuWin);
X	}
X	/* SetStringBoxSize(Question); /* in case the text has changed */
X
X	ShowStringBox(Question);
X
X	(void) touchwin(Question->Window); /* now we're on top... */
X
X	/* the +7 is for the bar on the left of the window, the " : ",
X	 * and an extra one to put the cursor where the *next* character
X	 * will be, plus an unexplained fudge factor.
X	 */
X	(void) move(Question->tly + Question->Height - 2,
X		Question->tlx + (p - Answer) - Question->HScrollPos + 4);
X	(void) wrefresh(Question->Window);
X	(void) move(Question->tly + Question->Height - 2,
X		Question->tlx + (p - Answer) - Question->HScrollPos + 4);
X	ch = getch();
X
X	if (ch == KEY_F(1) || ch == '\r' || ch == '\n') break;
X
X	/* Note: p always points to the point at which the next character
X	 * to be typed would go, and *p is \0.
X	 */
X
X	switch (ch) {
X	case KEY_UP:
X	case KEY_LEFT:
X	case KEY_DOWN:
X	case KEY_RIGHT:
X	    (void) MoveStringBox(Question, MenuWin);
X	    break;
X	case KEY_HOME:
X	    /* Resize */
X	    (void) ResizeStringBox(Question, MenuWin);
X	    break;
X	case '\b':
X	case 127:
X	case 255:
X	case -1:
X	    if (p > Answer) {
X		*p = '\0';
X		--p;
X		if (Question->HScrollPos > 0) --Question->HScrollPos;
X	    } else {
X		beep();
X	    }
X	    break;
X	case 'W' ^ 64: /* copntrol-W -- delete word */
X	    *p = '_';
X	    while (p > Answer && (*p == ' ' || *p == '/')) {
X		p--;
X		if (Question->HScrollPos > 0) --Question->HScrollPos;
X	    }
X	    while (p > Answer && *p != ' ' && *p != '/') {
X		p--;
X		if (Question->HScrollPos > 0) --Question->HScrollPos;
X	    }
X	    if (*p == '/' || *p == ' ') p++;
X	    *p = '\0';
X	    break;
X	case 'U' ^ 64:
X	case 'X' ^ 64:
X	    p = Answer;
X	    *p = '\0';
X	    Question->HScrollPos = 0;
X	    break;
X	default:
X	    if (p - Answer >= MaxLen) {
X		beep();
X	    } else {
X		*p++ = ch;
X		/* The magic number on the next line works as follows:
X		 * | : here is me typing stuff__ |
X		 * ^^^^				^^
X		 * So there are 6 characters, plus one for the cursor;
X		 * (p - Answer) gives one fewer than needed, so we arrive
X		 * at 8. 
X		 */
X		if (p - Answer + 7 - Question->HScrollPos >= Question->Width) {
X		    Question->HScrollPos += 4;
X		}
X	    }
X	}
X    } while (ch != EOF && ch != KEY_F(1));
X    /* Clean Up */
X    (void) delwin(Question->Window);
X    (void) touchwin(stdscr);
X
X    if (p > Answer) {
X	*p = '\0';
X	--p;
X    }
X
X    if (p == Answer) {
X	(void) free(Text);
X	return (char *) 0;
X    } else {
X	unsigned len = strlen(Answer);
X	(void) bcopy(Answer, Text, len + 1);
X	Text[len] = '\0';
X	return realloc(Text, len + 1);
X    }
X    /*NOTREACHED*/
X}
@@@End of lq-text/src/menu/stringbox.c
echo end of part 09
-- 
Liam R. E. Quin,  lee@sq.com, SoftQuad Inc., Toronto, +1 (416) 963-8337