1/* 2 * menubox.c -- implements the menu box 3 * 4 * AUTHOR: Savio Lam (lam836@cs.cuhk.hk) 5 * 6 * Substantial rennovation: 12/18/95, Jordan K. Hubbard 7 * 8 * This program is free software; you can redistribute it and/or 9 * modify it under the terms of the GNU General Public License 10 * as published by the Free Software Foundation; either version 2 11 * of the License, or (at your option) any later version. 12 * 13 * This program is distributed in the hope that it will be useful, 14 * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 * GNU General Public License for more details. 17 * 18 * You should have received a copy of the GNU General Public License 19 * along with this program; if not, write to the Free Software 20 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 21 */ 22 23#include <sys/cdefs.h> 24__FBSDID("$FreeBSD$"); 25 26#include <dialog.h> 27#include "dialog.priv.h" 28#include <err.h> 29#include <ncurses.h> 30 31static 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); 32 33#define DREF(di, item) ((di) ? &((di)[(item)]) : NULL) 34 35/* 36 * Display a menu for choosing among a number of options 37 */ 38int 39dialog_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) 40{ 41 int i, j, x, y, cur_x, cur_y, box_x, box_y, key = 0, button, choice, 42 l, k, scroll, max_choice, item_no, redraw_menu = FALSE; 43 char okButton, cancelButton; 44 int rval = 0, ok_space, cancel_space; 45 WINDOW *dialog, *menu; 46 unsigned char **items = NULL; 47 dialogMenuItem *ditems; 48 int menu_width, tag_x, item_x; 49 50draw: 51 choice = ch ? *ch : 0; 52 scroll = sc ? *sc : 0; 53 button = 0; 54 55 /* If item_no is a positive integer, use old item specification format */ 56 if (cnt >= 0) { 57 items = it; 58 ditems = NULL; 59 item_no = cnt; 60 } 61 /* It's the new specification format - fake the rest of the code out */ 62 else { 63 item_no = abs(cnt); 64 ditems = it; 65 if (!items) 66 items = (unsigned char **)alloca((item_no * 2) * sizeof(unsigned char *)); 67 68 /* Initializes status */ 69 for (i = 0; i < item_no; i++) { 70 items[i*2] = ditems[i].prompt; 71 items[i*2 + 1] = ditems[i].title; 72 } 73 } 74 max_choice = MIN(menu_height, item_no); 75 76 tag_x = 0; 77 item_x = 0; 78 /* Find length of longest item in order to center menu */ 79 for (i = 0; i < item_no; i++) { 80 l = strlen(items[i * 2]); 81 for (j = 0; j < item_no; j++) { 82 k = strlen(items[j * 2 + 1]); 83 tag_x = MAX(tag_x, l + k + 2); 84 } 85 item_x = MAX(item_x, l); 86 } 87 if (height < 0) 88 height = strheight(prompt) + menu_height + 4 + 2; 89 if (width < 0) { 90 i = strwidth(prompt); 91 j = ((title != NULL) ? strwidth(title) : 0); 92 width = MAX(i, j); 93 width = MAX(width, tag_x + 4) + 4; 94 } 95 width = MAX(width, 24); 96 97 if (width > COLS) 98 width = COLS; 99 if (height > LINES) 100 height = LINES; 101 /* center dialog box on screen */ 102 x = DialogX ? DialogX : (COLS - width) / 2; 103 y = DialogY ? DialogY : (LINES - height) / 2; 104 105#ifdef HAVE_NCURSES 106 if (use_shadow) 107 draw_shadow(stdscr, y, x, height, width); 108#endif 109 dialog = newwin(height, width, y, x); 110 if (dialog == NULL) { 111 endwin(); 112 fprintf(stderr, "\nnewwin(%d,%d,%d,%d) failed, maybe wrong dims\n", height, width, y, x); 113 return -1; 114 } 115 keypad(dialog, TRUE); 116 117 draw_box(dialog, 0, 0, height, width, dialog_attr, border_attr); 118 wattrset(dialog, border_attr); 119 wmove(dialog, height - 3, 0); 120 waddch(dialog, ACS_LTEE); 121 for (i = 0; i < width - 2; i++) 122 waddch(dialog, ACS_HLINE); 123 wattrset(dialog, dialog_attr); 124 waddch(dialog, ACS_RTEE); 125 wmove(dialog, height - 2, 1); 126 for (i = 0; i < width - 2; i++) 127 waddch(dialog, ' '); 128 129 if (title != NULL) { 130 wattrset(dialog, title_attr); 131 wmove(dialog, 0, (width - strlen(title)) / 2 - 1); 132 waddch(dialog, ' '); 133 waddstr(dialog, title); 134 waddch(dialog, ' '); 135 } 136 wattrset(dialog, dialog_attr); 137 wmove(dialog, 1, 2); 138 print_autowrap(dialog, prompt, height - 1, width - 2, width, 1, 2, TRUE, FALSE); 139 140 menu_width = width - 6; 141 getyx(dialog, cur_y, cur_x); 142 box_y = cur_y + 1; 143 box_x = (width - menu_width) / 2 - 1; 144 145 /* create new window for the menu */ 146 menu = subwin(dialog, menu_height, menu_width, y + box_y + 1, x + box_x + 1); 147 if (menu == NULL) { 148 delwin(dialog); 149 endwin(); 150 fprintf(stderr, "\nsubwin(dialog,%d,%d,%d,%d) failed, maybe wrong dims\n", menu_height, menu_width, 151 y + box_y + 1, x + box_x + 1); 152 return -1; 153 } 154 keypad(menu, TRUE); 155 156 /* draw a box around the menu items */ 157 draw_box(dialog, box_y, box_x, menu_height+2, menu_width+2, menubox_border_attr, menubox_attr); 158 159 tag_x = menu_width > tag_x + 1 ? (menu_width - tag_x) / 2 : 1; 160 item_x = menu_width > item_x + 4 ? tag_x + item_x + 2 : menu_width - 3; 161 162 /* Print the menu */ 163 for (i = 0; i < max_choice; i++) 164 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); 165 wnoutrefresh(menu); 166 print_arrows(dialog, scroll, menu_height, item_no, box_x, box_y, tag_x, cur_x, cur_y); 167 168 display_helpline(dialog, height - 1, width); 169 170 x = width / 2 - 11; 171 y = height - 2; 172 173 if (ditems && result) { 174 cancelButton = toupper(ditems[CANCEL_BUTTON].prompt[0]); 175 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); 176 okButton = toupper(ditems[OK_BUTTON].prompt[0]); 177 print_button(dialog, ditems[OK_BUTTON].prompt, y, x, ditems[OK_BUTTON].checked ? ditems[OK_BUTTON].checked(&ditems[OK_BUTTON]) : TRUE); 178 } 179 else { 180 cancelButton = 'C'; 181 print_button(dialog, "Cancel", y, x + 14, FALSE); 182 okButton = 'O'; 183 print_button(dialog, " OK ", y, x, TRUE); 184 } 185 186 wrefresh(dialog); 187 while (key != ESC) { 188 key = wgetch(dialog); 189 190 /* Shortcut to OK? */ 191 if (toupper(key) == okButton) { 192 if (ditems) { 193 if (result && ditems[OK_BUTTON].fire) { 194 int status; 195 WINDOW *save; 196 197 save = dupwin(newscr); 198 status = ditems[OK_BUTTON].fire(&ditems[OK_BUTTON]); 199 if (status & DITEM_RESTORE) { 200 touchwin(save); 201 wrefresh(save); 202 } 203 delwin(save); 204 } 205 } 206 else if (result) 207 strcpy(result, items[(scroll + choice) * 2]); 208 rval = 0; 209 key = ESC; /* Punt! */ 210 break; 211 } 212 213 /* Shortcut to cancel? */ 214 if (toupper(key) == cancelButton) { 215 if (ditems && result && ditems[CANCEL_BUTTON].fire) { 216 int status; 217 WINDOW *save; 218 219 save = dupwin(newscr); 220 status = ditems[CANCEL_BUTTON].fire(&ditems[CANCEL_BUTTON]); 221 if (status & DITEM_RESTORE) { 222 touchwin(save); 223 wrefresh(save); 224 } 225 delwin(save); 226 } 227 rval = 1; 228 key = ESC; /* Run away! */ 229 break; 230 } 231 232 /* Check if key pressed matches first character of any item tag in menu */ 233 for (i = 0; i < max_choice; i++) 234 if (key < 0x100 && key != ' ' && toupper(key) == toupper(items[(scroll + i) * 2][0])) 235 break; 236 237 if (i < max_choice || (key >= '1' && key <= MIN('9', '0'+max_choice)) || KEY_IS_UP(key) || KEY_IS_DOWN(key)) { 238 if (key >= '1' && key <= MIN('9', '0'+max_choice)) 239 i = key - '1'; 240 else if (KEY_IS_UP(key)) { 241 if (!choice) { 242 if (scroll) { 243 /* Scroll menu down */ 244 getyx(dialog, cur_y, cur_x); /* Save cursor position */ 245 if (menu_height > 1) { 246 /* De-highlight current first item before scrolling down */ 247 print_item(menu, items[scroll * 2], items[scroll * 2 + 1], 0, FALSE, DREF(ditems, scroll), menu_width, tag_x, item_x); 248 scrollok(menu, TRUE); 249 wscrl(menu, -1); 250 scrollok(menu, FALSE); 251 } 252 scroll--; 253 print_item(menu, items[scroll * 2], items[scroll * 2 + 1], 0, TRUE, DREF(ditems, scroll), menu_width, tag_x, item_x); 254 wnoutrefresh(menu); 255 print_arrows(dialog, scroll, menu_height, item_no, box_x, box_y, tag_x, cur_x, cur_y); 256 wrefresh(dialog); 257 } 258 continue; /* wait for another key press */ 259 } 260 else 261 i = choice - 1; 262 } 263 else if (KEY_IS_DOWN(key)) { 264 if (choice == max_choice - 1) { 265 if (scroll + choice < item_no - 1) { 266 /* Scroll menu up */ 267 getyx(dialog, cur_y, cur_x); /* Save cursor position */ 268 if (menu_height > 1) { 269 /* De-highlight current last item before scrolling up */ 270 print_item(menu, items[(scroll + max_choice - 1) * 2], 271 items[(scroll + max_choice - 1) * 2 + 1], 272 max_choice-1, FALSE, DREF(ditems, scroll + max_choice - 1), menu_width, tag_x, item_x); 273 scrollok(menu, TRUE); 274 scroll(menu); 275 scrollok(menu, FALSE); 276 } 277 scroll++; 278 print_item(menu, items[(scroll + max_choice - 1) * 2], 279 items[(scroll + max_choice - 1) * 2 + 1], 280 max_choice - 1, TRUE, DREF(ditems, scroll + max_choice - 1), menu_width, tag_x, item_x); 281 wnoutrefresh(menu); 282 print_arrows(dialog, scroll, menu_height, item_no, box_x, box_y, tag_x, cur_x, cur_y); 283 wrefresh(dialog); 284 } 285 continue; /* wait for another key press */ 286 } 287 else 288 i = choice + 1; 289 } 290 291 if (i != choice) { 292 /* De-highlight current item */ 293 getyx(dialog, cur_y, cur_x); /* Save cursor position */ 294 print_item(menu, items[(scroll + choice) * 2], items[(scroll + choice) * 2 + 1], choice, FALSE, DREF(ditems, scroll + choice), menu_width, tag_x, item_x); 295 296 /* Highlight new item */ 297 choice = i; 298 print_item(menu, items[(scroll + choice) * 2], items[(scroll + choice) * 2 + 1], choice, TRUE, DREF(ditems, scroll + choice), menu_width, tag_x, item_x); 299 wnoutrefresh(menu); 300 wmove(dialog, cur_y, cur_x); /* Restore cursor to previous position */ 301 wrefresh(dialog); 302 } 303 continue; /* wait for another key press */ 304 } 305 306 switch (key) { 307 case KEY_PPAGE: 308 if (scroll > height - 4) { /* can we go up? */ 309 scroll -= (height - 4); 310 } else { 311 scroll = 0; 312 } 313 redraw_menu = TRUE; 314 break; 315 316 case KEY_NPAGE: 317 if (scroll + menu_height >= item_no-1 - menu_height) { /* can we go down a full page? */ 318 scroll = item_no - menu_height; 319 if (scroll < 0) 320 scroll = 0; 321 } else { 322 scroll += menu_height; 323 } 324 redraw_menu = TRUE; 325 break; 326 327 case KEY_HOME: 328 scroll = 0; 329 choice = 0; 330 redraw_menu = TRUE; 331 break; 332 333 case KEY_END: 334 scroll = item_no - menu_height; 335 if (scroll < 0) 336 scroll = 0; 337 choice = max_choice - 1; 338 redraw_menu = TRUE; 339 break; 340 341 case KEY_BTAB: 342 case TAB: 343 case KEY_LEFT: 344 case KEY_RIGHT: 345 button = !button; 346 if (ditems && result) { 347 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); 348 print_button(dialog, ditems[OK_BUTTON].prompt, y, x, ditems[OK_BUTTON].checked ? ditems[OK_BUTTON].checked(&ditems[OK_BUTTON]) : !button); 349 ok_space = 1; 350 cancel_space = strlen(ditems[OK_BUTTON].prompt) + 6; 351 } 352 else { 353 print_button(dialog, "Cancel", y, x + 14, button); 354 print_button(dialog, " OK ", y, x, !button); 355 ok_space = 3; 356 cancel_space = 15; 357 } 358 if (button) 359 wmove(dialog, y, x+cancel_space); 360 else 361 wmove(dialog, y, x+ok_space); 362 wrefresh(dialog); 363 break; 364 365 case ' ': 366 case '\r': 367 case '\n': 368 if (!button) { 369 /* A fire routine can do just about anything to the screen, so be prepared 370 to accept some hints as to what to do in the aftermath. */ 371 if (ditems) { 372 if (ditems[scroll + choice].fire) { 373 int status; 374 WINDOW *save; 375 376 save = dupwin(newscr); 377 status = ditems[scroll + choice].fire(&ditems[scroll + choice]); 378 if (status & DITEM_RESTORE) { 379 touchwin(save); 380 wrefresh(save); 381 } 382 delwin(save); 383 if (status & DITEM_CONTINUE) 384 continue; 385 else if (status & DITEM_LEAVE_MENU) { 386 /* Allow a fire action to take us out of the menu */ 387 key = ESC; 388 break; 389 } 390 else if (status & DITEM_RECREATE) { 391 delwin(menu); 392 delwin(dialog); 393 dialog_clear(); 394 goto draw; 395 } 396 } 397 } 398 else if (result) 399 strcpy(result, items[(scroll+choice)*2]); 400 } 401 rval = button; 402 key = ESC; 403 break; 404 405 case ESC: 406 rval = -1; 407 break; 408 409 case KEY_F(1): 410 case '?': 411 display_helpfile(); 412 break; 413 } 414 415 /* save info about menu item position */ 416 if (ch) 417 *ch = choice; 418 if (sc) 419 *sc = scroll; 420 421 if (redraw_menu) { 422 for (i = 0; i < max_choice; i++) { 423 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); 424 } 425 wnoutrefresh(menu); 426 getyx(dialog, cur_y, cur_x); /* Save cursor position */ 427 print_arrows(dialog, scroll, menu_height, item_no, box_x, box_y, tag_x, cur_x, cur_y); 428 wmove(dialog, cur_y, cur_x); /* Restore cursor to previous position */ 429 wrefresh(dialog); 430 redraw_menu = FALSE; 431 } 432 } 433 delwin(menu); 434 delwin(dialog); 435 return rval; 436} 437 438 439/* 440 * Print menu item 441 */ 442static void 443print_item(WINDOW *win, unsigned char *tag, unsigned char *item, int choice, int selected, dialogMenuItem *me, int menu_width, int tag_x, int item_x) 444{ 445 int i; 446 447 if (tag == NULL) 448 errx(1, "bad parameter to print_item()\n"); 449 450 /* Clear 'residue' of last item */ 451 wattrset(win, menubox_attr); 452 wmove(win, choice, 0); 453 for (i = 0; i < menu_width; i++) 454 waddch(win, ' '); 455 wmove(win, choice, tag_x); 456 wattrset(win, selected ? tag_key_selected_attr : tag_key_attr); 457 waddch(win, tag[0]); 458 wattrset(win, selected ? tag_selected_attr : tag_attr); 459 waddnstr(win, tag + 1, item_x - tag_x - 3); 460 wmove(win, choice, item_x); 461 wattrset(win, selected ? item_selected_attr : item_attr); 462 waddnstr(win, item, menu_width - item_x - 1); 463 /* If have a selection handler for this, call it */ 464 if (me && me->selected) { 465 wrefresh(win); 466 me->selected(me, selected); 467 } 468} 469/* End of print_item() */ 470