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