menu.c revision 1.9 1 /* $NetBSD: menu.c,v 1.9 2000/07/11 06:07:27 itohy 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(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