Home | History | Annotate | Line # | Download | only in libmenu
menu.c revision 1.15
      1 /*	$NetBSD: menu.c,v 1.15 2002/11/27 11:53:11 blymn Exp $	*/
      2 
      3 /*-
      4  * Copyright (c) 1998-1999 Brett Lymn (blymn (at) baea.com.au, brett_lymn (at) 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 	NULL,       /* the window to write to */
     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) + 1)) == 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) + 1)) == 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 		_menui_default_menu.scrwin = win;
    153 	} else {
    154 		if (menu->posted == TRUE) {
    155 			return E_POSTED;
    156 		} else {
    157 			menu->menu_win = win;
    158 			menu->scrwin = win;
    159 		}
    160 	}
    161 
    162         return E_OK;
    163 }
    164 
    165 /*
    166  * Return the pointer to the menu window
    167  */
    168 WINDOW *
    169 menu_win(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 		_menui_default_menu.scrwin = sub;
    188 	} else {
    189 		if (menu->posted == TRUE)
    190 			return E_POSTED;
    191 
    192 		menu->menu_subwin = sub;
    193 		menu->scrwin = sub;
    194 	}
    195 
    196         return E_OK;
    197 }
    198 
    199 /*
    200  * Return the subwindow pointer for the menu
    201  */
    202 WINDOW *
    203 menu_sub(MENU *menu)
    204 {
    205 	if (menu == NULL)
    206 		return _menui_default_menu.menu_subwin;
    207 	else
    208 		return menu->menu_subwin;
    209 }
    210 
    211 /*
    212  * Set the maximum number of rows and columns of items that may be displayed.
    213  */
    214 int
    215 set_menu_format(MENU *param_menu, int rows, int cols)
    216 {
    217 	MENU *menu = (param_menu != NULL) ? param_menu : &_menui_default_menu;
    218 
    219         menu->rows = rows;
    220         menu->cols = cols;
    221 
    222 	if (menu->items != NULL)
    223 		  /* recalculate the item neighbours */
    224 		return _menui_stitch_items(menu);
    225 
    226 	return E_OK;
    227 }
    228 
    229 /*
    230  * Return the max number of rows and cols that may be displayed.
    231  */
    232 void
    233 menu_format(MENU *param_menu, int *rows, 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 *menu, Menu_Hook func)
    246 {
    247 	if (menu == NULL)
    248 		_menui_default_menu.menu_init = func;
    249 	else
    250 		menu->menu_init = func;
    251         return E_OK;
    252 }
    253 
    254 /*
    255  * Return the pointer to the menu init function.
    256  */
    257 Menu_Hook
    258 menu_init(MENU *menu)
    259 {
    260 	if (menu == NULL)
    261 		return _menui_default_menu.menu_init;
    262 	else
    263 		return menu->menu_init;
    264 }
    265 
    266 /*
    267  * Set the user defined function called when a menu is unposted.
    268  */
    269 int
    270 set_menu_term(MENU *menu, Menu_Hook func)
    271 {
    272 	if (menu == NULL)
    273 		_menui_default_menu.menu_term = func;
    274 	else
    275 		menu->menu_term = func;
    276         return E_OK;
    277 }
    278 
    279 /*
    280  * Return the user defined menu termination function pointer.
    281  */
    282 Menu_Hook
    283 menu_term(MENU *menu)
    284 {
    285 	if (menu == NULL)
    286 		return _menui_default_menu.menu_term;
    287 	else
    288 		return menu->menu_term;
    289 }
    290 
    291 /*
    292  * Return the current menu options set.
    293  */
    294 OPTIONS
    295 menu_opts(MENU *menu)
    296 {
    297 	if (menu == NULL)
    298 		return _menui_default_menu.opts;
    299 	else
    300 		return menu->opts;
    301 }
    302 
    303 /*
    304  * Set the menu options to the given options.
    305  */
    306 int
    307 set_menu_opts(MENU *param_menu, OPTIONS opts)
    308 {
    309 	int i, seen;
    310 	MENU *menu = (param_menu != NULL) ? param_menu : &_menui_default_menu;
    311 	OPTIONS old_opts = menu->opts;
    312 
    313         menu->opts = opts;
    314 
    315 	  /*
    316 	   * If the radio option is selected then make sure only one
    317 	   * item is actually selected in the items.
    318 	   */
    319 	if (((opts & O_RADIO) == O_RADIO) && (menu->items != NULL) &&
    320 	    (menu->items[0] != NULL)) {
    321 		seen = 0;
    322 		for (i = 0; i < menu->item_count; i++) {
    323 			if (menu->items[i]->selected == 1) {
    324 				if (seen == 0) {
    325 					seen = 1;
    326 				} else {
    327 					menu->items[i]->selected = 0;
    328 				}
    329 			}
    330 		}
    331 
    332 		  /* if none selected, select the first item */
    333 		if (seen == 0)
    334 			menu->items[0]->selected = 1;
    335 	}
    336 
    337  	if ((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  * Turn on the options in menu given by opts.
    346  */
    347 int
    348 menu_opts_on(MENU *param_menu, OPTIONS opts)
    349 {
    350 	int i, seen;
    351 	MENU *menu = (param_menu != NULL) ? param_menu : &_menui_default_menu;
    352 	OPTIONS old_opts = menu->opts;
    353 
    354         menu->opts |= opts;
    355 
    356 	  /*
    357 	   * If the radio option is selected then make sure only one
    358 	   * item is actually selected in the items.
    359 	   */
    360 	if (((opts & O_RADIO) == O_RADIO) && (menu->items != NULL) &&
    361 	    (menu->items[0] != NULL)) {
    362 		seen = 0;
    363 		for (i = 0; i < menu->item_count; i++) {
    364 			if (menu->items[i]->selected == 1) {
    365 				if (seen == 0) {
    366 					seen = 1;
    367 				} else {
    368 					menu->items[i]->selected = 0;
    369 				}
    370 			}
    371 		}
    372 		  /* if none selected then select the top item */
    373 		if (seen == 0)
    374 			menu->items[0]->selected = 1;
    375 	}
    376 
    377 	if ((menu->items != NULL) &&
    378 	    (menu->opts & O_ROWMAJOR) != (old_opts &  O_ROWMAJOR))
    379 		  /* changed menu layout - need to recalc neighbours */
    380 		_menui_stitch_items(menu);
    381 
    382         return E_OK;
    383 }
    384 
    385 /*
    386  * Turn off the menu options given in opts.
    387  */
    388 int
    389 menu_opts_off(MENU *param_menu, OPTIONS opts)
    390 {
    391 	MENU *menu = (param_menu != NULL) ? param_menu : &_menui_default_menu;
    392 	OPTIONS old_opts = menu->opts;
    393 
    394         menu->opts &= ~(opts);
    395 
    396 	if ((menu->items != NULL ) &&
    397 	    (menu->opts & O_ROWMAJOR) != (old_opts &  O_ROWMAJOR))
    398 		  /* changed menu layout - need to recalc neighbours */
    399 		_menui_stitch_items(menu);
    400 
    401         return E_OK;
    402 }
    403 
    404 /*
    405  * Return the menu pattern buffer.
    406  */
    407 char *
    408 menu_pattern(MENU *menu)
    409 {
    410 	if (menu == NULL)
    411 		return _menui_default_menu.pattern;
    412 	else
    413 		return menu->pattern;
    414 }
    415 
    416 /*
    417  * Set the menu pattern buffer to pat and attempt to match the pattern in
    418  * the item list.
    419  */
    420 int
    421 set_menu_pattern(MENU *param_menu, char *pat)
    422 {
    423 	MENU *menu = (param_menu != NULL) ? param_menu : &_menui_default_menu;
    424 	char *p = pat;
    425 
    426 	  /* check pattern is all printable characters */
    427 	while (*p)
    428 		if (!isprint((unsigned char) *p++)) return E_BAD_ARGUMENT;
    429 
    430         if ((menu->pattern = (char *) realloc(menu->pattern,
    431                                      sizeof(char) * strlen(pat) + 1)) == NULL)
    432                 return E_SYSTEM_ERROR;
    433 
    434         strcpy(menu->pattern, pat);
    435 	menu->plen = strlen(pat);
    436 
    437           /* search item list for pat here */
    438 	return _menui_match_items(menu, MATCH_FORWARD, &menu->cur_item);
    439 }
    440 
    441 /*
    442  * Allocate a new menu structure and fill it in.
    443  */
    444 MENU *
    445 new_menu(ITEM **items)
    446 {
    447         MENU *the_menu;
    448 
    449         if ((the_menu = (MENU *)malloc(sizeof(MENU))) == NULL)
    450                 return NULL;
    451 
    452           /* copy the defaults */
    453 	(void)memcpy(the_menu, &_menui_default_menu, sizeof(MENU));
    454 
    455 	  /* set a default window if none already set. */
    456 	if (the_menu->menu_win == NULL)
    457 		the_menu->scrwin = stdscr;
    458 
    459 	  /* make a private copy of the mark string */
    460 	if (_menui_default_menu.mark.string != NULL) {
    461 		if ((the_menu->mark.string =
    462 		     (char *) malloc((unsigned) _menui_default_menu.mark.length + 1))
    463 		    == NULL) {
    464 			free(the_menu);
    465 			return NULL;
    466 		}
    467 
    468 		strlcpy(the_menu->mark.string, _menui_default_menu.mark.string,
    469 			(unsigned) _menui_default_menu.mark.length + 1);
    470 	}
    471 
    472 	  /* make a private copy of the unmark string too */
    473 	if (_menui_default_menu.unmark.string != NULL) {
    474 		if ((the_menu->unmark.string =
    475 		     (char *) malloc((unsigned) _menui_default_menu.unmark.length + 1))
    476 		    == NULL) {
    477 			free(the_menu);
    478 			return NULL;
    479 		}
    480 
    481 		strlcpy(the_menu->unmark.string,
    482 			_menui_default_menu.unmark.string,
    483 			(unsigned) _menui_default_menu.unmark.length+ 1 );
    484 	}
    485 
    486           /* now attach the items, if any */
    487         if (items != NULL) {
    488 		if(set_menu_items(the_menu, items) < 0) {
    489 			if (the_menu->mark.string != NULL)
    490 				free(the_menu->mark.string);
    491 			if (the_menu->unmark.string != NULL)
    492 				free(the_menu->unmark.string);
    493 			free(the_menu);
    494 			return NULL;
    495 		}
    496 	}
    497 
    498 	return the_menu;
    499 }
    500 
    501 /*
    502  * Free up storage allocated to the menu object and destroy it.
    503  */
    504 int
    505 free_menu(MENU *menu)
    506 {
    507 	int i;
    508 
    509 	if (menu == NULL)
    510 		return E_BAD_ARGUMENT;
    511 
    512 	if (menu->posted != 0)
    513 		return E_POSTED;
    514 
    515 	if (menu->pattern != NULL)
    516 		free(menu->pattern);
    517 
    518 	if (menu->mark.string != NULL)
    519 		free(menu->mark.string);
    520 
    521 	if (menu->items != NULL) {
    522 		  /* disconnect the items from this menu */
    523 		for (i = 0; i < menu->item_count; i++) {
    524 			menu->items[i]->parent = NULL;
    525 		}
    526 	}
    527 
    528 	free(menu);
    529 	return E_OK;
    530 }
    531 
    532 /*
    533  * Calculate the minimum window size for the menu.
    534  */
    535 int
    536 scale_menu(MENU *param_menu, int *rows, int *cols)
    537 {
    538 	MENU *menu = (param_menu != NULL) ? param_menu : &_menui_default_menu;
    539 
    540 	if (menu->items == NULL)
    541 		return E_BAD_ARGUMENT;
    542 
    543 	  /* calculate the max item size */
    544 	_menui_max_item_size(menu);
    545 
    546 	*rows = menu->rows;
    547 	*cols = menu->cols * menu->max_item_width;
    548 
    549 	  /*
    550 	   * allow for spacing between columns...
    551 	   */
    552 	*cols += (menu->cols - 1);
    553 
    554 	return E_OK;
    555 }
    556 
    557 /*
    558  * Set the menu item list to the one given.
    559  */
    560 int
    561 set_menu_items(MENU *param_menu, ITEM **items)
    562 {
    563 	MENU *menu = (param_menu != NULL) ? param_menu : &_menui_default_menu;
    564 	int i, new_count = 0, sel_count = 0;
    565 
    566 	  /* don't change if menu is posted */
    567 	if (menu->posted == 1)
    568 		return E_POSTED;
    569 
    570 	  /* count the new items and validate none are connected already */
    571 	while (items[new_count] != NULL) {
    572 		if ((items[new_count]->parent != NULL) &&
    573 		    (items[new_count]->parent != menu))
    574 			return E_CONNECTED;
    575 		if (items[new_count]->selected == 1)
    576 			sel_count++;
    577 		new_count++;
    578 	}
    579 
    580 	  /*
    581 	   * don't allow multiple selected items if menu is radio
    582 	   * button style.
    583 	   */
    584 	if (((menu->opts & O_RADIO) == O_RADIO) &&
    585 	    (sel_count > 1))
    586 		return E_BAD_ARGUMENT;
    587 
    588 	  /* if there were items connected then disconnect them. */
    589 	if (menu->items != NULL) {
    590 		for (i = 0; i < menu->item_count; i++) {
    591 			menu->items[i]->parent = NULL;
    592 			menu->items[i]->index = -1;
    593 		}
    594 	}
    595 
    596 	menu->item_count = new_count;
    597 
    598 	  /* connect the new items to the menu */
    599 	for (i = 0; i < new_count; i++) {
    600 		items[i]->parent = menu;
    601 		items[i]->index = i;
    602 	}
    603 
    604 	menu->items = items;
    605 	menu->cur_item = 0; /* reset current item just in case */
    606 	menu->top_row = 0; /* and the top row too */
    607 	if (menu->pattern != NULL) { /* and the pattern buffer....sigh */
    608 		free(menu->pattern);
    609 		menu->plen = 0;
    610 		menu->match_len = 0;
    611 	}
    612 
    613 	  /*
    614 	   * make sure at least one item is selected on a radio
    615 	   * button style menu.
    616 	   */
    617 	if (((menu->opts & O_RADIO) == O_RADIO) && (sel_count == 0))
    618 		menu->items[0]->selected = 1;
    619 
    620 
    621 	_menui_stitch_items(menu); /* recalculate the item neighbours */
    622 
    623 	return E_OK;
    624 }
    625 
    626 /*
    627  * Return the pointer to the menu items array.
    628  */
    629 ITEM **
    630 menu_items(MENU *menu)
    631 {
    632 	if (menu == NULL)
    633 		return _menui_default_menu.items;
    634 	else
    635 		return menu->items;
    636 }
    637 
    638 /*
    639  * Return the count of items connected to the menu
    640  */
    641 int
    642 item_count(MENU *menu)
    643 {
    644 	if (menu == NULL)
    645 		return _menui_default_menu.item_count;
    646 	else
    647 		return menu->item_count;
    648 }
    649 
    650 /*
    651  * Set the menu top row to be the given row.  The current item becomes the
    652  * leftmost item on that row in the menu.
    653  */
    654 int
    655 set_top_row(MENU *param_menu, int row)
    656 {
    657 	MENU *menu = (param_menu != NULL) ? param_menu : &_menui_default_menu;
    658 	int i, cur_item, state = E_SYSTEM_ERROR;
    659 
    660 	if (row > menu->item_rows)
    661 		return E_BAD_ARGUMENT;
    662 
    663 	if (menu->items == NULL)
    664 		return E_NOT_CONNECTED;
    665 
    666 	if (menu->in_init == 1)
    667 		return E_BAD_STATE;
    668 
    669 	cur_item = 0;
    670 
    671 	for (i = 0; i < menu->item_count; i++) {
    672 		  /* search for first item that matches row - this will be
    673 		     the current item. */
    674 		if (row == menu->items[i]->row) {
    675 			cur_item = i;
    676 			state = E_OK;
    677 			break; /* found what we want - no need to go further */
    678 		}
    679 	}
    680 
    681 	menu->in_init = 1; /* just in case we call the init/term routines */
    682 
    683 	if (menu->posted == 1) {
    684 		if (menu->menu_term != NULL)
    685 			menu->menu_term(menu);
    686 		if (menu->item_term != NULL)
    687 			menu->item_term(menu);
    688 	}
    689 
    690 	menu->cur_item = cur_item;
    691 	menu->top_row = row;
    692 
    693 	if (menu->posted == 1) {
    694 		if (menu->menu_init != NULL)
    695 			menu->menu_init(menu);
    696 		if (menu->item_init != NULL)
    697 			menu->item_init(menu);
    698 	}
    699 
    700 	menu->in_init = 0;
    701 
    702 	  /* this should always be E_OK unless we are really screwed up */
    703 	return state;
    704 }
    705 
    706 /*
    707  * Return the current top row number.
    708  */
    709 int
    710 top_row(MENU *param_menu)
    711 {
    712 	MENU *menu = (param_menu != NULL) ? param_menu : &_menui_default_menu;
    713 
    714 	if (menu->items == NULL)
    715 		return E_NOT_CONNECTED;
    716 
    717 	return menu->top_row;
    718 }
    719 
    720 /*
    721  * Position the cursor at the correct place in the menu.
    722  *
    723  */
    724 int
    725 pos_menu_cursor(MENU *menu)
    726 {
    727 	int movx, maxmark;
    728 
    729 	if (menu == NULL)
    730 		return E_BAD_ARGUMENT;
    731 
    732 	maxmark = max(menu->mark.length, menu->unmark.length);
    733 	movx = maxmark + (menu->items[menu->cur_item]->col
    734 		* (menu->col_width + 1));
    735 
    736 	if (menu->match_len > 0)
    737 		movx += menu->match_len - 1;
    738 
    739 	wmove(menu->scrwin,
    740 	      menu->items[menu->cur_item]->row - menu->top_row, movx);
    741 
    742 	return E_OK;
    743 }
    744