1 /* $NetBSD: menu.c,v 1.5 1999/12/22 14:38:12 kleink 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 withough 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 <ctype.h> 30 #include <menu.h> 31 #include <string.h> 32 #include <stdlib.h> 33 #include "internals.h" 34 35 MENU _menui_default_menu = { 36 16, /* number of item rows that will fit in window */ 37 1, /* number of columns of items that will fit in window */ 38 0, /* number of rows of items we have */ 39 0, /* number of columns of items we have */ 40 0, /* current cursor row */ 41 0, /* current cursor column */ 42 {NULL, 0}, /* mark string */ 43 {NULL, 0}, /* unmark string */ 44 O_ONEVALUE, /* menu options */ 45 NULL, /* the pattern buffer */ 46 0, /* length of pattern buffer */ 47 0, /* the length of matched buffer */ 48 0, /* is the menu posted? */ 49 A_REVERSE, /* menu foreground */ 50 A_NORMAL, /* menu background */ 51 A_UNDERLINE, /* unselectable menu item */ 52 ' ', /* filler between name and description */ 53 NULL, /* user defined pointer */ 54 0, /* top row of menu */ 55 0, /* widest item in the menu */ 56 0, /* the width of a menu column */ 57 0, /* number of items attached to the menu */ 58 NULL, /* items in the menu */ 59 0, /* current menu item */ 60 0, /* currently in a hook function */ 61 NULL, /* function called when menu posted */ 62 NULL, /* function called when menu is unposted */ 63 NULL, /* function called when current item changes */ 64 NULL, /* function called when current item changes */ 65 NULL, /* the menu window */ 66 NULL, /* the menu subwindow */ 67 0 /* subwindow was created by library call */ 68 }; 69 70 71 72 /* 73 * Set the menu mark character 74 */ 75 int 76 set_menu_mark(m, mark) 77 MENU *m; 78 char *mark; 79 { 80 MENU *menu = m; 81 82 if (m == NULL) menu = &_menui_default_menu; 83 84 /* if there was an old mark string, free it first */ 85 if (menu->mark.string != NULL) free(menu->mark.string); 86 87 if ((menu->mark.string = (char *) malloc(strlen(mark))) == NULL) 88 return E_SYSTEM_ERROR; 89 90 strcpy(menu->mark.string, mark); 91 menu->mark.length = strlen(mark); 92 93 /* max item size may have changed - recalculate. */ 94 _menui_max_item_size(menu); 95 return E_OK; 96 } 97 98 /* 99 * Return the menu mark string for the menu. 100 */ 101 char * 102 menu_mark(menu) 103 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(m, mark) 116 MENU *m; 117 char *mark; 118 { 119 MENU *menu = m; 120 121 if (m == NULL) menu = &_menui_default_menu; 122 123 /* if there was an old mark string, free it first */ 124 if (menu->unmark.string != NULL) free(menu->unmark.string); 125 126 if ((menu->unmark.string = (char *) malloc(strlen(mark))) == NULL) 127 return E_SYSTEM_ERROR; 128 129 strcpy(menu->unmark.string, mark); 130 menu->unmark.length = strlen(mark); 131 /* max item size may have changed - recalculate. */ 132 _menui_max_item_size(menu); 133 return E_OK; 134 } 135 136 /* 137 * Return the menu unmark string for the menu. 138 */ 139 char * 140 menu_unmark(menu) 141 MENU *menu; 142 { 143 if (menu == NULL) 144 return _menui_default_menu.unmark.string; 145 else 146 return menu->unmark.string; 147 } 148 149 /* 150 * Set the menu window to the window passed. 151 */ 152 int 153 set_menu_win(menu, win) 154 MENU *menu; 155 WINDOW *win; 156 { 157 if (menu == NULL) 158 _menui_default_menu.menu_win = win; 159 else 160 menu->menu_win = win; 161 return E_OK; 162 } 163 164 /* 165 * Return the pointer to the menu window 166 */ 167 WINDOW * 168 menu_win(menu) 169 MENU *menu; 170 { 171 if (menu == NULL) 172 return _menui_default_menu.menu_win; 173 else 174 return menu->menu_win; 175 } 176 177 /* 178 * Set the menu subwindow for the menu. 179 */ 180 int 181 set_menu_sub(menu, sub) 182 MENU *menu; 183 WINDOW *sub; 184 { 185 if (menu == NULL) 186 _menui_default_menu.menu_subwin = sub; 187 else 188 menu->menu_subwin = sub; 189 return E_OK; 190 } 191 192 /* 193 * Return the subwindow pointer for the menu 194 */ 195 WINDOW * 196 menu_sub(menu) 197 MENU *menu; 198 { 199 if (menu == NULL) 200 return _menui_default_menu.menu_subwin; 201 else 202 return menu->menu_subwin; 203 } 204 205 /* 206 * Set the maximum number of rows and columns of items that may be displayed. 207 */ 208 int 209 set_menu_format(param_menu, rows, cols) 210 MENU *param_menu; 211 int rows; 212 int cols; 213 { 214 MENU *menu = (param_menu != NULL) ? param_menu : &_menui_default_menu; 215 216 menu->rows = rows; 217 menu->cols = cols; 218 219 if (menu->items != NULL) 220 /* recalculate the item neighbours */ 221 return _menui_stitch_items(menu); 222 223 return E_OK; 224 } 225 226 /* 227 * Return the max number of rows and cols that may be displayed. 228 */ 229 void 230 menu_format(param_menu, rows, cols) 231 MENU *param_menu; 232 int *rows; 233 int *cols; 234 { 235 MENU *menu = (param_menu != NULL) ? param_menu : &_menui_default_menu; 236 237 *rows = menu->rows; 238 *cols = menu->cols; 239 } 240 241 /* 242 * Set the user defined function to call when a menu is posted. 243 */ 244 int 245 set_menu_init(menu, func) 246 MENU *menu; 247 Menu_Hook func; 248 { 249 if (menu == NULL) 250 _menui_default_menu.menu_init = func; 251 else 252 menu->menu_init = func; 253 return E_OK; 254 } 255 256 /* 257 * Return the pointer to the menu init function. 258 */ 259 Menu_Hook 260 menu_init(MENU *menu) 261 { 262 if (menu == NULL) 263 return _menui_default_menu.menu_init; 264 else 265 return menu->menu_init; 266 } 267 268 /* 269 * Set the user defined function called when a menu is unposted. 270 */ 271 int 272 set_menu_term(menu, func) 273 MENU *menu; 274 Menu_Hook func; 275 { 276 if (menu == NULL) 277 _menui_default_menu.menu_term = func; 278 else 279 menu->menu_term = func; 280 return E_OK; 281 } 282 283 /* 284 * Return the user defined menu termination function pointer. 285 */ 286 Menu_Hook 287 menu_term(menu) 288 MENU *menu; 289 { 290 if (menu == NULL) 291 return _menui_default_menu.menu_term; 292 else 293 return menu->menu_term; 294 } 295 296 /* 297 * Return the current menu options set. 298 */ 299 OPTIONS 300 menu_opts(menu) 301 MENU *menu; 302 { 303 if (menu == NULL) 304 return _menui_default_menu.opts; 305 else 306 return menu->opts; 307 } 308 309 /* 310 * Set the menu options to the given options. 311 */ 312 int 313 set_menu_opts(param_menu, opts) 314 MENU *param_menu; 315 OPTIONS opts; 316 { 317 MENU *menu = (param_menu != NULL) ? param_menu : &_menui_default_menu; 318 OPTIONS old_opts = menu->opts; 319 320 menu->opts = opts; 321 322 if ((menu->opts & O_ROWMAJOR) != (old_opts & O_ROWMAJOR)) 323 /* changed menu layout - need to recalc neighbours */ 324 _menui_stitch_items(menu); 325 326 return E_OK; 327 } 328 329 /* 330 * Turn on the options in menu given by opts. 331 */ 332 int 333 menu_opts_on(param_menu, opts) 334 MENU *param_menu; 335 OPTIONS opts; 336 { 337 MENU *menu = (param_menu != NULL) ? param_menu : &_menui_default_menu; 338 OPTIONS old_opts = menu->opts; 339 340 menu->opts |= opts; 341 342 if ((menu->items != NULL) && 343 (menu->opts & O_ROWMAJOR) != (old_opts & O_ROWMAJOR)) 344 /* changed menu layout - need to recalc neighbours */ 345 _menui_stitch_items(menu); 346 347 return E_OK; 348 } 349 350 /* 351 * Turn off the menu options given in opts. 352 */ 353 int 354 menu_opts_off(param_menu, opts) 355 MENU *param_menu; 356 OPTIONS opts; 357 { 358 MENU *menu = (param_menu != NULL) ? param_menu : &_menui_default_menu; 359 OPTIONS old_opts = menu->opts; 360 361 menu->opts &= ~(opts); 362 363 if ((menu->items != NULL ) && 364 (menu->opts & O_ROWMAJOR) != (old_opts & O_ROWMAJOR)) 365 /* changed menu layout - need to recalc neighbours */ 366 _menui_stitch_items(menu); 367 368 return E_OK; 369 } 370 371 /* 372 * Return the menu pattern buffer. 373 */ 374 char * 375 menu_pattern(menu) 376 MENU *menu; 377 { 378 if (menu == NULL) 379 return _menui_default_menu.pattern; 380 else 381 return menu->pattern; 382 } 383 384 /* 385 * Set the menu pattern buffer to pat and attempt to match the pattern in 386 * the item list. 387 */ 388 int 389 set_menu_pattern(param_menu, pat) 390 MENU *param_menu; 391 char *pat; 392 { 393 MENU *menu = (param_menu != NULL) ? param_menu : &_menui_default_menu; 394 char *p = pat; 395 396 /* check pattern is all printable characters */ 397 while (*p) 398 if (!isprint(*p++)) return E_BAD_ARGUMENT; 399 400 if ((menu->pattern = (char *) realloc(menu->pattern, 401 sizeof(char) * strlen(pat))) == NULL) 402 return E_SYSTEM_ERROR; 403 404 strcpy(menu->pattern, pat); 405 menu->plen = strlen(pat); 406 407 /* search item list for pat here */ 408 return _menui_match_items(menu, MATCH_FORWARD, &menu->cur_item); 409 } 410 411 /* 412 * Allocate a new menu structure and fill it in. 413 */ 414 MENU * 415 new_menu(items) 416 ITEM **items; 417 { 418 MENU *the_menu; 419 420 if ((the_menu = (MENU *)malloc(sizeof(MENU))) == NULL) 421 return NULL; 422 423 /* copy the defaults */ 424 (void)memcpy(the_menu, &_menui_default_menu, sizeof(MENU)); 425 426 /* set a default window if none already set. */ 427 if (the_menu->menu_win == NULL) 428 the_menu->menu_win = stdscr; 429 430 /* now attach the items, if any */ 431 if (items != NULL) { 432 if(set_menu_items(the_menu, items) < 0) { 433 free(the_menu); 434 return NULL; 435 } 436 } 437 438 return the_menu; 439 } 440 441 /* 442 * Free up storage allocated to the menu object and destroy it. 443 */ 444 int 445 free_menu(menu) 446 MENU *menu; 447 { 448 int i; 449 450 if (menu == NULL) 451 return E_BAD_ARGUMENT; 452 453 if (menu->posted != 0) 454 return E_POSTED; 455 456 if (menu->pattern != NULL) 457 free(menu->pattern); 458 459 if (menu->mark.string != NULL) 460 free(menu->mark.string); 461 462 if (menu->items != NULL) { 463 /* disconnect the items from this menu */ 464 for (i = 0; i < menu->item_count; i++) { 465 menu->items[i]->parent = NULL; 466 } 467 } 468 469 free(menu); 470 return E_OK; 471 } 472 473 /* 474 * Calculate the minimum window size for the menu. 475 */ 476 int 477 scale_menu(param_menu, rows, cols) 478 MENU *param_menu; 479 int *rows; 480 int *cols; 481 { 482 MENU *menu = (param_menu != NULL) ? param_menu : &_menui_default_menu; 483 484 if (menu->items == NULL) 485 return E_BAD_ARGUMENT; 486 487 /* calculate the max item size */ 488 _menui_max_item_size(menu); 489 490 *rows = menu->rows; 491 *cols = menu->cols * menu->max_item_width; 492 493 /* 494 * allow for spacing between columns... 495 */ 496 *cols += menu->cols - 1; 497 498 return E_OK; 499 } 500 501 /* 502 * Set the menu item list to the one given. 503 */ 504 int 505 set_menu_items(param_menu, items) 506 MENU *param_menu; 507 ITEM **items; 508 { 509 MENU *menu = (param_menu != NULL) ? param_menu : &_menui_default_menu; 510 int i, new_count = 0; 511 512 /* don't change if menu is posted */ 513 if (menu->posted == 1) 514 return E_POSTED; 515 516 /* count the new items and validate none are connected already */ 517 while (items[new_count] != NULL) { 518 if ((items[new_count]->parent != NULL) && 519 (items[new_count]->parent != menu)) 520 return E_CONNECTED; 521 new_count++; 522 } 523 524 525 /* if there were items connected then disconnect them. */ 526 if (menu->items != NULL) { 527 for (i = 0; i < menu->item_count; i++) { 528 menu->items[i]->parent = NULL; 529 menu->items[i]->index = -1; 530 } 531 } 532 533 menu->item_count = new_count; 534 535 /* connect the new items to the menu */ 536 for (i = 0; i < new_count; i++) { 537 items[i]->parent = menu; 538 items[i]->index = i; 539 } 540 541 menu->items = items; 542 menu->cur_item = 0; /* reset current item just in case */ 543 menu->top_row = 0; /* and the top row too */ 544 if (menu->pattern != NULL) { /* and the pattern buffer....sigh */ 545 free(menu->pattern); 546 menu->plen = 0; 547 menu->match_len = 0; 548 } 549 550 _menui_stitch_items(menu); /* recalculate the item neighbours */ 551 552 return E_OK; 553 } 554 555 /* 556 * Return the pointer to the menu items array. 557 */ 558 ITEM ** 559 menu_items(menu) 560 MENU *menu; 561 { 562 if (menu == NULL) 563 return _menui_default_menu.items; 564 else 565 return menu->items; 566 } 567 568 /* 569 * Return the count of items connected to the menu 570 */ 571 int 572 item_count(menu) 573 MENU *menu; 574 { 575 if (menu == NULL) 576 return _menui_default_menu.item_count; 577 else 578 return menu->item_count; 579 } 580 581 /* 582 * Set the menu top row to be the given row. The current item becomes the 583 * leftmost item on that row in the menu. 584 */ 585 int 586 set_top_row(param_menu, row) 587 MENU *param_menu; 588 int row; 589 { 590 MENU *menu = (param_menu != NULL) ? param_menu : &_menui_default_menu; 591 int i, cur_item, state = E_SYSTEM_ERROR; 592 593 if (row > menu->item_rows) 594 return E_BAD_ARGUMENT; 595 596 if (menu->items == NULL) 597 return E_NOT_CONNECTED; 598 599 if (menu->in_init == 1) 600 return E_BAD_STATE; 601 602 for (i = 0; i < menu->item_count; i++) { 603 /* search for first item that matches row - this will be 604 the current item. */ 605 if (row == menu->items[i]->row) { 606 cur_item = i; 607 state = E_OK; 608 break; /* found what we want - no need to go further */ 609 } 610 } 611 612 menu->in_init = 1; /* just in case we call the init/term routines */ 613 614 if (menu->posted == 1) { 615 if (menu->menu_term != NULL) 616 menu->menu_term(menu); 617 if (menu->item_term != NULL) 618 menu->item_term(menu); 619 } 620 621 menu->cur_item = cur_item; 622 menu->top_row = row; 623 624 if (menu->posted == 1) { 625 if (menu->menu_init != NULL) 626 menu->menu_init(menu); 627 if (menu->item_init != NULL) 628 menu->item_init(menu); 629 } 630 631 menu->in_init = 0; 632 633 /* this should always be E_OK unless we are really screwed up */ 634 return state; 635 } 636 637 /* 638 * Return the current top row number. 639 */ 640 int 641 top_row(param_menu) 642 MENU *param_menu; 643 { 644 MENU *menu = (param_menu != NULL) ? param_menu : &_menui_default_menu; 645 646 if (menu->items == NULL) 647 return E_NOT_CONNECTED; 648 649 return menu->top_row; 650 } 651 652 /* 653 * Position the cursor at the correct place in the menu. 654 * 655 */ 656 int 657 pos_menu_cursor(menu) 658 MENU *menu; 659 { 660 int movx, maxmark; 661 662 if (menu == NULL) 663 return E_BAD_ARGUMENT; 664 665 maxmark = max(menu->mark.length, menu->unmark.length); 666 movx = maxmark + (menu->items[menu->cur_item]->col 667 * (menu->col_width + 1)); 668 669 if (menu->match_len > 0) 670 movx += menu->match_len - 1; 671 672 wmove(menu->menu_subwin, 673 menu->items[menu->cur_item]->row - menu->top_row, movx); 674 675 return E_OK; 676 } 677