1 /* $NetBSD: menu.c,v 1.15 2002/11/27 11:53:11 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>
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.
142 return _menui_default_menu
.unmark
.string
;
144 return menu
->unmark
.string
;
148 * Set the menu window to the window passed.
151 set_menu_win(MENU
*menu
, WINDOW
*win
)
154 _menui_default_menu
.menu_win
= win
;
155 _menui_default_menu
.scrwin
= win
;
157 if (menu
->posted
== TRUE
) {
160 menu
->menu_win
= win
;
169 * Return the pointer to the menu window
175 return _menui_default_menu
.menu_win
;
177 return menu
->menu_win
;
181 * Set the menu subwindow for the menu.
184 set_menu_sub(menu
, sub
)
189 _menui_default_menu
.menu_subwin
= sub
;
190 _menui_default_menu
.scrwin
= sub
;
192 if (menu
->posted
== TRUE
)
195 menu
->menu_subwin
= sub
;
203 * Return the subwindow pointer for the menu
209 return _menui_default_menu
.menu_subwin
;
211 return menu
->menu_subwin
;
215 * Set the maximum number of rows and columns of items that may be displayed.
218 set_menu_format(MENU
*param_menu
, int rows
, int cols
)
220 MENU
*menu
= (param_menu
!= NULL
) ? param_menu
: &_menui_default_menu
;
225 if (menu
->items
!= NULL
)
226 /* recalculate the item neighbours */
227 return _menui_stitch_items(menu
);
233 * Return the max number of rows and cols that may be displayed.
236 menu_format(MENU
*param_menu
, int *rows
, int *cols
)
238 MENU
*menu
= (param_menu
!= NULL
) ? param_menu
: &_menui_default_menu
;
245 * Set the user defined function to call when a menu is posted.
248 set_menu_init(MENU
*menu
, Menu_Hook func
)
251 _menui_default_menu
.menu_init
= func
;
253 menu
->menu_init
= func
;
258 * Return the pointer to the menu init function.
261 menu_init(MENU
*menu
)
264 return _menui_default_menu
.menu_init
;
266 return menu
->menu_init
;
270 * Set the user defined function called when a menu is unposted.
273 set_menu_term(MENU
*menu
, Menu_Hook func
)
276 _menui_default_menu
.menu_term
= func
;
278 menu
->menu_term
= func
;
283 * Return the user defined menu termination function pointer.
286 menu_term(MENU
*menu
)
289 return _menui_default_menu
.menu_term
;
291 return menu
->menu_term
;
295 * Return the current menu options set.
298 menu_opts(MENU
*menu
)
301 return _menui_default_menu
.opts
;
307 * Set the menu options to the given options.
310 set_menu_opts(MENU
*param_menu
, OPTIONS opts
)
313 MENU
*menu
= (param_menu
!= NULL
) ? param_menu
: &_menui_default_menu
;
314 OPTIONS old_opts
= menu
->opts
;
319 * If the radio option is selected then make sure only one
320 * item is actually selected in the items.
322 if (((opts
& O_RADIO
) == O_RADIO
) && (menu
->items
!= NULL
) &&
323 (menu
->items
[0] != NULL
)) {
325 for (i
= 0; i
< menu
->item_count
; i
++) {
326 if (menu
->items
[i
]->selected
== 1) {
330 menu
->items
[i
]->selected
= 0;
335 /* if none selected, select the first item */
337 menu
->items
[0]->selected
= 1;
340 if ((menu
->opts
& O_ROWMAJOR
) != (old_opts
& O_ROWMAJOR
))
341 /* changed menu layout - need to recalc neighbours */
342 _menui_stitch_items(menu
);
348 * Turn on the options in menu given by opts.
351 menu_opts_on(MENU
*param_menu
, OPTIONS opts
)
354 MENU
*menu
= (param_menu
!= NULL
) ? param_menu
: &_menui_default_menu
;
355 OPTIONS old_opts
= menu
->opts
;
360 * If the radio option is selected then make sure only one
361 * item is actually selected in the items.
363 if (((opts
& O_RADIO
) == O_RADIO
) && (menu
->items
!= NULL
) &&
364 (menu
->items
[0] != NULL
)) {
366 for (i
= 0; i
< menu
->item_count
; i
++) {
367 if (menu
->items
[i
]->selected
== 1) {
371 menu
->items
[i
]->selected
= 0;
375 /* if none selected then select the top item */
377 menu
->items
[0]->selected
= 1;
380 if ((menu
->items
!= NULL
) &&
381 (menu
->opts
& O_ROWMAJOR
) != (old_opts
& O_ROWMAJOR
))
382 /* changed menu layout - need to recalc neighbours */
383 _menui_stitch_items(menu
);
389 * Turn off the menu options given in opts.
392 menu_opts_off(MENU
*param_menu
, OPTIONS opts
)
394 MENU
*menu
= (param_menu
!= NULL
) ? param_menu
: &_menui_default_menu
;
395 OPTIONS old_opts
= menu
->opts
;
397 menu
->opts
&= ~(opts
);
399 if ((menu
->items
!= NULL
) &&
400 (menu
->opts
& O_ROWMAJOR
) != (old_opts
& O_ROWMAJOR
))
401 /* changed menu layout - need to recalc neighbours */
402 _menui_stitch_items(menu
);
408 * Return the menu pattern buffer.
411 menu_pattern(MENU
*menu
)
414 return _menui_default_menu
.pattern
;
416 return menu
->pattern
;
420 * Set the menu pattern buffer to pat and attempt to match the pattern in
424 set_menu_pattern(MENU
*param_menu
, char *pat
)
426 MENU
*menu
= (param_menu
!= NULL
) ? param_menu
: &_menui_default_menu
;
429 /* check pattern is all printable characters */
431 if (!isprint((unsigned char) *p
++)) return E_BAD_ARGUMENT
;
433 if ((menu
->pattern
= (char *) realloc(menu
->pattern
,
434 sizeof(char) * strlen(pat
) + 1)) == NULL
)
435 return E_SYSTEM_ERROR
;
437 strcpy(menu
->pattern
, pat
);
438 menu
->plen
= strlen(pat
);
440 /* search item list for pat here */
441 return _menui_match_items(menu
, MATCH_FORWARD
, &menu
->cur_item
);
445 * Allocate a new menu structure and fill it in.
448 new_menu(ITEM
**items
)
452 if ((the_menu
= (MENU
*)malloc(sizeof(MENU
))) == NULL
)
455 /* copy the defaults */
456 (void)memcpy(the_menu
, &_menui_default_menu
, sizeof(MENU
));
458 /* set a default window if none already set. */
459 if (the_menu
->menu_win
== NULL
)
460 the_menu
->scrwin
= stdscr
;
462 /* make a private copy of the mark string */
463 if (_menui_default_menu
.mark
.string
!= NULL
) {
464 if ((the_menu
->mark
.string
=
465 (char *) malloc((unsigned) _menui_default_menu
.mark
.length
+ 1))
471 strlcpy(the_menu
->mark
.string
, _menui_default_menu
.mark
.string
,
472 (unsigned) _menui_default_menu
.mark
.length
+ 1);
475 /* make a private copy of the unmark string too */
476 if (_menui_default_menu
.unmark
.string
!= NULL
) {
477 if ((the_menu
->unmark
.string
=
478 (char *) malloc((unsigned) _menui_default_menu
.unmark
.length
+ 1))
484 strlcpy(the_menu
->unmark
.string
,
485 _menui_default_menu
.unmark
.string
,
486 (unsigned) _menui_default_menu
.unmark
.length
+ 1 );
489 /* now attach the items, if any */
491 if(set_menu_items(the_menu
, items
) < 0) {
492 if (the_menu
->mark
.string
!= NULL
)
493 free(the_menu
->mark
.string
);
494 if (the_menu
->unmark
.string
!= NULL
)
495 free(the_menu
->unmark
.string
);
505 * Free up storage allocated to the menu object and destroy it.
508 free_menu(MENU
*menu
)
513 return E_BAD_ARGUMENT
;
515 if (menu
->posted
!= 0)
518 if (menu
->pattern
!= NULL
)
521 if (menu
->mark
.string
!= NULL
)
522 free(menu
->mark
.string
);
524 if (menu
->items
!= NULL
) {
525 /* disconnect the items from this menu */
526 for (i
= 0; i
< menu
->item_count
; i
++) {
527 menu
->items
[i
]->parent
= NULL
;
536 * Calculate the minimum window size for the menu.
539 scale_menu(MENU
*param_menu
, int *rows
, int *cols
)
541 MENU
*menu
= (param_menu
!= NULL
) ? param_menu
: &_menui_default_menu
;
543 if (menu
->items
== NULL
)
544 return E_BAD_ARGUMENT
;
546 /* calculate the max item size */
547 _menui_max_item_size(menu
);
550 *cols
= menu
->cols
* menu
->max_item_width
;
553 * allow for spacing between columns...
555 *cols
+= (menu
->cols
- 1);
561 * Set the menu item list to the one given.
564 set_menu_items(MENU
*param_menu
, ITEM
**items
)
566 MENU
*menu
= (param_menu
!= NULL
) ? param_menu
: &_menui_default_menu
;
567 int i
, new_count
= 0, sel_count
= 0;
569 /* don't change if menu is posted */
570 if (menu
->posted
== 1)
573 /* count the new items and validate none are connected already */
574 while (items
[new_count
] != NULL
) {
575 if ((items
[new_count
]->parent
!= NULL
) &&
576 (items
[new_count
]->parent
!= menu
))
578 if (items
[new_count
]->selected
== 1)
584 * don't allow multiple selected items if menu is radio
587 if (((menu
->opts
& O_RADIO
) == O_RADIO
) &&
589 return E_BAD_ARGUMENT
;
591 /* if there were items connected then disconnect them. */
592 if (menu
->items
!= NULL
) {
593 for (i
= 0; i
< menu
->item_count
; i
++) {
594 menu
->items
[i
]->parent
= NULL
;
595 menu
->items
[i
]->index
= -1;
599 menu
->item_count
= new_count
;
601 /* connect the new items to the menu */
602 for (i
= 0; i
< new_count
; i
++) {
603 items
[i
]->parent
= menu
;
608 menu
->cur_item
= 0; /* reset current item just in case */
609 menu
->top_row
= 0; /* and the top row too */
610 if (menu
->pattern
!= NULL
) { /* and the pattern buffer....sigh */
617 * make sure at least one item is selected on a radio
620 if (((menu
->opts
& O_RADIO
) == O_RADIO
) && (sel_count
== 0))
621 menu
->items
[0]->selected
= 1;
624 _menui_stitch_items(menu
); /* recalculate the item neighbours */
630 * Return the pointer to the menu items array.
633 menu_items(MENU
*menu
)
636 return _menui_default_menu
.items
;
642 * Return the count of items connected to the menu
645 item_count(MENU
*menu
)
648 return _menui_default_menu
.item_count
;
650 return menu
->item_count
;
654 * Set the menu top row to be the given row. The current item becomes the
655 * leftmost item on that row in the menu.
658 set_top_row(MENU
*param_menu
, int row
)
660 MENU
*menu
= (param_menu
!= NULL
) ? param_menu
: &_menui_default_menu
;
661 int i
, cur_item
, state
= E_SYSTEM_ERROR
;
663 if (row
> menu
->item_rows
)
664 return E_BAD_ARGUMENT
;
666 if (menu
->items
== NULL
)
667 return E_NOT_CONNECTED
;
669 if (menu
->in_init
== 1)
674 for (i
= 0; i
< menu
->item_count
; i
++) {
675 /* search for first item that matches row - this will be
677 if (row
== menu
->items
[i
]->row
) {
680 break; /* found what we want - no need to go further */
684 menu
->in_init
= 1; /* just in case we call the init/term routines */
686 if (menu
->posted
== 1) {
687 if (menu
->menu_term
!= NULL
)
688 menu
->menu_term(menu
);
689 if (menu
->item_term
!= NULL
)
690 menu
->item_term(menu
);
693 menu
->cur_item
= cur_item
;
696 if (menu
->posted
== 1) {
697 if (menu
->menu_init
!= NULL
)
698 menu
->menu_init(menu
);
699 if (menu
->item_init
!= NULL
)
700 menu
->item_init(menu
);
705 /* this should always be E_OK unless we are really screwed up */
710 * Return the current top row number.
713 top_row(MENU
*param_menu
)
715 MENU
*menu
= (param_menu
!= NULL
) ? param_menu
: &_menui_default_menu
;
717 if (menu
->items
== NULL
)
718 return E_NOT_CONNECTED
;
720 return menu
->top_row
;
724 * Position the cursor at the correct place in the menu.
728 pos_menu_cursor(MENU
*menu
)
733 return E_BAD_ARGUMENT
;
735 maxmark
= max(menu
->mark
.length
, menu
->unmark
.length
);
736 movx
= maxmark
+ (menu
->items
[menu
->cur_item
]->col
737 * (menu
->col_width
+ 1));
739 if (menu
->match_len
> 0)
740 movx
+= menu
->match_len
- 1;
743 menu
->items
[menu
->cur_item
]->row
- menu
->top_row
, movx
);