Home | History | Annotate | Line # | Download | only in libmenu
menu.c revision 1.7
      1 /*	$NetBSD: menu.c,v 1.7 2000/05/05 11:28:56 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 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(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(*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 	for (i = 0; i < menu->item_count; i++) {
    562 		  /* search for first item that matches row - this will be
    563 		     the current item. */
    564 		if (row == menu->items[i]->row) {
    565 			cur_item = i;
    566 			state = E_OK;
    567 			break; /* found what we want - no need to go further */
    568 		}
    569 	}
    570 
    571 	menu->in_init = 1; /* just in case we call the init/term routines */
    572 
    573 	if (menu->posted == 1) {
    574 		if (menu->menu_term != NULL)
    575 			menu->menu_term(menu);
    576 		if (menu->item_term != NULL)
    577 			menu->item_term(menu);
    578 	}
    579 
    580 	menu->cur_item = cur_item;
    581 	menu->top_row = row;
    582 
    583 	if (menu->posted == 1) {
    584 		if (menu->menu_init != NULL)
    585 			menu->menu_init(menu);
    586 		if (menu->item_init != NULL)
    587 			menu->item_init(menu);
    588 	}
    589 
    590 	menu->in_init = 0;
    591 
    592 	  /* this should always be E_OK unless we are really screwed up */
    593 	return state;
    594 }
    595 
    596 /*
    597  * Return the current top row number.
    598  */
    599 int
    600 top_row(MENU *param_menu)
    601 {
    602 	MENU *menu = (param_menu != NULL) ? param_menu : &_menui_default_menu;
    603 
    604 	if (menu->items == NULL)
    605 		return E_NOT_CONNECTED;
    606 
    607 	return menu->top_row;
    608 }
    609 
    610 /*
    611  * Position the cursor at the correct place in the menu.
    612  *
    613  */
    614 int
    615 pos_menu_cursor(MENU *menu)
    616 {
    617 	int movx, maxmark;
    618 
    619 	if (menu == NULL)
    620 		return E_BAD_ARGUMENT;
    621 
    622 	maxmark = max(menu->mark.length, menu->unmark.length);
    623 	movx = maxmark + (menu->items[menu->cur_item]->col
    624 		* menu->col_width);
    625 
    626 	if (menu->match_len > 0)
    627 		movx += menu->match_len - 1;
    628 
    629 	wmove(menu->menu_subwin,
    630 	      menu->items[menu->cur_item]->row - menu->top_row, movx);
    631 
    632 	return E_OK;
    633 }
    634