1 /* $NetBSD: menu.c,v 1.16 2003/03/09 01:08:48 lukem Exp $ */ 2 3 /*- 4 * Copyright (c) 1998-1999 Brett Lymn (blymn@baea.com.au, brett_lymn@yahoo.com.au) 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. The name of the author may not be used to endorse or promote products 13 * derived from this software without specific prior written permission 14 * 15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 16 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 17 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 18 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 19 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 20 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 21 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 22 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 24 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 * 26 * 27 */ 28 29 #include <sys/cdefs.h> 30 __RCSID("$NetBSD: menu.c,v 1.16 2003/03/09 01:08:48 lukem Exp $"); 31 32 #include <ctype.h> 33 #include <menu.h> 34 #include <string.h> 35 #include <stdlib.h> 36 #include "internals.h" 37 38 MENU _menui_default_menu = { 39 16, /* number of item rows that will fit in window */ 40 1, /* number of columns of items that will fit in window */ 41 0, /* number of rows of items we have */ 42 0, /* number of columns of items we have */ 43 0, /* current cursor row */ 44 0, /* current cursor column */ 45 {NULL, 0}, /* mark string */ 46 {NULL, 0}, /* unmark string */ 47 O_ONEVALUE, /* menu options */ 48 NULL, /* the pattern buffer */ 49 0, /* length of pattern buffer */ 50 0, /* the length of matched buffer */ 51 0, /* is the menu posted? */ 52 A_REVERSE, /* menu foreground */ 53 A_NORMAL, /* menu background */ 54 A_UNDERLINE, /* unselectable menu item */ 55 ' ', /* filler between name and description */ 56 NULL, /* user defined pointer */ 57 0, /* top row of menu */ 58 0, /* widest item in the menu */ 59 0, /* the width of a menu column */ 60 0, /* number of items attached to the menu */ 61 NULL, /* items in the menu */ 62 0, /* current menu item */ 63 0, /* currently in a hook function */ 64 NULL, /* function called when menu posted */ 65 NULL, /* function called when menu is unposted */ 66 NULL, /* function called when current item changes */ 67 NULL, /* function called when current item changes */ 68 NULL, /* the menu window */ 69 NULL, /* the menu subwindow */ 70 NULL, /* the window to write to */ 71 }; 72 73 74 75 /* 76 * Set the menu mark character 77 */ 78 int 79 set_menu_mark(MENU *m, char *mark) 80 { 81 MENU *menu = m; 82 83 if (m == NULL) menu = &_menui_default_menu; 84 85 /* if there was an old mark string, free it first */ 86 if (menu->mark.string != NULL) free(menu->mark.string); 87 88 if ((menu->mark.string = (char *) malloc(strlen(mark) + 1)) == NULL) 89 return E_SYSTEM_ERROR; 90 91 strcpy(menu->mark.string, mark); 92 menu->mark.length = strlen(mark); 93 94 /* max item size may have changed - recalculate. */ 95 _menui_max_item_size(menu); 96 return E_OK; 97 } 98 99 /* 100 * Return the menu mark string for the menu. 101 */ 102 char * 103 menu_mark(MENU *menu) 104 { 105 if (menu == NULL) 106 return _menui_default_menu.mark.string; 107 else 108 return menu->mark.string; 109 } 110 111 /* 112 * Set the menu unmark character 113 */ 114 int 115 set_menu_unmark(MENU *m, char *mark) 116 { 117 MENU *menu = m; 118 119 if (m == NULL) menu = &_menui_default_menu; 120 121 /* if there was an old mark string, free it first */ 122 if (menu->unmark.string != NULL) free(menu->unmark.string); 123 124 if ((menu->unmark.string = (char *) malloc(strlen(mark) + 1)) == NULL) 125 return E_SYSTEM_ERROR; 126 127 strcpy(menu->unmark.string, mark); 128 menu->unmark.length = strlen(mark); 129 /* max item size may have changed - recalculate. */ 130 _menui_max_item_size(menu); 131 return E_OK; 132 } 133 134 /* 135 * Return the menu unmark string for the menu. 136 */ 137 char * 138 menu_unmark(menu) 139 MENU *menu; 140 { 141 if (menu == NULL) 142 return _menui_default_menu.unmark.string; 143 else 144 return menu->unmark.string; 145 } 146 147 /* 148 * Set the menu window to the window passed. 149 */ 150 int 151 set_menu_win(MENU *menu, WINDOW *win) 152 { 153 if (menu == NULL) { 154 _menui_default_menu.menu_win = win; 155 _menui_default_menu.scrwin = win; 156 } else { 157 if (menu->posted == TRUE) { 158 return E_POSTED; 159 } else { 160 menu->menu_win = win; 161 menu->scrwin = win; 162 } 163 } 164 165 return E_OK; 166 } 167 168 /* 169 * Return the pointer to the menu window 170 */ 171 WINDOW * 172 menu_win(MENU *menu) 173 { 174 if (menu == NULL) 175 return _menui_default_menu.menu_win; 176 else 177 return menu->menu_win; 178 } 179 180 /* 181 * Set the menu subwindow for the menu. 182 */ 183 int 184 set_menu_sub(menu, sub) 185 MENU *menu; 186 WINDOW *sub; 187 { 188 if (menu == NULL) { 189 _menui_default_menu.menu_subwin = sub; 190 _menui_default_menu.scrwin = sub; 191 } else { 192 if (menu->posted == TRUE) 193 return E_POSTED; 194 195 menu->menu_subwin = sub; 196 menu->scrwin = sub; 197 } 198 199 return E_OK; 200 } 201 202 /* 203 * Return the subwindow pointer for the menu 204 */ 205 WINDOW * 206 menu_sub(MENU *menu) 207 { 208 if (menu == NULL) 209 return _menui_default_menu.menu_subwin; 210 else 211 return menu->menu_subwin; 212 } 213 214 /* 215 * Set the maximum number of rows and columns of items that may be displayed. 216 */ 217 int 218 set_menu_format(MENU *param_menu, int rows, int cols) 219 { 220 MENU *menu = (param_menu != NULL) ? param_menu : &_menui_default_menu; 221 222 menu->rows = rows; 223 menu->cols = cols; 224 225 if (menu->items != NULL) 226 /* recalculate the item neighbours */ 227 return _menui_stitch_items(menu); 228 229 return E_OK; 230 } 231 232 /* 233 * Return the max number of rows and cols that may be displayed. 234 */ 235 void 236 menu_format(MENU *param_menu, int *rows, int *cols) 237 { 238 MENU *menu = (param_menu != NULL) ? param_menu : &_menui_default_menu; 239 240 *rows = menu->rows; 241 *cols = menu->cols; 242 } 243 244 /* 245 * Set the user defined function to call when a menu is posted. 246 */ 247 int 248 set_menu_init(MENU *menu, Menu_Hook func) 249 { 250 if (menu == NULL) 251 _menui_default_menu.menu_init = func; 252 else 253 menu->menu_init = func; 254 return E_OK; 255 } 256 257 /* 258 * Return the pointer to the menu init function. 259 */ 260 Menu_Hook 261 menu_init(MENU *menu) 262 { 263 if (menu == NULL) 264 return _menui_default_menu.menu_init; 265 else 266 return menu->menu_init; 267 } 268 269 /* 270 * Set the user defined function called when a menu is unposted. 271 */ 272 int 273 set_menu_term(MENU *menu, Menu_Hook func) 274 { 275 if (menu == NULL) 276 _menui_default_menu.menu_term = func; 277 else 278 menu->menu_term = func; 279 return E_OK; 280 } 281 282 /* 283 * Return the user defined menu termination function pointer. 284 */ 285 Menu_Hook 286 menu_term(MENU *menu) 287 { 288 if (menu == NULL) 289 return _menui_default_menu.menu_term; 290 else 291 return menu->menu_term; 292 } 293 294 /* 295 * Return the current menu options set. 296 */ 297 OPTIONS 298 menu_opts(MENU *menu) 299 { 300 if (menu == NULL) 301 return _menui_default_menu.opts; 302 else 303 return menu->opts; 304 } 305 306 /* 307 * Set the menu options to the given options. 308 */ 309 int 310 set_menu_opts(MENU *param_menu, OPTIONS opts) 311 { 312 int i, seen; 313 MENU *menu = (param_menu != NULL) ? param_menu : &_menui_default_menu; 314 OPTIONS old_opts = menu->opts; 315 316 menu->opts = opts; 317 318 /* 319 * If the radio option is selected then make sure only one 320 * item is actually selected in the items. 321 */ 322 if (((opts & O_RADIO) == O_RADIO) && (menu->items != NULL) && 323 (menu->items[0] != NULL)) { 324 seen = 0; 325 for (i = 0; i < menu->item_count; i++) { 326 if (menu->items[i]->selected == 1) { 327 if (seen == 0) { 328 seen = 1; 329 } else { 330 menu->items[i]->selected = 0; 331 } 332 } 333 } 334 335 /* if none selected, select the first item */ 336 if (seen == 0) 337 menu->items[0]->selected = 1; 338 } 339 340 if ((menu->opts & O_ROWMAJOR) != (old_opts & O_ROWMAJOR)) 341 /* changed menu layout - need to recalc neighbours */ 342 _menui_stitch_items(menu); 343 344 return E_OK; 345 } 346 347 /* 348 * Turn on the options in menu given by opts. 349 */ 350 int 351 menu_opts_on(MENU *param_menu, OPTIONS opts) 352 { 353 int i, seen; 354 MENU *menu = (param_menu != NULL) ? param_menu : &_menui_default_menu; 355 OPTIONS old_opts = menu->opts; 356 357 menu->opts |= opts; 358 359 /* 360 * If the radio option is selected then make sure only one 361 * item is actually selected in the items. 362 */ 363 if (((opts & O_RADIO) == O_RADIO) && (menu->items != NULL) && 364 (menu->items[0] != NULL)) { 365 seen = 0; 366 for (i = 0; i < menu->item_count; i++) { 367 if (menu->items[i]->selected == 1) { 368 if (seen == 0) { 369 seen = 1; 370 } else { 371 menu->items[i]->selected = 0; 372 } 373 } 374 } 375 /* if none selected then select the top item */ 376 if (seen == 0) 377 menu->items[0]->selected = 1; 378 } 379 380 if ((menu->items != NULL) && 381 (menu->opts & O_ROWMAJOR) != (old_opts & O_ROWMAJOR)) 382 /* changed menu layout - need to recalc neighbours */ 383 _menui_stitch_items(menu); 384 385 return E_OK; 386 } 387 388 /* 389 * Turn off the menu options given in opts. 390 */ 391 int 392 menu_opts_off(MENU *param_menu, OPTIONS opts) 393 { 394 MENU *menu = (param_menu != NULL) ? param_menu : &_menui_default_menu; 395 OPTIONS old_opts = menu->opts; 396 397 menu->opts &= ~(opts); 398 399 if ((menu->items != NULL ) && 400 (menu->opts & O_ROWMAJOR) != (old_opts & O_ROWMAJOR)) 401 /* changed menu layout - need to recalc neighbours */ 402 _menui_stitch_items(menu); 403 404 return E_OK; 405 } 406 407 /* 408 * Return the menu pattern buffer. 409 */ 410 char * 411 menu_pattern(MENU *menu) 412 { 413 if (menu == NULL) 414 return _menui_default_menu.pattern; 415 else 416 return menu->pattern; 417 } 418 419 /* 420 * Set the menu pattern buffer to pat and attempt to match the pattern in 421 * the item list. 422 */ 423 int 424 set_menu_pattern(MENU *param_menu, char *pat) 425 { 426 MENU *menu = (param_menu != NULL) ? param_menu : &_menui_default_menu; 427 char *p = pat; 428 429 /* check pattern is all printable characters */ 430 while (*p) 431 if (!isprint((unsigned char) *p++)) return E_BAD_ARGUMENT; 432 433 if ((menu->pattern = (char *) realloc(menu->pattern, 434 sizeof(char) * strlen(pat) + 1)) == NULL) 435 return E_SYSTEM_ERROR; 436 437 strcpy(menu->pattern, pat); 438 menu->plen = strlen(pat); 439 440 /* search item list for pat here */ 441 return _menui_match_items(menu, MATCH_FORWARD, &menu->cur_item); 442 } 443 444 /* 445 * Allocate a new menu structure and fill it in. 446 */ 447 MENU * 448 new_menu(ITEM **items) 449 { 450 MENU *the_menu; 451 452 if ((the_menu = (MENU *)malloc(sizeof(MENU))) == NULL) 453 return NULL; 454 455 /* copy the defaults */ 456 (void)memcpy(the_menu, &_menui_default_menu, sizeof(MENU)); 457 458 /* set a default window if none already set. */ 459 if (the_menu->menu_win == NULL) 460 the_menu->scrwin = stdscr; 461 462 /* make a private copy of the mark string */ 463 if (_menui_default_menu.mark.string != NULL) { 464 if ((the_menu->mark.string = 465 (char *) malloc((unsigned) _menui_default_menu.mark.length + 1)) 466 == NULL) { 467 free(the_menu); 468 return NULL; 469 } 470 471 strlcpy(the_menu->mark.string, _menui_default_menu.mark.string, 472 (unsigned) _menui_default_menu.mark.length + 1); 473 } 474 475 /* make a private copy of the unmark string too */ 476 if (_menui_default_menu.unmark.string != NULL) { 477 if ((the_menu->unmark.string = 478 (char *) malloc((unsigned) _menui_default_menu.unmark.length + 1)) 479 == NULL) { 480 free(the_menu); 481 return NULL; 482 } 483 484 strlcpy(the_menu->unmark.string, 485 _menui_default_menu.unmark.string, 486 (unsigned) _menui_default_menu.unmark.length+ 1 ); 487 } 488 489 /* now attach the items, if any */ 490 if (items != NULL) { 491 if(set_menu_items(the_menu, items) < 0) { 492 if (the_menu->mark.string != NULL) 493 free(the_menu->mark.string); 494 if (the_menu->unmark.string != NULL) 495 free(the_menu->unmark.string); 496 free(the_menu); 497 return NULL; 498 } 499 } 500 501 return the_menu; 502 } 503 504 /* 505 * Free up storage allocated to the menu object and destroy it. 506 */ 507 int 508 free_menu(MENU *menu) 509 { 510 int i; 511 512 if (menu == NULL) 513 return E_BAD_ARGUMENT; 514 515 if (menu->posted != 0) 516 return E_POSTED; 517 518 if (menu->pattern != NULL) 519 free(menu->pattern); 520 521 if (menu->mark.string != NULL) 522 free(menu->mark.string); 523 524 if (menu->items != NULL) { 525 /* disconnect the items from this menu */ 526 for (i = 0; i < menu->item_count; i++) { 527 menu->items[i]->parent = NULL; 528 } 529 } 530 531 free(menu); 532 return E_OK; 533 } 534 535 /* 536 * Calculate the minimum window size for the menu. 537 */ 538 int 539 scale_menu(MENU *param_menu, int *rows, int *cols) 540 { 541 MENU *menu = (param_menu != NULL) ? param_menu : &_menui_default_menu; 542 543 if (menu->items == NULL) 544 return E_BAD_ARGUMENT; 545 546 /* calculate the max item size */ 547 _menui_max_item_size(menu); 548 549 *rows = menu->rows; 550 *cols = menu->cols * menu->max_item_width; 551 552 /* 553 * allow for spacing between columns... 554 */ 555 *cols += (menu->cols - 1); 556 557 return E_OK; 558 } 559 560 /* 561 * Set the menu item list to the one given. 562 */ 563 int 564 set_menu_items(MENU *param_menu, ITEM **items) 565 { 566 MENU *menu = (param_menu != NULL) ? param_menu : &_menui_default_menu; 567 int i, new_count = 0, sel_count = 0; 568 569 /* don't change if menu is posted */ 570 if (menu->posted == 1) 571 return E_POSTED; 572 573 /* count the new items and validate none are connected already */ 574 while (items[new_count] != NULL) { 575 if ((items[new_count]->parent != NULL) && 576 (items[new_count]->parent != menu)) 577 return E_CONNECTED; 578 if (items[new_count]->selected == 1) 579 sel_count++; 580 new_count++; 581 } 582 583 /* 584 * don't allow multiple selected items if menu is radio 585 * button style. 586 */ 587 if (((menu->opts & O_RADIO) == O_RADIO) && 588 (sel_count > 1)) 589 return E_BAD_ARGUMENT; 590 591 /* if there were items connected then disconnect them. */ 592 if (menu->items != NULL) { 593 for (i = 0; i < menu->item_count; i++) { 594 menu->items[i]->parent = NULL; 595 menu->items[i]->index = -1; 596 } 597 } 598 599 menu->item_count = new_count; 600 601 /* connect the new items to the menu */ 602 for (i = 0; i < new_count; i++) { 603 items[i]->parent = menu; 604 items[i]->index = i; 605 } 606 607 menu->items = items; 608 menu->cur_item = 0; /* reset current item just in case */ 609 menu->top_row = 0; /* and the top row too */ 610 if (menu->pattern != NULL) { /* and the pattern buffer....sigh */ 611 free(menu->pattern); 612 menu->plen = 0; 613 menu->match_len = 0; 614 } 615 616 /* 617 * make sure at least one item is selected on a radio 618 * button style menu. 619 */ 620 if (((menu->opts & O_RADIO) == O_RADIO) && (sel_count == 0)) 621 menu->items[0]->selected = 1; 622 623 624 _menui_stitch_items(menu); /* recalculate the item neighbours */ 625 626 return E_OK; 627 } 628 629 /* 630 * Return the pointer to the menu items array. 631 */ 632 ITEM ** 633 menu_items(MENU *menu) 634 { 635 if (menu == NULL) 636 return _menui_default_menu.items; 637 else 638 return menu->items; 639 } 640 641 /* 642 * Return the count of items connected to the menu 643 */ 644 int 645 item_count(MENU *menu) 646 { 647 if (menu == NULL) 648 return _menui_default_menu.item_count; 649 else 650 return menu->item_count; 651 } 652 653 /* 654 * Set the menu top row to be the given row. The current item becomes the 655 * leftmost item on that row in the menu. 656 */ 657 int 658 set_top_row(MENU *param_menu, int row) 659 { 660 MENU *menu = (param_menu != NULL) ? param_menu : &_menui_default_menu; 661 int i, cur_item, state = E_SYSTEM_ERROR; 662 663 if (row > menu->item_rows) 664 return E_BAD_ARGUMENT; 665 666 if (menu->items == NULL) 667 return E_NOT_CONNECTED; 668 669 if (menu->in_init == 1) 670 return E_BAD_STATE; 671 672 cur_item = 0; 673 674 for (i = 0; i < menu->item_count; i++) { 675 /* search for first item that matches row - this will be 676 the current item. */ 677 if (row == menu->items[i]->row) { 678 cur_item = i; 679 state = E_OK; 680 break; /* found what we want - no need to go further */ 681 } 682 } 683 684 menu->in_init = 1; /* just in case we call the init/term routines */ 685 686 if (menu->posted == 1) { 687 if (menu->menu_term != NULL) 688 menu->menu_term(menu); 689 if (menu->item_term != NULL) 690 menu->item_term(menu); 691 } 692 693 menu->cur_item = cur_item; 694 menu->top_row = row; 695 696 if (menu->posted == 1) { 697 if (menu->menu_init != NULL) 698 menu->menu_init(menu); 699 if (menu->item_init != NULL) 700 menu->item_init(menu); 701 } 702 703 menu->in_init = 0; 704 705 /* this should always be E_OK unless we are really screwed up */ 706 return state; 707 } 708 709 /* 710 * Return the current top row number. 711 */ 712 int 713 top_row(MENU *param_menu) 714 { 715 MENU *menu = (param_menu != NULL) ? param_menu : &_menui_default_menu; 716 717 if (menu->items == NULL) 718 return E_NOT_CONNECTED; 719 720 return menu->top_row; 721 } 722 723 /* 724 * Position the cursor at the correct place in the menu. 725 * 726 */ 727 int 728 pos_menu_cursor(MENU *menu) 729 { 730 int movx, maxmark; 731 732 if (menu == NULL) 733 return E_BAD_ARGUMENT; 734 735 maxmark = max(menu->mark.length, menu->unmark.length); 736 movx = maxmark + (menu->items[menu->cur_item]->col 737 * (menu->col_width + 1)); 738 739 if (menu->match_len > 0) 740 movx += menu->match_len - 1; 741 742 wmove(menu->scrwin, 743 menu->items[menu->cur_item]->row - menu->top_row, movx); 744 745 return E_OK; 746 } 747