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