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