1 /* -*- c -*- ------------------------------------------------------------- *
2 *
3 * Copyright 2004-2005 Murali Krishnan Ganapathy - All Rights Reserved
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, Inc., 53 Temple Place Ste 330,
8 * Boston MA 02111-1307, USA; either version 2 of the License, or
9 * (at your option) any later version; incorporated herein by reference.
10 *
11 * ----------------------------------------------------------------------- */
12
13 #include "cmenu.h"
14 #include "com32io.h"
15 #include <stdlib.h>
16 #include <console.h>
17
18 // Local Variables
19 static pt_menusystem ms; // Pointer to the menusystem
20 char TITLESTR[] =
21 "COMBOOT Menu System for SYSLINUX developed by Murali Krishnan Ganapathy";
22 char TITLELONG[] = " TITLE too long ";
23 char ITEMLONG[] = " ITEM too long ";
24 char ACTIONLONG[] = " ACTION too long ";
25 char STATUSLONG[] = " STATUS too long ";
26 char EMPTYSTR[] = "";
27
28 /* Forward declarations */
29 int calc_visible(pt_menu menu, int first);
30 int next_visible(pt_menu menu, int index);
31 int prev_visible(pt_menu menu, int index);
32 int next_visible_sep(pt_menu menu, int index);
33 int prev_visible_sep(pt_menu menu, int index);
34 int calc_first_early(pt_menu menu, int curr);
35 int calc_first_late(pt_menu menu, int curr);
36 int isvisible(pt_menu menu, int first, int curr);
37
38 /* Basic Menu routines */
39
40 // This is same as inputc except it honors the ontimeout handler
41 // and calls it when needed. For the callee, there is no difference
42 // as this will not return unless a key has been pressed.
getch(void)43 static int getch(void)
44 {
45 t_timeout_handler th;
46 int key;
47 unsigned long i;
48
49 // Wait until keypress if no handler specified
50 if ((ms->ontimeout == NULL) && (ms->ontotaltimeout == NULL))
51 return get_key(stdin, 0);
52
53 th = ms->ontimeout;
54 for (;;) {
55 for (i = 0; i < ms->tm_numsteps; i++) {
56 key = get_key(stdin, ms->tm_stepsize);
57 if (key != KEY_NONE)
58 return key;
59
60 if ((ms->tm_total_timeout == 0) || (ms->ontotaltimeout == NULL))
61 continue; // Dont bother with calculations if no handler
62 ms->tm_sofar_timeout += ms->tm_stepsize;
63 if (ms->tm_sofar_timeout >= ms->tm_total_timeout) {
64 th = ms->ontotaltimeout;
65 ms->tm_sofar_timeout = 0;
66 break; // Get out of the for loop
67 }
68 }
69 if (!th)
70 continue; // no handler
71 key = th();
72 switch (key) {
73 case CODE_ENTER: // Pretend user hit enter
74 return KEY_ENTER;
75 case CODE_ESCAPE: // Pretend user hit escape
76 return KEY_ESC;
77 default:
78 break;
79 }
80 }
81 return KEY_NONE;
82 }
83
find_shortcut(pt_menu menu,uchar shortcut,int index)84 int find_shortcut(pt_menu menu, uchar shortcut, int index)
85 // Find the next index with specified shortcut key
86 {
87 int ans;
88 pt_menuitem mi;
89
90 // Garbage in garbage out
91 if ((index < 0) || (index >= menu->numitems))
92 return index;
93 ans = index + 1;
94 // Go till end of menu
95 while (ans < menu->numitems) {
96 mi = menu->items[ans];
97 if ((mi->action == OPT_INVISIBLE) || (mi->action == OPT_SEP)
98 || (mi->shortcut != shortcut))
99 ans++;
100 else
101 return ans;
102 }
103 // Start at the beginning and try again
104 ans = 0;
105 while (ans < index) {
106 mi = menu->items[ans];
107 if ((mi->action == OPT_INVISIBLE) || (mi->action == OPT_SEP)
108 || (mi->shortcut != shortcut))
109 ans++;
110 else
111 return ans;
112 }
113 return index; // Sorry not found
114 }
115
116 /* Redraw background and title */
reset_ui(void)117 static void reset_ui(void)
118 {
119 uchar tpos;
120
121 cls();
122 clearwindow(ms->minrow, ms->mincol, ms->maxrow, ms->maxcol,
123 ms->fillchar, ms->fillattr);
124
125 tpos = (ms->numcols - strlen(ms->title) - 1) >> 1; // center it on line
126 gotoxy(ms->minrow, ms->mincol);
127 cprint(ms->tfillchar, ms->titleattr, ms->numcols);
128 gotoxy(ms->minrow, ms->mincol + tpos);
129 csprint(ms->title, ms->titleattr);
130
131 cursoroff();
132 }
133
134 /*
135 * Print a menu item
136 *
137 * attr[0] is non-hilite attr, attr[1] is highlight attr
138 */
printmenuitem(const char * str,uchar * attr)139 void printmenuitem(const char *str, uchar * attr)
140 {
141 int hlite = NOHLITE; // Initially no highlighting
142
143 while (*str) {
144 switch (*str) {
145 case BELL: // No Bell Char
146 break;
147 case ENABLEHLITE: // Switch on highlighting
148 hlite = HLITE;
149 break;
150 case DISABLEHLITE: // Turn off highlighting
151 hlite = NOHLITE;
152 break;
153 default:
154 putch(*str, attr[hlite]);
155 }
156 str++;
157 }
158 }
159
160
161 /**
162 * print_line - Print a whole line in a menu
163 * @menu: current menu to handle
164 * @curr: index of the current entry highlighted
165 * @top: top coordinate of the @menu
166 * @left: left coordinate of the @menu
167 * @x: index in the menu of curr
168 * @row: row currently displayed
169 * @radio: radio item?
170 **/
print_line(pt_menu menu,int curr,uchar top,uchar left,int x,int row,bool radio)171 static void print_line(pt_menu menu, int curr, uchar top, uchar left,
172 int x, int row, bool radio)
173 {
174 pt_menuitem ci;
175 char fchar[6], lchar[6]; // The first and last char in for each entry
176 const char *str; // Item string (cf printmenuitem)
177 char sep[MENULEN]; // Separator (OPT_SEP)
178 uchar *attr; // Attribute
179 int menuwidth = menu->menuwidth + 3;
180
181 if (row >= menu->menuheight)
182 return;
183
184 ci = menu->items[x];
185
186 memset(sep, ms->box_horiz, menuwidth);
187 sep[menuwidth - 1] = 0;
188
189 // Setup the defaults now
190 if (radio) {
191 fchar[0] = '\b';
192 fchar[1] = SO;
193 fchar[2] = (x == curr ? RADIOSEL : RADIOUNSEL);
194 fchar[3] = SI;
195 fchar[4] = '\0'; // Unselected ( )
196 lchar[0] = '\0'; // Nothing special after
197 attr = ms->normalattr; // Always same attribute
198 } else {
199 lchar[0] = fchar[0] = ' ';
200 lchar[1] = fchar[1] = '\0'; // fchar and lchar are just spaces
201 attr = (x == curr ? ms->reverseattr : ms->normalattr); // Normal attributes
202 }
203 str = ci->item; // Pointer to item string
204 switch (ci->action) // set up attr,str,fchar,lchar for everything
205 {
206 case OPT_INACTIVE:
207 if (radio)
208 attr = ms->inactattr;
209 else
210 attr = (x == curr ? ms->revinactattr : ms->inactattr);
211 break;
212 case OPT_SUBMENU:
213 if (radio)
214 break; // Not supported for radio menu
215 lchar[0] = '>';
216 lchar[1] = 0;
217 break;
218 case OPT_RADIOMENU:
219 if (radio)
220 break; // Not supported for radio menu
221 lchar[0] = RADIOMENUCHAR;
222 lchar[1] = 0;
223 break;
224 case OPT_CHECKBOX:
225 if (radio)
226 break; // Not supported for radio menu
227 lchar[0] = '\b';
228 lchar[1] = SO;
229 lchar[2] = (ci->itemdata.checked ? CHECKED : UNCHECKED);
230 lchar[3] = SI;
231 lchar[4] = 0;
232 break;
233 case OPT_SEP:
234 fchar[0] = '\b';
235 fchar[1] = SO;
236 fchar[2] = LEFT_MIDDLE_BORDER;
237 fchar[3] = MIDDLE_BORDER;
238 fchar[4] = MIDDLE_BORDER;
239 fchar[5] = 0;
240 memset(sep, MIDDLE_BORDER, menuwidth);
241 sep[menuwidth - 1] = 0;
242 str = sep;
243 lchar[0] = MIDDLE_BORDER;
244 lchar[1] = RIGHT_MIDDLE_BORDER;
245 lchar[2] = SI;
246 lchar[3] = 0;
247 break;
248 case OPT_EXITMENU:
249 if (radio)
250 break; // Not supported for radio menu
251 fchar[0] = '<';
252 fchar[1] = 0;
253 break;
254 default: // Just to keep the compiler happy
255 break;
256 }
257
258 // Wipe area with spaces
259 gotoxy(top + row, left - 2);
260 cprint(ms->spacechar, attr[NOHLITE], menuwidth + 2);
261
262 // Print first part
263 gotoxy(top + row, left - 2);
264 csprint(fchar, attr[NOHLITE]);
265
266 // Print main part
267 gotoxy(top + row, left);
268 printmenuitem(str, attr);
269
270 // Print last part
271 gotoxy(top + row, left + menuwidth - 1);
272 csprint(lchar, attr[NOHLITE]);
273 }
274
275 // print the menu starting from FIRST
276 // will print a maximum of menu->menuheight items
printmenu(pt_menu menu,int curr,uchar top,uchar left,uchar first,bool radio)277 static void printmenu(pt_menu menu, int curr, uchar top, uchar left, uchar first, bool radio)
278 {
279 int x, row; // x = index, row = position from top
280 int numitems, menuwidth;
281 pt_menuitem ci;
282
283 numitems = calc_visible(menu, first);
284 if (numitems > menu->menuheight)
285 numitems = menu->menuheight;
286
287 menuwidth = menu->menuwidth + 3;
288 clearwindow(top, left - 2, top + numitems + 1, left + menuwidth + 1,
289 ms->fillchar, ms->shadowattr);
290 drawbox(top - 1, left - 3, top + numitems, left + menuwidth,
291 ms->normalattr[NOHLITE]);
292
293 // Menu title
294 x = (menuwidth - strlen(menu->title) - 1) >> 1;
295 gotoxy(top - 1, left + x);
296 printmenuitem(menu->title, ms->normalattr);
297
298 // All lines in the menu
299 row = -1; // 1 less than inital value of x
300 for (x = first; x < menu->numitems; x++) {
301 ci = menu->items[x];
302 if (ci->action == OPT_INVISIBLE)
303 continue;
304 row++;
305 if (row >= numitems)
306 break; // Already have enough number of items
307 print_line(menu, curr, top, left, x, row, radio);
308 }
309 // Check if we need to MOREABOVE and MOREBELOW to be added
310 // reuse x
311 row = 0;
312 x = next_visible_sep(menu, 0); // First item
313 if (!isvisible(menu, first, x)) // There is more above
314 {
315 row = 1;
316 gotoxy(top, left + menuwidth);
317 cprint(MOREABOVE, ms->normalattr[NOHLITE], 1);
318 }
319 x = prev_visible_sep(menu, menu->numitems); // last item
320 if (!isvisible(menu, first, x)) // There is more above
321 {
322 row = 1;
323 gotoxy(top + numitems - 1, left + menuwidth);
324 cprint(MOREBELOW, ms->normalattr[NOHLITE], 1);
325 }
326 // Add a scroll box
327 x = ((numitems - 1) * curr) / (menu->numitems);
328 if ((x > 0) && (row == 1)) {
329 gotoxy(top + x, left + menuwidth);
330 csprint("\016\141\017", ms->normalattr[NOHLITE]);
331 }
332 if (ms->handler)
333 ms->handler(ms, menu->items[curr]);
334 }
335
cleanupmenu(pt_menu menu,uchar top,uchar left,int numitems)336 void cleanupmenu(pt_menu menu, uchar top, uchar left, int numitems)
337 {
338 if (numitems > menu->menuheight)
339 numitems = menu->menuheight;
340 clearwindow(top, left - 2, top + numitems + 1, left + menu->menuwidth + 4, ms->fillchar, ms->fillattr); // Clear the shadow
341 clearwindow(top - 1, left - 3, top + numitems, left + menu->menuwidth + 3, ms->fillchar, ms->fillattr); // main window
342 }
343
344
345 /* Handle one menu */
getmenuoption(pt_menu menu,uchar top,uchar left,uchar startopt,bool radio)346 static pt_menuitem getmenuoption(pt_menu menu, uchar top, uchar left, uchar startopt, bool radio)
347 // Return item chosen or NULL if ESC was hit.
348 {
349 int prev, prev_first, curr, i, first, tmp;
350 int asc = 0;
351 bool redraw = true; // Need to draw the menu the first time
352 uchar numitems;
353 pt_menuitem ci; // Current item
354 t_handler_return hr; // Return value of handler
355
356 numitems = calc_visible(menu, 0);
357 // Setup status line
358 gotoxy(ms->minrow + ms->statline, ms->mincol);
359 cprint(ms->spacechar, ms->statusattr[NOHLITE], ms->numcols);
360
361 // Initialise current menu item
362 curr = next_visible(menu, startopt);
363 prev = curr;
364
365 gotoxy(ms->minrow + ms->statline, ms->mincol);
366 cprint(ms->spacechar, ms->statusattr[NOHLITE], ms->numcols);
367 gotoxy(ms->minrow + ms->statline, ms->mincol);
368 printmenuitem(menu->items[curr]->status, ms->statusattr);
369 first = calc_first_early(menu, curr);
370 prev_first = first;
371 while (1) // Forever
372 {
373 /* Redraw everything if:
374 * + we need to scroll (take care of scroll bars, ...)
375 * + menuoption
376 */
377 if (prev_first != first || redraw) {
378 printmenu(menu, curr, top, left, first, radio);
379 } else {
380 /* Redraw only the highlighted entry */
381 print_line(menu, curr, top, left, prev, prev - first, radio);
382 print_line(menu, curr, top, left, curr, curr - first, radio);
383 }
384 redraw = false;
385 prev = curr;
386 prev_first = first;
387 ci = menu->items[curr];
388 asc = getch();
389 switch (asc) {
390 case KEY_CTRL('L'):
391 redraw = true;
392 break;
393 case KEY_HOME:
394 curr = next_visible(menu, 0);
395 first = calc_first_early(menu, curr);
396 break;
397 case KEY_END:
398 curr = prev_visible(menu, numitems - 1);
399 first = calc_first_late(menu, curr);
400 break;
401 case KEY_PGDN:
402 for (i = 0; i < 5; i++)
403 curr = next_visible(menu, curr + 1);
404 first = calc_first_late(menu, curr);
405 break;
406 case KEY_PGUP:
407 for (i = 0; i < 5; i++)
408 curr = prev_visible(menu, curr - 1);
409 first = calc_first_early(menu, curr);
410 break;
411 case KEY_UP:
412 curr = prev_visible(menu, curr - 1);
413 if (curr < first)
414 first = calc_first_early(menu, curr);
415 break;
416 case KEY_DOWN:
417 curr = next_visible(menu, curr + 1);
418 if (!isvisible(menu, first, curr))
419 first = calc_first_late(menu, curr);
420 break;
421 case KEY_LEFT:
422 case KEY_ESC:
423 return NULL;
424 break;
425 case KEY_RIGHT:
426 case KEY_ENTER:
427 if (ci->action == OPT_INACTIVE)
428 break;
429 if (ci->action == OPT_CHECKBOX)
430 break;
431 if (ci->action == OPT_SEP)
432 break;
433 if (ci->action == OPT_EXITMENU)
434 return NULL; // As if we hit Esc
435 // If we are going into a radio menu, dont call handler, return ci
436 if (ci->action == OPT_RADIOMENU)
437 return ci;
438 if (ci->handler != NULL) // Do we have a handler
439 {
440 hr = ci->handler(ms, ci);
441 if (hr.refresh) // Do we need to refresh
442 {
443 // Cleanup menu using old number of items
444 cleanupmenu(menu, top, left, numitems);
445 // Recalculate the number of items
446 numitems = calc_visible(menu, 0);
447 // Reprint the menu
448 printmenu(menu, curr, top, left, first, radio);
449 }
450 if (hr.valid)
451 return ci;
452 } else
453 return ci;
454 break;
455 case SPACECHAR:
456 if (ci->action != OPT_CHECKBOX)
457 break;
458 ci->itemdata.checked = !ci->itemdata.checked;
459 if (ci->handler != NULL) // Do we have a handler
460 {
461 hr = ci->handler(ms, ci);
462 if (hr.refresh) // Do we need to refresh
463 {
464 // Cleanup menu using old number of items
465 cleanupmenu(menu, top, left, numitems);
466 // Recalculate the number of items
467 numitems = calc_visible(menu, 0);
468 // Reprint the menu
469 printmenu(menu, curr, top, left, first, radio);
470 }
471 }
472 break;
473 default:
474 // Check if this is a shortcut key
475 if (((asc >= 'A') && (asc <= 'Z')) ||
476 ((asc >= 'a') && (asc <= 'z')) ||
477 ((asc >= '0') && (asc <= '9'))) {
478 tmp = find_shortcut(menu, asc, curr);
479 if ((tmp > curr) && (!isvisible(menu, first, tmp)))
480 first = calc_first_late(menu, tmp);
481 if (tmp < curr)
482 first = calc_first_early(menu, tmp);
483 curr = tmp;
484 } else {
485 if (ms->keys_handler) // Call extra keys handler
486 ms->keys_handler(ms, menu->items[curr], asc);
487
488 /* The handler may have changed the UI, reset it on exit */
489 reset_ui();
490 // Cleanup menu using old number of items
491 cleanupmenu(menu, top, left, numitems);
492 // Recalculate the number of items
493 numitems = calc_visible(menu, 0);
494 // Reprint the menu
495 printmenu(menu, curr, top, left, first, radio);
496 }
497 break;
498 }
499 // Update status line
500 /* Erase the previous status */
501 gotoxy(ms->minrow + ms->statline, ms->mincol);
502 cprint(ms->spacechar, ms->statusattr[NOHLITE], ms->numcols);
503 /* Print the new status */
504 gotoxy(ms->minrow + ms->statline, ms->mincol);
505 printmenuitem(menu->items[curr]->status, ms->statusattr);
506 }
507 return NULL; // Should never come here
508 }
509
510 /* Handle the entire system of menu's. */
runmenusystem(uchar top,uchar left,pt_menu cmenu,uchar startopt,uchar menutype)511 pt_menuitem runmenusystem(uchar top, uchar left, pt_menu cmenu, uchar startopt,
512 uchar menutype)
513 /*
514 * cmenu
515 * Which menu should be currently displayed
516 * top,left
517 * What is the position of the top,left corner of the menu
518 * startopt
519 * which menu item do I start with
520 * menutype
521 * NORMALMENU or RADIOMENU
522 *
523 * Return Value:
524 * Returns a pointer to the final item chosen, or NULL if nothing chosen.
525 */
526 {
527 pt_menuitem opt, choice;
528 uchar startat, mt;
529 uchar row, col;
530
531 if (cmenu == NULL)
532 return NULL;
533
534 startover:
535 // Set the menu height
536 cmenu->menuheight = ms->maxrow - top - 3;
537 if (cmenu->menuheight > ms->maxmenuheight)
538 cmenu->menuheight = ms->maxmenuheight;
539 if (menutype == NORMALMENU)
540 opt = getmenuoption(cmenu, top, left, startopt, false);
541 else // menutype == RADIOMENU
542 opt = getmenuoption(cmenu, top, left, startopt, true);
543
544 if (opt == NULL) {
545 // User hit Esc
546 cleanupmenu(cmenu, top, left, calc_visible(cmenu, 0));
547 return NULL;
548 }
549 // Are we done with the menu system?
550 if ((opt->action != OPT_SUBMENU) && (opt->action != OPT_RADIOMENU)) {
551 cleanupmenu(cmenu, top, left, calc_visible(cmenu, 0));
552 return opt; // parent cleanup other menus
553 }
554 // Either radiomenu or submenu
555 // Do we have a valid menu number? The next hack uses the fact that
556 // itemdata.submenunum = itemdata.radiomenunum (since enum data type)
557 if (opt->itemdata.submenunum >= ms->nummenus) // This is Bad....
558 {
559 gotoxy(12, 12); // Middle of screen
560 csprint("ERROR: Invalid submenu requested.", 0x07);
561 cleanupmenu(cmenu, top, left, calc_visible(cmenu, 0));
562 return NULL; // Pretend user hit esc
563 }
564 // Call recursively for submenu
565 // Position the submenu below the current item,
566 // covering half the current window (horizontally)
567 row = ms->menus[(unsigned int)opt->itemdata.submenunum]->row;
568 col = ms->menus[(unsigned int)opt->itemdata.submenunum]->col;
569 if (row == 0xFF)
570 row = top + opt->index + 2;
571 if (col == 0xFF)
572 col = left + 3 + (cmenu->menuwidth >> 1);
573 mt = (opt->action == OPT_SUBMENU ? NORMALMENU : RADIOMENU);
574 startat = 0;
575 if ((opt->action == OPT_RADIOMENU) && (opt->data != NULL))
576 startat = ((t_menuitem *) opt->data)->index;
577
578 choice = runmenusystem(row, col,
579 ms->menus[(unsigned int)opt->itemdata.submenunum],
580 startat, mt);
581 if (opt->action == OPT_RADIOMENU) {
582 if (choice != NULL)
583 opt->data = (void *)choice; // store choice in data field
584 if (opt->handler != NULL)
585 opt->handler(ms, opt);
586 choice = NULL; // Pretend user hit esc
587 }
588 if (choice == NULL) // User hit Esc in submenu
589 {
590 // Startover
591 startopt = opt->index;
592 goto startover;
593 } else {
594 cleanupmenu(cmenu, top, left, calc_visible(cmenu, 0));
595 return choice;
596 }
597 }
598
599 // Finds the indexof the menu with given name
find_menu_num(const char * name)600 uchar find_menu_num(const char *name)
601 {
602 int i;
603 pt_menu m;
604
605 if (name == NULL)
606 return (uchar) (-1);
607 for (i = 0; i < ms->nummenus; i++) {
608 m = ms->menus[i];
609 if ((m->name) && (strcmp(m->name, name) == 0))
610 return i;
611 }
612 return (uchar) (-1);
613 }
614
615 // Run through all items and if they are submenus
616 // with a non-trivial "action" and trivial submenunum
617 // replace submenunum with the menu with name "action"
fix_submenus(void)618 void fix_submenus(void)
619 {
620 int i, j;
621 pt_menu m;
622 pt_menuitem mi;
623
624 i = 0;
625 for (i = 0; i < ms->nummenus; i++) {
626 m = ms->menus[i];
627 for (j = 0; j < m->numitems; j++) {
628 mi = m->items[j];
629 // if item is a submenu and has non-empty non-trivial data string
630 if (mi->data && strlen(mi->data) > 0 &&
631 ((mi->action == OPT_SUBMENU)
632 || (mi->action == OPT_RADIOMENU))) {
633 mi->itemdata.submenunum = find_menu_num(mi->data);
634 }
635 }
636 }
637 }
638
639 /* User Callable functions */
640
showmenus(uchar startmenu)641 pt_menuitem showmenus(uchar startmenu)
642 {
643 pt_menuitem rv;
644
645 fix_submenus(); // Fix submenu numbers incase nick names were used
646
647 /* Turn autowrap off, to avoid scrolling the menu */
648 printf(CSI "?7l");
649
650 // Setup screen for menusystem
651 reset_ui();
652
653 // Go, main menu cannot be a radio menu
654 rv = runmenusystem(ms->minrow + MENUROW, ms->mincol + MENUCOL,
655 ms->menus[(unsigned int)startmenu], 0, NORMALMENU);
656
657 // Hide the garbage we left on the screen
658 cls();
659 gotoxy(ms->minrow, ms->mincol);
660 cursoron();
661
662 // Return user choice
663 return rv;
664 }
665
init_menusystem(const char * title)666 pt_menusystem init_menusystem(const char *title)
667 {
668 int i;
669
670 ms = NULL;
671 ms = (pt_menusystem) malloc(sizeof(t_menusystem));
672 if (ms == NULL)
673 return NULL;
674 ms->nummenus = 0;
675 // Initialise all menu pointers
676 for (i = 0; i < MAXMENUS; i++)
677 ms->menus[i] = NULL;
678
679 ms->title = (char *)malloc(TITLELEN + 1);
680 if (title == NULL)
681 strcpy(ms->title, TITLESTR); // Copy string
682 else
683 strcpy(ms->title, title);
684
685 // Timeout settings
686 ms->tm_stepsize = TIMEOUTSTEPSIZE;
687 ms->tm_numsteps = TIMEOUTNUMSTEPS;
688
689 ms->normalattr[NOHLITE] = NORMALATTR;
690 ms->normalattr[HLITE] = NORMALHLITE;
691
692 ms->reverseattr[NOHLITE] = REVERSEATTR;
693 ms->reverseattr[HLITE] = REVERSEHLITE;
694
695 ms->inactattr[NOHLITE] = INACTATTR;
696 ms->inactattr[HLITE] = INACTHLITE;
697
698 ms->revinactattr[NOHLITE] = REVINACTATTR;
699 ms->revinactattr[HLITE] = REVINACTHLITE;
700
701 ms->statusattr[NOHLITE] = STATUSATTR;
702 ms->statusattr[HLITE] = STATUSHLITE;
703
704 ms->statline = STATLINE;
705 ms->tfillchar = TFILLCHAR;
706 ms->titleattr = TITLEATTR;
707
708 ms->fillchar = FILLCHAR;
709 ms->fillattr = FILLATTR;
710 ms->spacechar = SPACECHAR;
711 ms->shadowattr = SHADOWATTR;
712
713 ms->menupage = MENUPAGE; // Usually no need to change this at all
714
715 // Initialise all handlers
716 ms->handler = NULL;
717 ms->keys_handler = NULL;
718 ms->ontimeout = NULL; // No timeout handler
719 ms->tm_total_timeout = 0;
720 ms->tm_sofar_timeout = 0;
721 ms->ontotaltimeout = NULL;
722
723 // Setup ACTION_{,IN}VALID
724 ACTION_VALID.valid = 1;
725 ACTION_VALID.refresh = 0;
726 ACTION_INVALID.valid = 0;
727 ACTION_INVALID.refresh = 0;
728
729 // Figure out the size of the screen we are in now.
730 // By default we use the whole screen for our menu
731 if (getscreensize(1, &ms->numrows, &ms->numcols)) {
732 /* Unknown screen size? */
733 ms->numcols = 80;
734 ms->numrows = 24;
735 }
736 ms->minrow = ms->mincol = 0;
737 ms->maxcol = ms->numcols - 1;
738 ms->maxrow = ms->numrows - 1;
739
740 // How many entries per menu can we display at a time
741 ms->maxmenuheight = ms->maxrow - ms->minrow - 3;
742 if (ms->maxmenuheight > MAXMENUHEIGHT)
743 ms->maxmenuheight = MAXMENUHEIGHT;
744
745 console_ansi_raw();
746
747 return ms;
748 }
749
set_normal_attr(uchar normal,uchar selected,uchar inactivenormal,uchar inactiveselected)750 void set_normal_attr(uchar normal, uchar selected, uchar inactivenormal,
751 uchar inactiveselected)
752 {
753 if (normal != 0xFF)
754 ms->normalattr[0] = normal;
755 if (selected != 0xFF)
756 ms->reverseattr[0] = selected;
757 if (inactivenormal != 0xFF)
758 ms->inactattr[0] = inactivenormal;
759 if (inactiveselected != 0xFF)
760 ms->revinactattr[0] = inactiveselected;
761 }
762
set_normal_hlite(uchar normal,uchar selected,uchar inactivenormal,uchar inactiveselected)763 void set_normal_hlite(uchar normal, uchar selected, uchar inactivenormal,
764 uchar inactiveselected)
765 {
766 if (normal != 0xFF)
767 ms->normalattr[1] = normal;
768 if (selected != 0xFF)
769 ms->reverseattr[1] = selected;
770 if (inactivenormal != 0xFF)
771 ms->inactattr[1] = inactivenormal;
772 if (inactiveselected != 0xFF)
773 ms->revinactattr[1] = inactiveselected;
774 }
775
set_status_info(uchar statusattr,uchar statushlite,uchar statline)776 void set_status_info(uchar statusattr, uchar statushlite, uchar statline)
777 {
778 if (statusattr != 0xFF)
779 ms->statusattr[NOHLITE] = statusattr;
780 if (statushlite != 0xFF)
781 ms->statusattr[HLITE] = statushlite;
782 // statline is relative to minrow
783 if (statline >= ms->numrows)
784 statline = ms->numrows - 1;
785 ms->statline = statline; // relative to ms->minrow, 0 based
786 }
787
set_title_info(uchar tfillchar,uchar titleattr)788 void set_title_info(uchar tfillchar, uchar titleattr)
789 {
790 if (tfillchar != 0xFF)
791 ms->tfillchar = tfillchar;
792 if (titleattr != 0xFF)
793 ms->titleattr = titleattr;
794 }
795
set_misc_info(uchar fillchar,uchar fillattr,uchar spacechar,uchar shadowattr)796 void set_misc_info(uchar fillchar, uchar fillattr, uchar spacechar,
797 uchar shadowattr)
798 {
799 if (fillchar != 0xFF)
800 ms->fillchar = fillchar;
801 if (fillattr != 0xFF)
802 ms->fillattr = fillattr;
803 if (spacechar != 0xFF)
804 ms->spacechar = spacechar;
805 if (shadowattr != 0xFF)
806 ms->shadowattr = shadowattr;
807 }
808
set_menu_options(uchar maxmenuheight)809 void set_menu_options(uchar maxmenuheight)
810 {
811 if (maxmenuheight != 0xFF)
812 ms->maxmenuheight = maxmenuheight;
813 }
814
815 // Set the window which menusystem should use
set_window_size(uchar top,uchar left,uchar bot,uchar right)816 void set_window_size(uchar top, uchar left, uchar bot, uchar right)
817 {
818 int nr, nc;
819
820 if ((top > bot) || (left > right))
821 return; // Sorry no change will happen here
822
823 if (getscreensize(1, &nr, &nc)) {
824 /* Unknown screen size? */
825 nr = 80;
826 nc = 24;
827 }
828 if (bot >= nr)
829 bot = nr - 1;
830 if (right >= nc)
831 right = nc - 1;
832 ms->minrow = top;
833 ms->mincol = left;
834 ms->maxrow = bot;
835 ms->maxcol = right;
836 ms->numcols = right - left + 1;
837 ms->numrows = bot - top + 1;
838 if (ms->statline >= ms->numrows)
839 ms->statline = ms->numrows - 1; // Clip statline if need be
840 }
841
reg_handler(t_handler htype,void * handler)842 void reg_handler(t_handler htype, void *handler)
843 {
844 // If bad value set to default screen handler
845 switch (htype) {
846 case HDLR_KEYS:
847 ms->keys_handler = (t_keys_handler) handler;
848 break;
849 default:
850 ms->handler = (t_menusystem_handler) handler;
851 break;
852 }
853 }
854
unreg_handler(t_handler htype)855 void unreg_handler(t_handler htype)
856 {
857 switch (htype) {
858 case HDLR_KEYS:
859 ms->keys_handler = NULL;
860 break;
861 default:
862 ms->handler = NULL;
863 break;
864 }
865 }
866
reg_ontimeout(t_timeout_handler handler,unsigned int numsteps,unsigned int stepsize)867 void reg_ontimeout(t_timeout_handler handler, unsigned int numsteps,
868 unsigned int stepsize)
869 {
870 ms->ontimeout = handler;
871 if (numsteps != 0)
872 ms->tm_numsteps = numsteps;
873 if (stepsize != 0)
874 ms->tm_stepsize = stepsize;
875 }
876
unreg_ontimeout(void)877 void unreg_ontimeout(void)
878 {
879 ms->ontimeout = NULL;
880 }
881
reg_ontotaltimeout(t_timeout_handler handler,unsigned long numcentiseconds)882 void reg_ontotaltimeout(t_timeout_handler handler,
883 unsigned long numcentiseconds)
884 {
885 if (numcentiseconds != 0) {
886 ms->ontotaltimeout = handler;
887 ms->tm_total_timeout = numcentiseconds * 10; // to convert to milliseconds
888 ms->tm_sofar_timeout = 0;
889 }
890 }
891
unreg_ontotaltimeout(void)892 void unreg_ontotaltimeout(void)
893 {
894 ms->ontotaltimeout = NULL;
895 }
896
next_visible(pt_menu menu,int index)897 int next_visible(pt_menu menu, int index)
898 {
899 int ans;
900 if (index < 0)
901 ans = 0;
902 else if (index >= menu->numitems)
903 ans = menu->numitems - 1;
904 else
905 ans = index;
906 while ((ans < menu->numitems - 1) &&
907 ((menu->items[ans]->action == OPT_INVISIBLE) ||
908 (menu->items[ans]->action == OPT_SEP)))
909 ans++;
910 return ans;
911 }
912
prev_visible(pt_menu menu,int index)913 int prev_visible(pt_menu menu, int index) // Return index of prev visible
914 {
915 int ans;
916 if (index < 0)
917 ans = 0;
918 else if (index >= menu->numitems)
919 ans = menu->numitems - 1;
920 else
921 ans = index;
922 while ((ans > 0) &&
923 ((menu->items[ans]->action == OPT_INVISIBLE) ||
924 (menu->items[ans]->action == OPT_SEP)))
925 ans--;
926 return ans;
927 }
928
next_visible_sep(pt_menu menu,int index)929 int next_visible_sep(pt_menu menu, int index)
930 {
931 int ans;
932 if (index < 0)
933 ans = 0;
934 else if (index >= menu->numitems)
935 ans = menu->numitems - 1;
936 else
937 ans = index;
938 while ((ans < menu->numitems - 1) &&
939 (menu->items[ans]->action == OPT_INVISIBLE))
940 ans++;
941 return ans;
942 }
943
prev_visible_sep(pt_menu menu,int index)944 int prev_visible_sep(pt_menu menu, int index) // Return index of prev visible
945 {
946 int ans;
947 if (index < 0)
948 ans = 0;
949 else if (index >= menu->numitems)
950 ans = menu->numitems - 1;
951 else
952 ans = index;
953 while ((ans > 0) && (menu->items[ans]->action == OPT_INVISIBLE))
954 ans--;
955 return ans;
956 }
957
calc_visible(pt_menu menu,int first)958 int calc_visible(pt_menu menu, int first)
959 {
960 int ans, i;
961
962 if (menu == NULL)
963 return 0;
964 ans = 0;
965 for (i = first; i < menu->numitems; i++)
966 if (menu->items[i]->action != OPT_INVISIBLE)
967 ans++;
968 return ans;
969 }
970
971 // is curr visible if first entry is first?
isvisible(pt_menu menu,int first,int curr)972 int isvisible(pt_menu menu, int first, int curr)
973 {
974 if (curr < first)
975 return 0;
976 return (calc_visible(menu, first) - calc_visible(menu, curr) <
977 menu->menuheight);
978 }
979
980 // Calculate the first entry to be displayed
981 // so that curr is visible and make curr as late as possible
calc_first_late(pt_menu menu,int curr)982 int calc_first_late(pt_menu menu, int curr)
983 {
984 int ans, i, nv;
985
986 nv = calc_visible(menu, 0);
987 if (nv <= menu->menuheight)
988 return 0;
989 // Start with curr and go back menu->menuheight times
990 ans = curr + 1;
991 for (i = 0; i < menu->menuheight; i++)
992 ans = prev_visible_sep(menu, ans - 1);
993 return ans;
994 }
995
996 // Calculate the first entry to be displayed
997 // so that curr is visible and make curr as early as possible
calc_first_early(pt_menu menu,int curr)998 int calc_first_early(pt_menu menu, int curr)
999 {
1000 int ans, i, nv;
1001
1002 nv = calc_visible(menu, 0);
1003 if (nv <= menu->menuheight)
1004 return 0;
1005 // Start with curr and go back till >= menu->menuheight
1006 // items are visible
1007 nv = calc_visible(menu, curr); // Already nv of them are visible
1008 ans = curr;
1009 for (i = 0; i < menu->menuheight - nv; i++)
1010 ans = prev_visible_sep(menu, ans - 1);
1011 return ans;
1012 }
1013
1014 // Create a new menu and return its position
add_menu(const char * title,int maxmenusize)1015 uchar add_menu(const char *title, int maxmenusize)
1016 {
1017 int num, i;
1018 pt_menu m;
1019
1020 num = ms->nummenus;
1021 if (num >= MAXMENUS)
1022 return -1;
1023 m = NULL;
1024 m = (pt_menu) malloc(sizeof(t_menu));
1025 if (m == NULL)
1026 return -1;
1027 ms->menus[num] = m;
1028 m->numitems = 0;
1029 m->name = NULL;
1030 m->row = 0xFF;
1031 m->col = 0xFF;
1032 if (maxmenusize < 1)
1033 m->maxmenusize = MAXMENUSIZE;
1034 else
1035 m->maxmenusize = maxmenusize;
1036 m->items = (pt_menuitem *) malloc(sizeof(pt_menuitem) * (m->maxmenusize));
1037 for (i = 0; i < m->maxmenusize; i++)
1038 m->items[i] = NULL;
1039
1040 m->title = (char *)malloc(MENULEN + 1);
1041 if (title) {
1042 if (strlen(title) > MENULEN - 2)
1043 strcpy(m->title, TITLELONG);
1044 else
1045 strcpy(m->title, title);
1046 } else
1047 strcpy(m->title, EMPTYSTR);
1048 m->menuwidth = strlen(m->title);
1049 ms->nummenus++;
1050 return ms->nummenus - 1;
1051 }
1052
set_menu_name(const char * name)1053 void set_menu_name(const char *name) // Set the "name" of this menu
1054 {
1055 pt_menu m;
1056
1057 m = ms->menus[ms->nummenus - 1];
1058 if (m->name) // Free up previous name
1059 {
1060 free(m->name);
1061 m->name = NULL;
1062 }
1063
1064 if (name) {
1065 m->name = (char *)malloc(strlen(name) + 1);
1066 strcpy(m->name, name);
1067 }
1068 }
1069
1070 // Create a new named menu and return its position
add_named_menu(const char * name,const char * title,int maxmenusize)1071 uchar add_named_menu(const char *name, const char *title, int maxmenusize)
1072 {
1073 add_menu(title, maxmenusize);
1074 set_menu_name(name);
1075 return ms->nummenus - 1;
1076 }
1077
set_menu_pos(uchar row,uchar col)1078 void set_menu_pos(uchar row, uchar col) // Set the position of this menu.
1079 {
1080 pt_menu m;
1081
1082 m = ms->menus[ms->nummenus - 1];
1083 m->row = row;
1084 m->col = col;
1085 }
1086
add_sep(void)1087 pt_menuitem add_sep(void) // Add a separator to current menu
1088 {
1089 pt_menuitem mi;
1090 pt_menu m;
1091
1092 m = (ms->menus[ms->nummenus - 1]);
1093 mi = NULL;
1094 mi = (pt_menuitem) malloc(sizeof(t_menuitem));
1095 if (mi == NULL)
1096 return NULL;
1097 m->items[(unsigned int)m->numitems] = mi;
1098 mi->handler = NULL; // No handler
1099 mi->item = mi->status = mi->data = NULL;
1100 mi->action = OPT_SEP;
1101 mi->index = m->numitems++;
1102 mi->parindex = ms->nummenus - 1;
1103 mi->shortcut = 0;
1104 mi->helpid = 0;
1105 return mi;
1106 }
1107
1108 // Add item to the "current" menu
add_item(const char * item,const char * status,t_action action,const char * data,uchar itemdata)1109 pt_menuitem add_item(const char *item, const char *status, t_action action,
1110 const char *data, uchar itemdata)
1111 {
1112 pt_menuitem mi;
1113 pt_menu m;
1114 const char *str;
1115 uchar inhlite = 0; // Are we inside hlite area
1116
1117 m = (ms->menus[ms->nummenus - 1]);
1118 mi = NULL;
1119 mi = (pt_menuitem) malloc(sizeof(t_menuitem));
1120 if (mi == NULL)
1121 return NULL;
1122 m->items[(unsigned int)m->numitems] = mi;
1123 mi->handler = NULL; // No handler
1124
1125 // Allocate space to store stuff
1126 mi->item = (char *)malloc(MENULEN + 1);
1127 mi->status = (char *)malloc(STATLEN + 1);
1128 mi->data = (char *)malloc(ACTIONLEN + 1);
1129
1130 if (item) {
1131 if (strlen(item) > MENULEN) {
1132 strcpy(mi->item, ITEMLONG);
1133 } else {
1134 strcpy(mi->item, item);
1135 }
1136 if (strlen(mi->item) > m->menuwidth)
1137 m->menuwidth = strlen(mi->item);
1138 } else
1139 strcpy(mi->item, EMPTYSTR);
1140
1141 if (status) {
1142 if (strlen(status) > STATLEN) {
1143 strcpy(mi->status, STATUSLONG);
1144 } else {
1145 strcpy(mi->status, status);
1146 }
1147 } else
1148 strcpy(mi->status, EMPTYSTR);
1149
1150 mi->action = action;
1151 str = mi->item;
1152 mi->shortcut = 0;
1153 mi->helpid = 0xFFFF;
1154 inhlite = 0; // We have not yet seen an ENABLEHLITE char
1155 // Find the first char in [A-Za-z0-9] after ENABLEHLITE and not arg to control char
1156 while (*str) {
1157 if (*str == ENABLEHLITE) {
1158 inhlite = 1;
1159 }
1160 if (*str == DISABLEHLITE) {
1161 inhlite = 0;
1162 }
1163 if ((inhlite == 1) &&
1164 (((*str >= 'A') && (*str <= 'Z')) ||
1165 ((*str >= 'a') && (*str <= 'z')) ||
1166 ((*str >= '0') && (*str <= '9')))) {
1167 mi->shortcut = *str;
1168 break;
1169 }
1170 ++str;
1171 }
1172 if ((mi->shortcut >= 'A') && (mi->shortcut <= 'Z')) // Make lower case
1173 mi->shortcut = mi->shortcut - 'A' + 'a';
1174
1175 if (data) {
1176 if (strlen(data) > ACTIONLEN) {
1177 strcpy(mi->data, ACTIONLONG);
1178 } else {
1179 strcpy(mi->data, data);
1180 }
1181 } else
1182 strcpy(mi->data, EMPTYSTR);
1183
1184 switch (action) {
1185 case OPT_SUBMENU:
1186 mi->itemdata.submenunum = itemdata;
1187 break;
1188 case OPT_CHECKBOX:
1189 mi->itemdata.checked = itemdata;
1190 break;
1191 case OPT_RADIOMENU:
1192 mi->itemdata.radiomenunum = itemdata;
1193 if (mi->data)
1194 free(mi->data);
1195 mi->data = NULL; // No selection made
1196 break;
1197 default: // to keep the compiler happy
1198 break;
1199 }
1200 mi->index = m->numitems++;
1201 mi->parindex = ms->nummenus - 1;
1202 return mi;
1203 }
1204
1205 // Set the shortcut key for the current item
set_item_options(uchar shortcut,int helpid)1206 void set_item_options(uchar shortcut, int helpid)
1207 {
1208 pt_menuitem mi;
1209 pt_menu m;
1210
1211 m = (ms->menus[ms->nummenus - 1]);
1212 if (m->numitems <= 0)
1213 return;
1214 mi = m->items[(unsigned int)m->numitems - 1];
1215
1216 if (shortcut != 0xFF)
1217 mi->shortcut = shortcut;
1218 if (helpid != 0xFFFF)
1219 mi->helpid = helpid;
1220 }
1221
1222 // Free internal datasutructures
close_menusystem(void)1223 void close_menusystem(void)
1224 {
1225 }
1226
1227 // append_line_helper(pt_menu menu,char *line)
append_line_helper(int menunum,char * line)1228 void append_line_helper(int menunum, char *line)
1229 {
1230 pt_menu menu;
1231 pt_menuitem mi, ri;
1232 char *app;
1233 int ctr;
1234
1235 menu = ms->menus[menunum];
1236 for (ctr = 0; ctr < (int)menu->numitems; ctr++) {
1237 mi = menu->items[ctr];
1238 app = NULL; //What to append
1239 switch (mi->action) {
1240 case OPT_CHECKBOX:
1241 if (mi->itemdata.checked)
1242 app = mi->data;
1243 break;
1244 case OPT_RADIOMENU:
1245 if (mi->data) { // Some selection has been made
1246 ri = (pt_menuitem) (mi->data);
1247 app = ri->data;
1248 }
1249 break;
1250 case OPT_SUBMENU:
1251 append_line_helper(mi->itemdata.submenunum, line);
1252 break;
1253 default:
1254 break;
1255 }
1256 if (app) {
1257 strcat(line, " ");
1258 strcat(line, app);
1259 }
1260 }
1261 }
1262
1263 // Generate string based on state of checkboxes and radioitem in given menu
1264 // Assume line points to large enough buffer
gen_append_line(const char * menu_name,char * line)1265 void gen_append_line(const char *menu_name, char *line)
1266 {
1267 int menunum;
1268
1269 menunum = find_menu_num(menu_name);
1270 if (menunum < 0)
1271 return; // No such menu
1272 append_line_helper(menunum, line);
1273 }
1274