xref: /netbsd/lib/libmenu/menu.c (revision bf9ec67e)
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@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 	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