Home | History | Annotate | Line # | Download | only in libmenu
menu.c revision 1.11
      1 /*	$NetBSD: menu.c,v 1.11 2002/02/04 13:02:05 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 	MENU *menu = (param_menu != NULL) ? param_menu : &_menui_default_menu;
    310 	OPTIONS old_opts = menu->opts;
    311 
    312         menu->opts = opts;
    313 
    314  	if ((menu->opts & O_ROWMAJOR) != (old_opts &  O_ROWMAJOR))
    315 		  /* changed menu layout - need to recalc neighbours */
    316 		_menui_stitch_items(menu);
    317 
    318         return E_OK;
    319 }
    320 
    321 /*
    322  * Turn on the options in menu given by opts.
    323  */
    324 int
    325 menu_opts_on(MENU *param_menu, OPTIONS opts)
    326 {
    327 	MENU *menu = (param_menu != NULL) ? param_menu : &_menui_default_menu;
    328 	OPTIONS old_opts = menu->opts;
    329 
    330         menu->opts |= opts;
    331 
    332 	if ((menu->items != NULL) &&
    333 	    (menu->opts & O_ROWMAJOR) != (old_opts &  O_ROWMAJOR))
    334 		  /* changed menu layout - need to recalc neighbours */
    335 		_menui_stitch_items(menu);
    336 
    337         return E_OK;
    338 }
    339 
    340 /*
    341  * Turn off the menu options given in opts.
    342  */
    343 int
    344 menu_opts_off(MENU *param_menu, OPTIONS opts)
    345 {
    346 	MENU *menu = (param_menu != NULL) ? param_menu : &_menui_default_menu;
    347 	OPTIONS old_opts = menu->opts;
    348 
    349         menu->opts &= ~(opts);
    350 
    351 	if ((menu->items != NULL ) &&
    352 	    (menu->opts & O_ROWMAJOR) != (old_opts &  O_ROWMAJOR))
    353 		  /* changed menu layout - need to recalc neighbours */
    354 		_menui_stitch_items(menu);
    355 
    356         return E_OK;
    357 }
    358 
    359 /*
    360  * Return the menu pattern buffer.
    361  */
    362 char *
    363 menu_pattern(MENU *menu)
    364 {
    365 	if (menu == NULL)
    366 		return _menui_default_menu.pattern;
    367 	else
    368 		return menu->pattern;
    369 }
    370 
    371 /*
    372  * Set the menu pattern buffer to pat and attempt to match the pattern in
    373  * the item list.
    374  */
    375 int
    376 set_menu_pattern(MENU *param_menu, char *pat)
    377 {
    378 	MENU *menu = (param_menu != NULL) ? param_menu : &_menui_default_menu;
    379 	char *p = pat;
    380 
    381 	  /* check pattern is all printable characters */
    382 	while (*p)
    383 		if (!isprint((unsigned char) *p++)) return E_BAD_ARGUMENT;
    384 
    385         if ((menu->pattern = (char *) realloc(menu->pattern,
    386                                      sizeof(char) * strlen(pat))) == NULL)
    387                 return E_SYSTEM_ERROR;
    388 
    389         strcpy(menu->pattern, pat);
    390 	menu->plen = strlen(pat);
    391 
    392           /* search item list for pat here */
    393 	return _menui_match_items(menu, MATCH_FORWARD, &menu->cur_item);
    394 }
    395 
    396 /*
    397  * Allocate a new menu structure and fill it in.
    398  */
    399 MENU *
    400 new_menu(ITEM **items)
    401 {
    402         MENU *the_menu;
    403 
    404         if ((the_menu = (MENU *)malloc(sizeof(MENU))) == NULL)
    405                 return NULL;
    406 
    407           /* copy the defaults */
    408 	(void)memcpy(the_menu, &_menui_default_menu, sizeof(MENU));
    409 
    410 	  /* set a default window if none already set. */
    411 	if (the_menu->menu_win == NULL)
    412 		the_menu->scrwin = stdscr;
    413 
    414           /* now attach the items, if any */
    415         if (items != NULL) {
    416 		if(set_menu_items(the_menu, items) < 0) {
    417 			free(the_menu);
    418 			return NULL;
    419 		}
    420 	}
    421 
    422 	return the_menu;
    423 }
    424 
    425 /*
    426  * Free up storage allocated to the menu object and destroy it.
    427  */
    428 int
    429 free_menu(MENU *menu)
    430 {
    431 	int i;
    432 
    433 	if (menu == NULL)
    434 		return E_BAD_ARGUMENT;
    435 
    436 	if (menu->posted != 0)
    437 		return E_POSTED;
    438 
    439 	if (menu->pattern != NULL)
    440 		free(menu->pattern);
    441 
    442 	if (menu->mark.string != NULL)
    443 		free(menu->mark.string);
    444 
    445 	if (menu->items != NULL) {
    446 		  /* disconnect the items from this menu */
    447 		for (i = 0; i < menu->item_count; i++) {
    448 			menu->items[i]->parent = NULL;
    449 		}
    450 	}
    451 
    452 	free(menu);
    453 	return E_OK;
    454 }
    455 
    456 /*
    457  * Calculate the minimum window size for the menu.
    458  */
    459 int
    460 scale_menu(MENU *param_menu, int *rows, int *cols)
    461 {
    462 	MENU *menu = (param_menu != NULL) ? param_menu : &_menui_default_menu;
    463 
    464 	if (menu->items == NULL)
    465 		return E_BAD_ARGUMENT;
    466 
    467 	  /* calculate the max item size */
    468 	_menui_max_item_size(menu);
    469 
    470 	*rows = menu->rows;
    471 	*cols = menu->cols * menu->max_item_width;
    472 
    473 	  /*
    474 	   * allow for spacing between columns...
    475 	   */
    476 	*cols += menu->cols;
    477 
    478 	return E_OK;
    479 }
    480 
    481 /*
    482  * Set the menu item list to the one given.
    483  */
    484 int
    485 set_menu_items(MENU *param_menu, ITEM **items)
    486 {
    487 	MENU *menu = (param_menu != NULL) ? param_menu : &_menui_default_menu;
    488 	int i, new_count = 0;
    489 
    490 	  /* don't change if menu is posted */
    491 	if (menu->posted == 1)
    492 		return E_POSTED;
    493 
    494 	  /* count the new items and validate none are connected already */
    495 	while (items[new_count] != NULL) {
    496 		if ((items[new_count]->parent != NULL) &&
    497 		    (items[new_count]->parent != menu))
    498 			return E_CONNECTED;
    499 		new_count++;
    500 	}
    501 
    502 
    503 	  /* if there were items connected then disconnect them. */
    504 	if (menu->items != NULL) {
    505 		for (i = 0; i < menu->item_count; i++) {
    506 			menu->items[i]->parent = NULL;
    507 			menu->items[i]->index = -1;
    508 		}
    509 	}
    510 
    511 	menu->item_count = new_count;
    512 
    513 	  /* connect the new items to the menu */
    514 	for (i = 0; i < new_count; i++) {
    515 		items[i]->parent = menu;
    516 		items[i]->index = i;
    517 	}
    518 
    519 	menu->items = items;
    520 	menu->cur_item = 0; /* reset current item just in case */
    521 	menu->top_row = 0; /* and the top row too */
    522 	if (menu->pattern != NULL) { /* and the pattern buffer....sigh */
    523 		free(menu->pattern);
    524 		menu->plen = 0;
    525 		menu->match_len = 0;
    526 	}
    527 
    528 	_menui_stitch_items(menu); /* recalculate the item neighbours */
    529 
    530 	return E_OK;
    531 }
    532 
    533 /*
    534  * Return the pointer to the menu items array.
    535  */
    536 ITEM **
    537 menu_items(MENU *menu)
    538 {
    539 	if (menu == NULL)
    540 		return _menui_default_menu.items;
    541 	else
    542 		return menu->items;
    543 }
    544 
    545 /*
    546  * Return the count of items connected to the menu
    547  */
    548 int
    549 item_count(MENU *menu)
    550 {
    551 	if (menu == NULL)
    552 		return _menui_default_menu.item_count;
    553 	else
    554 		return menu->item_count;
    555 }
    556 
    557 /*
    558  * Set the menu top row to be the given row.  The current item becomes the
    559  * leftmost item on that row in the menu.
    560  */
    561 int
    562 set_top_row(MENU *param_menu, int row)
    563 {
    564 	MENU *menu = (param_menu != NULL) ? param_menu : &_menui_default_menu;
    565 	int i, cur_item, state = E_SYSTEM_ERROR;
    566 
    567 	if (row > menu->item_rows)
    568 		return E_BAD_ARGUMENT;
    569 
    570 	if (menu->items == NULL)
    571 		return E_NOT_CONNECTED;
    572 
    573 	if (menu->in_init == 1)
    574 		return E_BAD_STATE;
    575 
    576 	cur_item = 0;
    577 
    578 	for (i = 0; i < menu->item_count; i++) {
    579 		  /* search for first item that matches row - this will be
    580 		     the current item. */
    581 		if (row == menu->items[i]->row) {
    582 			cur_item = i;
    583 			state = E_OK;
    584 			break; /* found what we want - no need to go further */
    585 		}
    586 	}
    587 
    588 	menu->in_init = 1; /* just in case we call the init/term routines */
    589 
    590 	if (menu->posted == 1) {
    591 		if (menu->menu_term != NULL)
    592 			menu->menu_term(menu);
    593 		if (menu->item_term != NULL)
    594 			menu->item_term(menu);
    595 	}
    596 
    597 	menu->cur_item = cur_item;
    598 	menu->top_row = row;
    599 
    600 	if (menu->posted == 1) {
    601 		if (menu->menu_init != NULL)
    602 			menu->menu_init(menu);
    603 		if (menu->item_init != NULL)
    604 			menu->item_init(menu);
    605 	}
    606 
    607 	menu->in_init = 0;
    608 
    609 	  /* this should always be E_OK unless we are really screwed up */
    610 	return state;
    611 }
    612 
    613 /*
    614  * Return the current top row number.
    615  */
    616 int
    617 top_row(MENU *param_menu)
    618 {
    619 	MENU *menu = (param_menu != NULL) ? param_menu : &_menui_default_menu;
    620 
    621 	if (menu->items == NULL)
    622 		return E_NOT_CONNECTED;
    623 
    624 	return menu->top_row;
    625 }
    626 
    627 /*
    628  * Position the cursor at the correct place in the menu.
    629  *
    630  */
    631 int
    632 pos_menu_cursor(MENU *menu)
    633 {
    634 	int movx, maxmark;
    635 
    636 	if (menu == NULL)
    637 		return E_BAD_ARGUMENT;
    638 
    639 	maxmark = max(menu->mark.length, menu->unmark.length);
    640 	movx = maxmark + (menu->items[menu->cur_item]->col
    641 		* menu->col_width);
    642 
    643 	if (menu->match_len > 0)
    644 		movx += menu->match_len - 1;
    645 
    646 	wmove(menu->scrwin,
    647 	      menu->items[menu->cur_item]->row - menu->top_row, movx);
    648 
    649 	return E_OK;
    650 }
    651