1 /* $NetBSD: menu.c,v 1.18 2012/12/30 12:27:09 blymn Exp $ */
4 * Copyright (c) 1998-1999 Brett Lymn (blymn@baea.com.au, brett_lymn@yahoo.com.au)
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
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
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.
29 #include <sys/cdefs.h>
30 __RCSID("$NetBSD: menu.c,v 1.18 2012/12/30 12:27:09 blymn Exp $");
36 #include "internals.h"
38 MENU _menui_default_menu
= {
39 16, /* number of item rows that will fit in window */
40 1, /* number of columns of items that will fit in window */
41 0, /* number of rows of items we have */
42 0, /* number of columns of items we have */
43 0, /* current cursor row */
44 0, /* current cursor column */
45 {NULL
, 0}, /* mark string */
46 {NULL
, 0}, /* unmark string */
47 O_ONEVALUE
, /* menu options */
48 NULL
, /* the pattern buffer */
49 0, /* length of pattern buffer */
50 0, /* the length of matched buffer */
51 0, /* is the menu posted? */
52 A_REVERSE
, /* menu foreground */
53 A_NORMAL
, /* menu background */
54 A_UNDERLINE
, /* unselectable menu item */
55 ' ', /* filler between name and description */
56 NULL
, /* user defined pointer */
57 0, /* top row of menu */
58 0, /* widest item in the menu */
59 0, /* the width of a menu column */
60 0, /* number of items attached to the menu */
61 NULL
, /* items in the menu */
62 0, /* current menu item */
63 0, /* currently in a hook function */
64 NULL
, /* function called when menu posted */
65 NULL
, /* function called when menu is unposted */
66 NULL
, /* function called when current item changes */
67 NULL
, /* function called when current item changes */
68 NULL
, /* the menu window */
69 NULL
, /* the menu subwindow */
70 NULL
, /* the window to write to */
76 * Set the menu mark character
79 set_menu_mark(MENU
*m
, char *mark
)
83 if (m
== NULL
) menu
= &_menui_default_menu
;
85 /* if there was an old mark string, free it first */
86 if (menu
->mark
.string
!= NULL
) free(menu
->mark
.string
);
88 if ((menu
->mark
.string
= (char *) malloc(strlen(mark
) + 1)) == NULL
)
89 return E_SYSTEM_ERROR
;
91 strcpy(menu
->mark
.string
, mark
);
92 menu
->mark
.length
= strlen(mark
);
94 /* max item size may have changed - recalculate. */
95 _menui_max_item_size(menu
);
100 * Return the menu mark string for the menu.
103 menu_mark(MENU
*menu
)
106 return _menui_default_menu
.mark
.string
;
108 return menu
->mark
.string
;
112 * Set the menu unmark character
115 set_menu_unmark(MENU
*m
, char *mark
)
119 if (m
== NULL
) menu
= &_menui_default_menu
;
121 /* if there was an old mark string, free it first */
122 if (menu
->unmark
.string
!= NULL
) free(menu
->unmark
.string
);
124 if ((menu
->unmark
.string
= (char *) malloc(strlen(mark
) + 1)) == NULL
)
125 return E_SYSTEM_ERROR
;
127 strcpy(menu
->unmark
.string
, mark
);
128 menu
->unmark
.length
= strlen(mark
);
129 /* max item size may have changed - recalculate. */
130 _menui_max_item_size(menu
);
135 * Return the menu unmark string for the menu.
138 menu_unmark(MENU
*menu
)
141 return _menui_default_menu
.unmark
.string
;
143 return menu
->unmark
.string
;
147 * Set the menu window to the window passed.
150 set_menu_win(MENU
*menu
, WINDOW
*win
)
153 _menui_default_menu
.menu_win
= win
;
154 _menui_default_menu
.scrwin
= win
;
156 if (menu
->posted
== TRUE
) {
159 menu
->menu_win
= win
;
168 * Return the pointer to the menu window
174 return _menui_default_menu
.menu_win
;
176 return menu
->menu_win
;
180 * Set the menu subwindow for the menu.
183 set_menu_sub(MENU
*menu
, WINDOW
*sub
)
186 _menui_default_menu
.menu_subwin
= sub
;
187 _menui_default_menu
.scrwin
= sub
;
189 if (menu
->posted
== TRUE
)
192 menu
->menu_subwin
= sub
;
200 * Return the subwindow pointer for the menu
206 return _menui_default_menu
.menu_subwin
;
208 return menu
->menu_subwin
;
212 * Set the maximum number of rows and columns of items that may be displayed.
215 set_menu_format(MENU
*param_menu
, int rows
, int cols
)
217 MENU
*menu
= (param_menu
!= NULL
) ? param_menu
: &_menui_default_menu
;
222 if (menu
->items
!= NULL
)
223 /* recalculate the item neighbours */
224 return _menui_stitch_items(menu
);
230 * Return the max number of rows and cols that may be displayed.
233 menu_format(MENU
*param_menu
, int *rows
, int *cols
)
235 MENU
*menu
= (param_menu
!= NULL
) ? param_menu
: &_menui_default_menu
;
242 * Set the user defined function to call when a menu is posted.
245 set_menu_init(MENU
*menu
, Menu_Hook func
)
248 _menui_default_menu
.menu_init
= func
;
250 menu
->menu_init
= func
;
255 * Return the pointer to the menu init function.
258 menu_init(MENU
*menu
)
261 return _menui_default_menu
.menu_init
;
263 return menu
->menu_init
;
267 * Set the user defined function called when a menu is unposted.
270 set_menu_term(MENU
*menu
, Menu_Hook func
)
273 _menui_default_menu
.menu_term
= func
;
275 menu
->menu_term
= func
;
280 * Return the user defined menu termination function pointer.
283 menu_term(MENU
*menu
)
286 return _menui_default_menu
.menu_term
;
288 return menu
->menu_term
;
292 * Return the current menu options set.
295 menu_opts(MENU
*menu
)
298 return _menui_default_menu
.opts
;
304 * Set the menu options to the given options.
307 set_menu_opts(MENU
*param_menu
, OPTIONS opts
)
310 MENU
*menu
= (param_menu
!= NULL
) ? param_menu
: &_menui_default_menu
;
311 OPTIONS old_opts
= menu
->opts
;
316 * If the radio option is selected then make sure only one
317 * item is actually selected in the items.
319 if (((opts
& O_RADIO
) == O_RADIO
) && (menu
->items
!= NULL
) &&
320 (menu
->items
[0] != NULL
)) {
322 for (i
= 0; i
< menu
->item_count
; i
++) {
323 if (menu
->items
[i
]->selected
== 1) {
327 menu
->items
[i
]->selected
= 0;
332 /* if none selected, select the first item */
334 menu
->items
[0]->selected
= 1;
337 if ((menu
->opts
& O_ROWMAJOR
) != (old_opts
& O_ROWMAJOR
))
338 /* changed menu layout - need to recalc neighbours */
339 _menui_stitch_items(menu
);
345 * Turn on the options in menu given by opts.
348 menu_opts_on(MENU
*param_menu
, OPTIONS opts
)
351 MENU
*menu
= (param_menu
!= NULL
) ? param_menu
: &_menui_default_menu
;
352 OPTIONS old_opts
= menu
->opts
;
357 * If the radio option is selected then make sure only one
358 * item is actually selected in the items.
360 if (((opts
& O_RADIO
) == O_RADIO
) && (menu
->items
!= NULL
) &&
361 (menu
->items
[0] != NULL
)) {
363 for (i
= 0; i
< menu
->item_count
; i
++) {
364 if (menu
->items
[i
]->selected
== 1) {
368 menu
->items
[i
]->selected
= 0;
372 /* if none selected then select the top item */
374 menu
->items
[0]->selected
= 1;
377 if ((menu
->items
!= NULL
) &&
378 (menu
->opts
& O_ROWMAJOR
) != (old_opts
& O_ROWMAJOR
))
379 /* changed menu layout - need to recalc neighbours */
380 _menui_stitch_items(menu
);
386 * Turn off the menu options given in opts.
389 menu_opts_off(MENU
*param_menu
, OPTIONS opts
)
391 MENU
*menu
= (param_menu
!= NULL
) ? param_menu
: &_menui_default_menu
;
392 OPTIONS old_opts
= menu
->opts
;
394 menu
->opts
&= ~(opts
);
396 if ((menu
->items
!= NULL
) &&
397 (menu
->opts
& O_ROWMAJOR
) != (old_opts
& O_ROWMAJOR
))
398 /* changed menu layout - need to recalc neighbours */
399 _menui_stitch_items(menu
);
405 * Return the menu pattern buffer.
408 menu_pattern(MENU
*menu
)
411 return _menui_default_menu
.pattern
;
413 return menu
->pattern
;
417 * Set the menu pattern buffer to pat and attempt to match the pattern in
421 set_menu_pattern(MENU
*param_menu
, char *pat
)
423 MENU
*menu
= (param_menu
!= NULL
) ? param_menu
: &_menui_default_menu
;
426 /* check pattern is all printable characters */
428 if (!isprint((unsigned char) *p
++)) return E_BAD_ARGUMENT
;
430 if ((menu
->pattern
= (char *) realloc(menu
->pattern
,
431 sizeof(char) * strlen(pat
) + 1)) == NULL
)
432 return E_SYSTEM_ERROR
;
434 strcpy(menu
->pattern
, pat
);
435 menu
->plen
= strlen(pat
);
437 /* search item list for pat here */
438 return _menui_match_items(menu
, MATCH_FORWARD
, &menu
->cur_item
);
442 * Allocate a new menu structure and fill it in.
445 new_menu(ITEM
**items
)
450 if ((the_menu
= (MENU
*)malloc(sizeof(MENU
))) == NULL
)
453 /* copy the defaults */
454 (void)memcpy(the_menu
, &_menui_default_menu
, sizeof(MENU
));
456 /* set a default window if none already set. */
457 if (the_menu
->menu_win
== NULL
)
458 the_menu
->scrwin
= stdscr
;
460 /* make a private copy of the mark string */
461 if (_menui_default_menu
.mark
.string
!= NULL
) {
462 if ((the_menu
->mark
.string
=
463 (char *) malloc((unsigned) _menui_default_menu
.mark
.length
+ 1))
469 strlcpy(the_menu
->mark
.string
, _menui_default_menu
.mark
.string
,
470 (unsigned) _menui_default_menu
.mark
.length
+ 1);
473 /* make a private copy of the unmark string too */
474 if (_menui_default_menu
.unmark
.string
!= NULL
) {
475 if ((the_menu
->unmark
.string
=
476 (char *) malloc((unsigned) _menui_default_menu
.unmark
.length
+ 1))
482 strlcpy(the_menu
->unmark
.string
,
483 _menui_default_menu
.unmark
.string
,
484 (unsigned) _menui_default_menu
.unmark
.length
+ 1 );
487 /* default mark needs to be set */
491 set_menu_mark(the_menu
, mark
);
493 /* now attach the items, if any */
495 if(set_menu_items(the_menu
, items
) < 0) {
496 if (the_menu
->mark
.string
!= NULL
)
497 free(the_menu
->mark
.string
);
498 if (the_menu
->unmark
.string
!= NULL
)
499 free(the_menu
->unmark
.string
);
509 * Free up storage allocated to the menu object and destroy it.
512 free_menu(MENU
*menu
)
517 return E_BAD_ARGUMENT
;
519 if (menu
->posted
!= 0)
522 if (menu
->pattern
!= NULL
)
525 if (menu
->mark
.string
!= NULL
)
526 free(menu
->mark
.string
);
528 if (menu
->items
!= NULL
) {
529 /* disconnect the items from this menu */
530 for (i
= 0; i
< menu
->item_count
; i
++) {
531 menu
->items
[i
]->parent
= NULL
;
540 * Calculate the minimum window size for the menu.
543 scale_menu(MENU
*param_menu
, int *rows
, int *cols
)
545 MENU
*menu
= (param_menu
!= NULL
) ? param_menu
: &_menui_default_menu
;
547 if (menu
->items
== NULL
)
548 return E_BAD_ARGUMENT
;
550 /* calculate the max item size */
551 _menui_max_item_size(menu
);
554 *cols
= menu
->cols
* menu
->max_item_width
;
557 * allow for spacing between columns...
559 *cols
+= (menu
->cols
- 1);
565 * Set the menu item list to the one given.
568 set_menu_items(MENU
*param_menu
, ITEM
**items
)
570 MENU
*menu
= (param_menu
!= NULL
) ? param_menu
: &_menui_default_menu
;
571 int i
, new_count
= 0, sel_count
= 0;
573 /* don't change if menu is posted */
574 if (menu
->posted
== 1)
577 /* count the new items and validate none are connected already */
578 while (items
[new_count
] != NULL
) {
579 if ((items
[new_count
]->parent
!= NULL
) &&
580 (items
[new_count
]->parent
!= menu
))
582 if (items
[new_count
]->selected
== 1)
588 * don't allow multiple selected items if menu is radio
591 if (((menu
->opts
& O_RADIO
) == O_RADIO
) &&
593 return E_BAD_ARGUMENT
;
595 /* if there were items connected then disconnect them. */
596 if (menu
->items
!= NULL
) {
597 for (i
= 0; i
< menu
->item_count
; i
++) {
598 menu
->items
[i
]->parent
= NULL
;
599 menu
->items
[i
]->index
= -1;
603 menu
->item_count
= new_count
;
605 /* connect the new items to the menu */
606 for (i
= 0; i
< new_count
; i
++) {
607 items
[i
]->parent
= menu
;
612 menu
->cur_item
= 0; /* reset current item just in case */
613 menu
->top_row
= 0; /* and the top row too */
614 if (menu
->pattern
!= NULL
) { /* and the pattern buffer....sigh */
621 * make sure at least one item is selected on a radio
624 if (((menu
->opts
& O_RADIO
) == O_RADIO
) && (sel_count
== 0))
625 menu
->items
[0]->selected
= 1;
628 _menui_stitch_items(menu
); /* recalculate the item neighbours */
634 * Return the pointer to the menu items array.
637 menu_items(MENU
*menu
)
640 return _menui_default_menu
.items
;
646 * Return the count of items connected to the menu
649 item_count(MENU
*menu
)
652 return _menui_default_menu
.item_count
;
654 return menu
->item_count
;
658 * Set the menu top row to be the given row. The current item becomes the
659 * leftmost item on that row in the menu.
662 set_top_row(MENU
*param_menu
, int row
)
664 MENU
*menu
= (param_menu
!= NULL
) ? param_menu
: &_menui_default_menu
;
665 int i
, cur_item
, state
= E_SYSTEM_ERROR
;
667 if (row
> menu
->item_rows
)
668 return E_BAD_ARGUMENT
;
670 if (menu
->items
== NULL
)
671 return E_NOT_CONNECTED
;
673 if (menu
->in_init
== 1)
678 for (i
= 0; i
< menu
->item_count
; i
++) {
679 /* search for first item that matches row - this will be
681 if (row
== menu
->items
[i
]->row
) {
684 break; /* found what we want - no need to go further */
688 menu
->in_init
= 1; /* just in case we call the init/term routines */
690 if (menu
->posted
== 1) {
691 if (menu
->menu_term
!= NULL
)
692 menu
->menu_term(menu
);
693 if (menu
->item_term
!= NULL
)
694 menu
->item_term(menu
);
697 menu
->cur_item
= cur_item
;
700 if (menu
->posted
== 1) {
701 if (menu
->menu_init
!= NULL
)
702 menu
->menu_init(menu
);
703 if (menu
->item_init
!= NULL
)
704 menu
->item_init(menu
);
709 /* this should always be E_OK unless we are really screwed up */
714 * Return the current top row number.
717 top_row(MENU
*param_menu
)
719 MENU
*menu
= (param_menu
!= NULL
) ? param_menu
: &_menui_default_menu
;
721 if (menu
->items
== NULL
)
722 return E_NOT_CONNECTED
;
724 return menu
->top_row
;
728 * Position the cursor at the correct place in the menu.
732 pos_menu_cursor(MENU
*menu
)
737 return E_BAD_ARGUMENT
;
739 maxmark
= max(menu
->mark
.length
, menu
->unmark
.length
);
740 movx
= maxmark
+ (menu
->items
[menu
->cur_item
]->col
741 * (menu
->col_width
+ 1));
743 if (menu
->match_len
> 0)
744 movx
+= menu
->match_len
- 1;
747 menu
->items
[menu
->cur_item
]->row
- menu
->top_row
, movx
);