freebsd-dev/gnu/lib/libdialog/menubox.c
Eric Melville 87aaead2f8 Improve the interface provided by libdialog. Move a cursor around over
the components and trigger actions based on its position. This reduces
the need to remember the functions of various keys, and makes the
interface more consistant across library.
~
2001-07-18 05:21:37 +00:00

468 lines
14 KiB
C

/*
* menubox.c -- implements the menu box
*
* AUTHOR: Savio Lam (lam836@cs.cuhk.hk)
*
* Substantial rennovation: 12/18/95, Jordan K. Hubbard
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#ifndef lint
static const char rcsid[] =
"$FreeBSD$";
#endif
#include <dialog.h>
#include "dialog.priv.h"
#include <ncurses.h>
static void print_item(WINDOW *win, unsigned char *tag, unsigned char *item, int choice, int selected, dialogMenuItem *me, int menu_width, int tag_x, int item_x);
#define DREF(di, item) ((di) ? &((di)[(item)]) : NULL)
/*
* Display a menu for choosing among a number of options
*/
int
dialog_menu(unsigned char *title, unsigned char *prompt, int height, int width, int menu_height, int cnt, void *it, unsigned char *result, int *ch, int *sc)
{
int i, j, x, y, cur_x, cur_y, box_x, box_y, key = 0, button, choice,
l, k, scroll, max_choice, item_no, redraw_menu = FALSE;
char okButton, cancelButton;
int rval = 0, ok_space, cancel_space;
WINDOW *dialog, *menu;
unsigned char **items = NULL;
dialogMenuItem *ditems;
int menu_width, tag_x, item_x;
draw:
choice = ch ? *ch : 0;
scroll = sc ? *sc : 0;
button = 0;
/* If item_no is a positive integer, use old item specification format */
if (cnt >= 0) {
items = it;
ditems = NULL;
item_no = cnt;
}
/* It's the new specification format - fake the rest of the code out */
else {
item_no = abs(cnt);
ditems = it;
if (!items)
items = (unsigned char **)alloca((item_no * 2) * sizeof(unsigned char *));
/* Initializes status */
for (i = 0; i < item_no; i++) {
items[i*2] = ditems[i].prompt;
items[i*2 + 1] = ditems[i].title;
}
}
max_choice = MIN(menu_height, item_no);
tag_x = 0;
item_x = 0;
/* Find length of longest item in order to center menu */
for (i = 0; i < item_no; i++) {
l = strlen(items[i * 2]);
for (j = 0; j < item_no; j++) {
k = strlen(items[j * 2 + 1]);
tag_x = MAX(tag_x, l + k + 2);
}
item_x = MAX(item_x, l);
}
if (height < 0)
height = strheight(prompt) + menu_height + 4 + 2;
if (width < 0) {
i = strwidth(prompt);
j = ((title != NULL) ? strwidth(title) : 0);
width = MAX(i, j);
width = MAX(width, tag_x + 4) + 4;
}
width = MAX(width, 24);
if (width > COLS)
width = COLS;
if (height > LINES)
height = LINES;
/* center dialog box on screen */
x = DialogX ? DialogX : (COLS - width) / 2;
y = DialogY ? DialogY : (LINES - height) / 2;
#ifdef HAVE_NCURSES
if (use_shadow)
draw_shadow(stdscr, y, x, height, width);
#endif
dialog = newwin(height, width, y, x);
if (dialog == NULL) {
endwin();
fprintf(stderr, "\nnewwin(%d,%d,%d,%d) failed, maybe wrong dims\n", height, width, y, x);
return -1;
}
keypad(dialog, TRUE);
draw_box(dialog, 0, 0, height, width, dialog_attr, border_attr);
wattrset(dialog, border_attr);
wmove(dialog, height - 3, 0);
waddch(dialog, ACS_LTEE);
for (i = 0; i < width - 2; i++)
waddch(dialog, ACS_HLINE);
wattrset(dialog, dialog_attr);
waddch(dialog, ACS_RTEE);
wmove(dialog, height - 2, 1);
for (i = 0; i < width - 2; i++)
waddch(dialog, ' ');
if (title != NULL) {
wattrset(dialog, title_attr);
wmove(dialog, 0, (width - strlen(title)) / 2 - 1);
waddch(dialog, ' ');
waddstr(dialog, title);
waddch(dialog, ' ');
}
wattrset(dialog, dialog_attr);
wmove(dialog, 1, 2);
print_autowrap(dialog, prompt, height - 1, width - 2, width, 1, 2, TRUE, FALSE);
menu_width = width - 6;
getyx(dialog, cur_y, cur_x);
box_y = cur_y + 1;
box_x = (width - menu_width) / 2 - 1;
/* create new window for the menu */
menu = subwin(dialog, menu_height, menu_width, y + box_y + 1, x + box_x + 1);
if (menu == NULL) {
delwin(dialog);
endwin();
fprintf(stderr, "\nsubwin(dialog,%d,%d,%d,%d) failed, maybe wrong dims\n", menu_height, menu_width,
y + box_y + 1, x + box_x + 1);
return -1;
}
keypad(menu, TRUE);
/* draw a box around the menu items */
draw_box(dialog, box_y, box_x, menu_height+2, menu_width+2, menubox_border_attr, menubox_attr);
tag_x = menu_width > tag_x + 1 ? (menu_width - tag_x) / 2 : 1;
item_x = menu_width > item_x + 4 ? tag_x + item_x + 2 : menu_width - 3;
/* Print the menu */
for (i = 0; i < max_choice; i++)
print_item(menu, items[(scroll + i) * 2], items[(scroll + i) * 2 + 1], i, i == choice, DREF(ditems, scroll + i), menu_width, tag_x, item_x);
wnoutrefresh(menu);
print_arrows(dialog, scroll, menu_height, item_no, box_x, box_y, tag_x, cur_x, cur_y);
display_helpline(dialog, height - 1, width);
x = width / 2 - 11;
y = height - 2;
if (ditems && result) {
cancelButton = toupper(ditems[CANCEL_BUTTON].prompt[0]);
print_button(dialog, ditems[CANCEL_BUTTON].prompt, y, x + strlen(ditems[OK_BUTTON].prompt) + 5, ditems[CANCEL_BUTTON].checked ? ditems[CANCEL_BUTTON].checked(&ditems[CANCEL_BUTTON]) : FALSE);
okButton = toupper(ditems[OK_BUTTON].prompt[0]);
print_button(dialog, ditems[OK_BUTTON].prompt, y, x, ditems[OK_BUTTON].checked ? ditems[OK_BUTTON].checked(&ditems[OK_BUTTON]) : TRUE);
}
else {
cancelButton = 'C';
print_button(dialog, "Cancel", y, x + 14, FALSE);
okButton = 'O';
print_button(dialog, " OK ", y, x, TRUE);
}
wrefresh(dialog);
while (key != ESC) {
key = wgetch(dialog);
/* Shortcut to OK? */
if (toupper(key) == okButton) {
if (ditems) {
if (result && ditems[OK_BUTTON].fire) {
int status;
WINDOW *save;
save = dupwin(newscr);
status = ditems[OK_BUTTON].fire(&ditems[OK_BUTTON]);
if (status & DITEM_RESTORE) {
touchwin(save);
wrefresh(save);
}
delwin(save);
}
}
else if (result)
strcpy(result, items[(scroll + choice) * 2]);
rval = 0;
key = ESC; /* Punt! */
break;
}
/* Shortcut to cancel? */
if (toupper(key) == cancelButton) {
if (ditems && result && ditems[CANCEL_BUTTON].fire) {
int status;
WINDOW *save;
save = dupwin(newscr);
status = ditems[CANCEL_BUTTON].fire(&ditems[CANCEL_BUTTON]);
if (status & DITEM_RESTORE) {
touchwin(save);
wrefresh(save);
}
delwin(save);
}
rval = 1;
key = ESC; /* Run away! */
break;
}
/* Check if key pressed matches first character of any item tag in menu */
for (i = 0; i < max_choice; i++)
if (key < 0x100 && key != ' ' && toupper(key) == toupper(items[(scroll + i) * 2][0]))
break;
if (i < max_choice || (key >= '1' && key <= MIN('9', '0'+max_choice)) || KEY_IS_UP(key) || KEY_IS_DOWN(key)) {
if (key >= '1' && key <= MIN('9', '0'+max_choice))
i = key - '1';
else if (KEY_IS_UP(key)) {
if (!choice) {
if (scroll) {
/* Scroll menu down */
getyx(dialog, cur_y, cur_x); /* Save cursor position */
if (menu_height > 1) {
/* De-highlight current first item before scrolling down */
print_item(menu, items[scroll * 2], items[scroll * 2 + 1], 0, FALSE, DREF(ditems, scroll), menu_width, tag_x, item_x);
scrollok(menu, TRUE);
wscrl(menu, -1);
scrollok(menu, FALSE);
}
scroll--;
print_item(menu, items[scroll * 2], items[scroll * 2 + 1], 0, TRUE, DREF(ditems, scroll), menu_width, tag_x, item_x);
wnoutrefresh(menu);
print_arrows(dialog, scroll, menu_height, item_no, box_x, box_y, tag_x, cur_x, cur_y);
wrefresh(dialog);
}
continue; /* wait for another key press */
}
else
i = choice - 1;
}
else if (KEY_IS_DOWN(key)) {
if (choice == max_choice - 1) {
if (scroll + choice < item_no - 1) {
/* Scroll menu up */
getyx(dialog, cur_y, cur_x); /* Save cursor position */
if (menu_height > 1) {
/* De-highlight current last item before scrolling up */
print_item(menu, items[(scroll + max_choice - 1) * 2],
items[(scroll + max_choice - 1) * 2 + 1],
max_choice-1, FALSE, DREF(ditems, scroll + max_choice - 1), menu_width, tag_x, item_x);
scrollok(menu, TRUE);
scroll(menu);
scrollok(menu, FALSE);
}
scroll++;
print_item(menu, items[(scroll + max_choice - 1) * 2],
items[(scroll + max_choice - 1) * 2 + 1],
max_choice - 1, TRUE, DREF(ditems, scroll + max_choice - 1), menu_width, tag_x, item_x);
wnoutrefresh(menu);
print_arrows(dialog, scroll, menu_height, item_no, box_x, box_y, tag_x, cur_x, cur_y);
wrefresh(dialog);
}
continue; /* wait for another key press */
}
else
i = choice + 1;
}
if (i != choice) {
/* De-highlight current item */
getyx(dialog, cur_y, cur_x); /* Save cursor position */
print_item(menu, items[(scroll + choice) * 2], items[(scroll + choice) * 2 + 1], choice, FALSE, DREF(ditems, scroll + choice), menu_width, tag_x, item_x);
/* Highlight new item */
choice = i;
print_item(menu, items[(scroll + choice) * 2], items[(scroll + choice) * 2 + 1], choice, TRUE, DREF(ditems, scroll + choice), menu_width, tag_x, item_x);
wnoutrefresh(menu);
wmove(dialog, cur_y, cur_x); /* Restore cursor to previous position */
wrefresh(dialog);
}
continue; /* wait for another key press */
}
switch (key) {
case KEY_PPAGE:
if (scroll > height - 4) { /* can we go up? */
scroll -= (height - 4);
} else {
scroll = 0;
}
redraw_menu = TRUE;
break;
case KEY_NPAGE:
if (scroll + menu_height >= item_no-1 - menu_height) { /* can we go down a full page? */
scroll = item_no - menu_height;
if (scroll < 0)
scroll = 0;
} else {
scroll += menu_height;
}
redraw_menu = TRUE;
break;
case KEY_HOME:
scroll = 0;
choice = 0;
redraw_menu = TRUE;
break;
case KEY_END:
scroll = item_no - menu_height;
if (scroll < 0)
scroll = 0;
choice = max_choice - 1;
redraw_menu = TRUE;
break;
case KEY_BTAB:
case TAB:
case KEY_LEFT:
case KEY_RIGHT:
button = !button;
if (ditems && result) {
print_button(dialog, ditems[CANCEL_BUTTON].prompt, y, x + strlen(ditems[OK_BUTTON].prompt) + 5, ditems[CANCEL_BUTTON].checked ? ditems[CANCEL_BUTTON].checked(&ditems[CANCEL_BUTTON]) : button);
print_button(dialog, ditems[OK_BUTTON].prompt, y, x, ditems[OK_BUTTON].checked ? ditems[OK_BUTTON].checked(&ditems[OK_BUTTON]) : !button);
ok_space = 1;
cancel_space = strlen(ditems[OK_BUTTON].prompt) + 6;
}
else {
print_button(dialog, "Cancel", y, x + 14, button);
print_button(dialog, " OK ", y, x, !button);
ok_space = 3;
cancel_space = 15;
}
if (button)
wmove(dialog, y, x+cancel_space);
else
wmove(dialog, y, x+ok_space);
wrefresh(dialog);
break;
case ' ':
case '\r':
case '\n':
if (!button) {
/* A fire routine can do just about anything to the screen, so be prepared
to accept some hints as to what to do in the aftermath. */
if (ditems) {
if (ditems[scroll + choice].fire) {
int status;
WINDOW *save;
save = dupwin(newscr);
status = ditems[scroll + choice].fire(&ditems[scroll + choice]);
if (status & DITEM_RESTORE) {
touchwin(save);
wrefresh(save);
}
delwin(save);
if (status & DITEM_CONTINUE)
continue;
else if (status & DITEM_LEAVE_MENU) {
/* Allow a fire action to take us out of the menu */
key = ESC;
break;
}
else if (status & DITEM_RECREATE) {
delwin(menu);
delwin(dialog);
dialog_clear();
goto draw;
}
}
}
else if (result)
strcpy(result, items[(scroll+choice)*2]);
}
rval = button;
key = ESC;
break;
case ESC:
rval = -1;
break;
case KEY_F(1):
case '?':
display_helpfile();
break;
}
/* save info about menu item position */
if (ch)
*ch = choice;
if (sc)
*sc = scroll;
if (redraw_menu) {
for (i = 0; i < max_choice; i++) {
print_item(menu, items[(scroll + i) * 2], items[(scroll + i) * 2 + 1], i, i == choice, DREF(ditems, scroll + i), menu_width, tag_x, item_x);
}
wnoutrefresh(menu);
getyx(dialog, cur_y, cur_x); /* Save cursor position */
print_arrows(dialog, scroll, menu_height, item_no, box_x, box_y, tag_x, cur_x, cur_y);
wmove(dialog, cur_y, cur_x); /* Restore cursor to previous position */
wrefresh(dialog);
redraw_menu = FALSE;
}
}
delwin(menu);
delwin(dialog);
return rval;
}
/*
* Print menu item
*/
static void
print_item(WINDOW *win, unsigned char *tag, unsigned char *item, int choice, int selected, dialogMenuItem *me, int menu_width, int tag_x, int item_x)
{
int i;
/* Clear 'residue' of last item */
wattrset(win, menubox_attr);
wmove(win, choice, 0);
for (i = 0; i < menu_width; i++)
waddch(win, ' ');
wmove(win, choice, tag_x);
wattrset(win, selected ? tag_key_selected_attr : tag_key_attr);
waddch(win, tag[0]);
wattrset(win, selected ? tag_selected_attr : tag_attr);
waddnstr(win, tag + 1, item_x - tag_x - 3);
wmove(win, choice, item_x);
wattrset(win, selected ? item_selected_attr : item_attr);
waddnstr(win, item, menu_width - item_x - 1);
/* If have a selection handler for this, call it */
if (me && me->selected) {
wrefresh(win);
me->selected(me, selected);
}
}
/* End of print_item() */