Home | History | Annotate | Line # | Download | only in libmenu
menu.c revision 1.12
      1 /*	$NetBSD: menu.c,v 1.12 2002/07/29 13:03:51 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))) == 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 		_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))) == 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           /* now attach the items, if any */
    460         if (items != NULL) {
    461 		if(set_menu_items(the_menu, items) < 0) {
    462 			free(the_menu);
    463 			return NULL;
    464 		}
    465 	}
    466 
    467 	return the_menu;
    468 }
    469 
    470 /*
    471  * Free up storage allocated to the menu object and destroy it.
    472  */
    473 int
    474 free_menu(MENU *menu)
    475 {
    476 	int i;
    477 
    478 	if (menu == NULL)
    479 		return E_BAD_ARGUMENT;
    480 
    481 	if (menu->posted != 0)
    482 		return E_POSTED;
    483 
    484 	if (menu->pattern != NULL)
    485 		free(menu->pattern);
    486 
    487 	if (menu->mark.string != NULL)
    488 		free(menu->mark.string);
    489 
    490 	if (menu->items != NULL) {
    491 		  /* disconnect the items from this menu */
    492 		for (i = 0; i < menu->item_count; i++) {
    493 			menu->items[i]->parent = NULL;
    494 		}
    495 	}
    496 
    497 	free(menu);
    498 	return E_OK;
    499 }
    500 
    501 /*
    502  * Calculate the minimum window size for the menu.
    503  */
    504 int
    505 scale_menu(MENU *param_menu, int *rows, int *cols)
    506 {
    507 	MENU *menu = (param_menu != NULL) ? param_menu : &_menui_default_menu;
    508 
    509 	if (menu->items == NULL)
    510 		return E_BAD_ARGUMENT;
    511 
    512 	  /* calculate the max item size */
    513 	_menui_max_item_size(menu);
    514 
    515 	*rows = menu->rows;
    516 	*cols = menu->cols * menu->max_item_width;
    517 
    518 	  /*
    519 	   * allow for spacing between columns...
    520 	   */
    521 	*cols += menu->cols;
    522 
    523 	return E_OK;
    524 }
    525 
    526 /*
    527  * Set the menu item list to the one given.
    528  */
    529 int
    530 set_menu_items(MENU *param_menu, ITEM **items)
    531 {
    532 	MENU *menu = (param_menu != NULL) ? param_menu : &_menui_default_menu;
    533 	int i, new_count = 0, sel_count = 0;
    534 
    535 	  /* don't change if menu is posted */
    536 	if (menu->posted == 1)
    537 		return E_POSTED;
    538 
    539 	  /* count the new items and validate none are connected already */
    540 	while (items[new_count] != NULL) {
    541 		if ((items[new_count]->parent != NULL) &&
    542 		    (items[new_count]->parent != menu))
    543 			return E_CONNECTED;
    544 		if (items[new_count]->selected == 1)
    545 			sel_count++;
    546 		new_count++;
    547 	}
    548 
    549 	  /*
    550 	   * don't allow multiple selected items if menu is radio
    551 	   * button style.
    552 	   */
    553 	if (((menu->opts & O_RADIO) == O_RADIO) &&
    554 	    (sel_count > 1))
    555 		return E_BAD_ARGUMENT;
    556 
    557 	  /* if there were items connected then disconnect them. */
    558 	if (menu->items != NULL) {
    559 		for (i = 0; i < menu->item_count; i++) {
    560 			menu->items[i]->parent = NULL;
    561 			menu->items[i]->index = -1;
    562 		}
    563 	}
    564 
    565 	menu->item_count = new_count;
    566 
    567 	  /* connect the new items to the menu */
    568 	for (i = 0; i < new_count; i++) {
    569 		items[i]->parent = menu;
    570 		items[i]->index = i;
    571 	}
    572 
    573 	menu->items = items;
    574 	menu->cur_item = 0; /* reset current item just in case */
    575 	menu->top_row = 0; /* and the top row too */
    576 	if (menu->pattern != NULL) { /* and the pattern buffer....sigh */
    577 		free(menu->pattern);
    578 		menu->plen = 0;
    579 		menu->match_len = 0;
    580 	}
    581 
    582 	  /*
    583 	   * make sure at least one item is selected on a radio
    584 	   * button style menu.
    585 	   */
    586 	if (((menu->opts & O_RADIO) == O_RADIO) && (sel_count == 0))
    587 		menu->items[0]->selected = 1;
    588 
    589 
    590 	_menui_stitch_items(menu); /* recalculate the item neighbours */
    591 
    592 	return E_OK;
    593 }
    594 
    595 /*
    596  * Return the pointer to the menu items array.
    597  */
    598 ITEM **
    599 menu_items(MENU *menu)
    600 {
    601 	if (menu == NULL)
    602 		return _menui_default_menu.items;
    603 	else
    604 		return menu->items;
    605 }
    606 
    607 /*
    608  * Return the count of items connected to the menu
    609  */
    610 int
    611 item_count(MENU *menu)
    612 {
    613 	if (menu == NULL)
    614 		return _menui_default_menu.item_count;
    615 	else
    616 		return menu->item_count;
    617 }
    618 
    619 /*
    620  * Set the menu top row to be the given row.  The current item becomes the
    621  * leftmost item on that row in the menu.
    622  */
    623 int
    624 set_top_row(MENU *param_menu, int row)
    625 {
    626 	MENU *menu = (param_menu != NULL) ? param_menu : &_menui_default_menu;
    627 	int i, cur_item, state = E_SYSTEM_ERROR;
    628 
    629 	if (row > menu->item_rows)
    630 		return E_BAD_ARGUMENT;
    631 
    632 	if (menu->items == NULL)
    633 		return E_NOT_CONNECTED;
    634 
    635 	if (menu->in_init == 1)
    636 		return E_BAD_STATE;
    637 
    638 	cur_item = 0;
    639 
    640 	for (i = 0; i < menu->item_count; i++) {
    641 		  /* search for first item that matches row - this will be
    642 		     the current item. */
    643 		if (row == menu->items[i]->row) {
    644 			cur_item = i;
    645 			state = E_OK;
    646 			break; /* found what we want - no need to go further */
    647 		}
    648 	}
    649 
    650 	menu->in_init = 1; /* just in case we call the init/term routines */
    651 
    652 	if (menu->posted == 1) {
    653 		if (menu->menu_term != NULL)
    654 			menu->menu_term(menu);
    655 		if (menu->item_term != NULL)
    656 			menu->item_term(menu);
    657 	}
    658 
    659 	menu->cur_item = cur_item;
    660 	menu->top_row = row;
    661 
    662 	if (menu->posted == 1) {
    663 		if (menu->menu_init != NULL)
    664 			menu->menu_init(menu);
    665 		if (menu->item_init != NULL)
    666 			menu->item_init(menu);
    667 	}
    668 
    669 	menu->in_init = 0;
    670 
    671 	  /* this should always be E_OK unless we are really screwed up */
    672 	return state;
    673 }
    674 
    675 /*
    676  * Return the current top row number.
    677  */
    678 int
    679 top_row(MENU *param_menu)
    680 {
    681 	MENU *menu = (param_menu != NULL) ? param_menu : &_menui_default_menu;
    682 
    683 	if (menu->items == NULL)
    684 		return E_NOT_CONNECTED;
    685 
    686 	return menu->top_row;
    687 }
    688 
    689 /*
    690  * Position the cursor at the correct place in the menu.
    691  *
    692  */
    693 int
    694 pos_menu_cursor(MENU *menu)
    695 {
    696 	int movx, maxmark;
    697 
    698 	if (menu == NULL)
    699 		return E_BAD_ARGUMENT;
    700 
    701 	maxmark = max(menu->mark.length, menu->unmark.length);
    702 	movx = maxmark + (menu->items[menu->cur_item]->col
    703 		* menu->col_width);
    704 
    705 	if (menu->match_len > 0)
    706 		movx += menu->match_len - 1;
    707 
    708 	wmove(menu->scrwin,
    709 	      menu->items[menu->cur_item]->row - menu->top_row, movx);
    710 
    711 	return E_OK;
    712 }
    713