Adding debian version 3.35-1.
[syslinux-debian/hramrach.git] / menu / libmenu / menu.c
blob841e59caaf3fbf5038e98227003d8ecfd24ff5c4
1 /* -*- c -*- ------------------------------------------------------------- *
3 * Copyright 2004-2005 Murali Krishnan Ganapathy - All Rights Reserved
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.
11 * ----------------------------------------------------------------------- */
13 #include "menu.h"
14 #include "com32io.h"
15 #include <stdlib.h>
17 // Local Variables
18 static pt_menusystem ms; // Pointer to the menusystem
19 char TITLESTR[] = "COMBOOT Menu System for SYSLINUX developed by Murali Krishnan Ganapathy";
20 char TITLELONG[] = " TITLE too long ";
21 char ITEMLONG[] = " ITEM too long ";
22 char ACTIONLONG[] = " ACTION too long ";
23 char STATUSLONG[] = " STATUS too long ";
24 char EMPTYSTR[] = "";
26 /* Forward declarations */
27 int calc_visible(pt_menu menu,int first);
28 int next_visible(pt_menu menu,int index);
29 int prev_visible(pt_menu menu,int index);
30 int next_visible_sep(pt_menu menu,int index);
31 int prev_visible_sep(pt_menu menu,int index);
32 int calc_first_early(pt_menu menu,int curr);
33 int calc_first_late(pt_menu menu,int curr);
34 int isvisible(pt_menu menu,int first, int curr);
37 /* Basic Menu routines */
39 // This is same as inputc except it honors the ontimeout handler
40 // and calls it when needed. For the callee, there is no difference
41 // as this will not return unless a key has been pressed.
42 char getch(char *scan)
44 unsigned long i;
45 TIMEOUTCODE c;
46 t_timeout_handler th;
48 // Wait until keypress if no handler specified
49 if ((ms->ontimeout==NULL) && (ms->ontotaltimeout==NULL)) return inputc(scan);
51 th = ms->ontimeout;
52 while (1) // Forever do
54 for (i=0; i < ms->tm_numsteps; i++)
56 if (checkkbdbuf()) return inputc(scan);
57 sleep(ms->tm_stepsize);
58 if ( (ms->tm_total_timeout == 0) || (ms->ontotaltimeout==NULL))
59 continue; // Dont bother with calculations if no handler
60 ms->tm_sofar_timeout += ms->tm_stepsize;
61 if (ms->tm_sofar_timeout >= ms->tm_total_timeout) {
62 th = ms->ontotaltimeout;
63 ms->tm_sofar_timeout = 0;
64 break; // Get out of the for loop
67 if (!th) continue; // no handler dont call
68 c = th();
69 switch(c)
71 case CODE_ENTER: // Pretend user hit enter
72 *scan = ENTERA;
73 return '\015'; // \015 octal = 13
74 case CODE_ESCAPE: // Pretend user hit escape
75 *scan = ESCAPE;
76 return '\033'; // \033 octal = 27
77 default:
78 break;
81 return 0;
84 /* Print a menu item */
85 /* attr[0] is non-hilite attr, attr[1] is highlight attr */
86 void printmenuitem(const char *str,uchar* attr)
88 uchar page = getdisppage();
89 uchar row,col;
90 int hlite=NOHLITE; // Initially no highlighting
92 getpos(&row,&col,page);
93 while ( *str ) {
94 switch (*str)
96 case '\b':
97 --col;
98 break;
99 case '\n':
100 ++row;
101 break;
102 case '\r':
103 col=0;
104 break;
105 case BELL: // No Bell Char
106 break;
107 case ENABLEHLITE: // Switch on highlighting
108 hlite = HLITE;
109 break;
110 case DISABLEHLITE: // Turn off highlighting
111 hlite = NOHLITE;
112 break;
113 default:
114 putch(*str, attr[hlite], page);
115 ++col;
117 if (col > getnumcols())
119 ++row;
120 col=0;
122 if (row > getnumrows())
124 scrollup();
125 row= getnumrows();
127 gotoxy(row,col,page);
128 str++;
132 int find_shortcut(pt_menu menu,uchar shortcut, int index)
133 // Find the next index with specified shortcut key
135 int ans;
136 pt_menuitem mi;
138 // Garbage in garbage out
139 if ((index <0) || (index >= menu->numitems)) return index;
140 ans = index+1;
141 // Go till end of menu
142 while (ans < menu->numitems)
144 mi = menu->items[ans];
145 if ((mi->action == OPT_INVISIBLE) || (mi->action == OPT_SEP)
146 || (mi->shortcut != shortcut))
147 ans ++;
148 else return ans;
150 // Start at the beginning and try again
151 ans = 0;
152 while (ans < index)
154 mi = menu->items[ans];
155 if ((mi->action == OPT_INVISIBLE) || (mi->action == OPT_SEP)
156 || (mi->shortcut != shortcut))
157 ans ++;
158 else return ans;
160 return index; // Sorry not found
163 // print the menu starting from FIRST
164 // will print a maximum of menu->menuheight items
165 void printmenu(pt_menu menu, int curr, uchar top, uchar left, uchar first)
167 int x,row; // x = index, row = position from top
168 int numitems,menuwidth;
169 char fchar[5],lchar[5]; // The first and last char in for each entry
170 const char *str; // and inbetween the item or a seperator is printed
171 uchar *attr; // attribute attr
172 char sep[MENULEN];// and inbetween the item or a seperator is printed
173 pt_menuitem ci;
175 numitems = calc_visible(menu,first);
176 if (numitems > menu->menuheight) numitems = menu->menuheight;
178 menuwidth = menu->menuwidth+3;
179 clearwindow(top,left-2, top+numitems+1, left+menuwidth+1,
180 ms->menupage, ms->fillchar, ms->shadowattr);
181 drawbox(top-1,left-3,top+numitems,left+menuwidth,
182 ms->menupage,ms->normalattr[NOHLITE],ms->menubt);
183 memset(sep,ms->box_horiz,menuwidth); // String containing the seperator string
184 sep[menuwidth-1] = 0;
185 // Menu title
186 x = (menuwidth - strlen(menu->title) - 1) >> 1;
187 gotoxy(top-1,left+x,ms->menupage);
188 printmenuitem(menu->title,ms->normalattr);
189 row = -1; // 1 less than inital value of x
190 for (x=first; x < menu->numitems; x++)
192 ci = menu->items[x];
193 if (ci->action == OPT_INVISIBLE) continue;
194 row++;
195 if (row >= numitems) break; // Already have enough number of items
196 // Setup the defaults now
197 lchar[0] = fchar[0] = ' ';
198 lchar[1] = fchar[1] = '\0'; // fchar and lchar are just spaces
199 str = ci->item; // Pointer to item string
200 attr = (x==curr ? ms->reverseattr : ms->normalattr); // Normal attributes
201 switch (ci->action) // set up attr,str,fchar,lchar for everything
203 case OPT_INACTIVE:
204 attr = (x==curr? ms->revinactattr : ms->inactattr);
205 break;
206 case OPT_SUBMENU:
207 lchar[0] = SUBMENUCHAR; lchar[1] = 0;
208 break;
209 case OPT_RADIOMENU:
210 lchar[0] = RADIOMENUCHAR; lchar[1] = 0;
211 break;
212 case OPT_CHECKBOX:
213 lchar[0] = (ci->itemdata.checked ? CHECKED : UNCHECKED);
214 lchar[1] = 0;
215 break;
216 case OPT_SEP:
217 fchar[0] = '\b'; fchar[1] = ms->box_ltrt; fchar[2] = ms->box_horiz; fchar[3] = ms->box_horiz; fchar[4] = 0;
218 lchar[0] = ms->box_horiz; lchar[1] = ms->box_rtlt; lchar[2] = 0;
219 str = sep;
220 break;
221 case OPT_EXITMENU:
222 fchar[0] = EXITMENUCHAR; fchar[1] = 0;
223 break;
224 default: // Just to keep the compiler happy
225 break;
227 gotoxy(top+row,left-2,ms->menupage);
228 cprint(ms->spacechar,attr[NOHLITE],menuwidth+2,ms->menupage); // Wipe area with spaces
229 gotoxy(top+row,left-2,ms->menupage);
230 csprint(fchar,attr[NOHLITE]); // Print first part
231 gotoxy(top+row,left,ms->menupage);
232 printmenuitem(str,attr); // Print main part
233 gotoxy(top+row,left+menuwidth-1,ms->menupage); // Last char if any
234 csprint(lchar,attr[NOHLITE]); // Print last part
236 // Check if we need to MOREABOVE and MOREBELOW to be added
237 // reuse x
238 row = 0;
239 x = next_visible_sep(menu,0); // First item
240 if (! isvisible(menu,first,x)) // There is more above
242 row = 1;
243 gotoxy(top,left+menuwidth,ms->menupage);
244 cprint(MOREABOVE,ms->normalattr[NOHLITE],1,ms->menupage);
246 x = prev_visible_sep(menu,menu->numitems); // last item
247 if (! isvisible(menu,first,x)) // There is more above
249 row = 1;
250 gotoxy(top+numitems-1,left+menuwidth,ms->menupage);
251 cprint(MOREBELOW,ms->normalattr[NOHLITE],1,ms->menupage);
253 // Add a scroll box
254 x = ((numitems-1)*curr)/(menu->numitems);
255 if ((x>0) && (row==1)) {
256 gotoxy(top+x,left+menuwidth,ms->menupage);
257 cprint(SCROLLBOX,ms->normalattr[NOHLITE],1,ms->menupage);
259 if (ms->handler) ms->handler(ms,menu->items[curr]);
262 // Difference between this and regular menu, is that only
263 // OPT_INVISIBLE, OPT_SEP are honoured
264 void printradiomenu(pt_menu menu, int curr, uchar top, uchar left, int first)
266 int x,row; // x = index, row = position from top
267 int numitems,menuwidth;
268 char fchar[5],lchar[5]; // The first and last char in for each entry
269 const char *str; // and inbetween the item or a seperator is printed
270 uchar *attr; // all in the attribute attr
271 char sep[MENULEN];// and inbetween the item or a seperator is printed
272 pt_menuitem ci;
274 numitems = calc_visible(menu,first);
275 if (numitems > menu->menuheight) numitems = menu->menuheight;
277 menuwidth = menu->menuwidth+3;
278 clearwindow(top,left-2, top+numitems+1, left+menuwidth+1,
279 ms->menupage, ms->fillchar, ms->shadowattr);
280 drawbox(top-1,left-3,top+numitems,left+menuwidth,
281 ms->menupage,ms->normalattr[NOHLITE],ms->menubt);
282 memset(sep,ms->box_horiz,menuwidth); // String containing the seperator string
283 sep[menuwidth-1] = 0;
284 // Menu title
285 x = (menuwidth - strlen(menu->title) - 1) >> 1;
286 gotoxy(top-1,left+x,ms->menupage);
287 printmenuitem(menu->title,ms->normalattr);
288 row = -1; // 1 less than inital value of x
289 for (x=first; x < menu->numitems; x++)
291 ci = menu->items[x];
292 if (ci->action == OPT_INVISIBLE) continue;
293 row++;
294 if (row > numitems) break;
295 // Setup the defaults now
296 fchar[0] = RADIOUNSEL; fchar[1]='\0'; // Unselected ( )
297 lchar[0] = '\0'; // Nothing special after
298 str = ci->item; // Pointer to item string
299 attr = ms->normalattr; // Always same attribute
300 fchar[0] = (x==curr ? RADIOSEL : RADIOUNSEL);
301 switch (ci->action) // set up attr,str,fchar,lchar for everything
303 case OPT_INACTIVE:
304 attr = ms->inactattr;
305 break;
306 case OPT_SEP:
307 fchar[0] = '\b'; fchar[1] = ms->box_ltrt; fchar[2] = ms->box_horiz; fchar[3] = ms->box_horiz; fchar[4] = 0;
308 lchar[0] = ms->box_horiz; lchar[1] = ms->box_rtlt; lchar[3] = 0;
309 str = sep;
310 break;
311 default: // To keep the compiler happy
312 break;
314 gotoxy(top+row,left-2,ms->menupage);
315 cprint(ms->spacechar,attr[NOHLITE],menuwidth+2,ms->menupage); // Wipe area with spaces
316 gotoxy(top+row,left-2,ms->menupage);
317 csprint(fchar,attr[NOHLITE]); // Print first part
318 gotoxy(top+row,left,ms->menupage);
319 printmenuitem(str,attr); // Print main part
320 gotoxy(top+row,left+menuwidth-1,ms->menupage); // Last char if any
321 csprint(lchar,attr[NOHLITE]); // Print last part
323 // Check if we need to MOREABOVE and MOREBELOW to be added
324 // reuse x
325 row = 0;
326 x = next_visible_sep(menu,0); // First item
327 if (! isvisible(menu,first,x)) // There is more above
329 row = 1;
330 gotoxy(top,left+menuwidth,ms->menupage);
331 cprint(MOREABOVE,ms->normalattr[NOHLITE],1,ms->menupage);
333 x = prev_visible_sep(menu,menu->numitems); // last item
334 if (! isvisible(menu,first,x)) // There is more above
336 row = 1;
337 gotoxy(top+numitems-1,left+menuwidth,ms->menupage);
338 cprint(MOREBELOW,ms->normalattr[NOHLITE],1,ms->menupage);
340 // Add a scroll box
341 x = ((numitems-1)*curr)/(menu->numitems);
342 if ((x > 0) && (row == 1))
344 gotoxy(top+x,left+menuwidth,ms->menupage);
345 cprint(SCROLLBOX,ms->normalattr[NOHLITE],1,ms->menupage);
347 if (ms->handler) ms->handler(ms,menu->items[curr]);
350 void cleanupmenu(pt_menu menu, uchar top,uchar left,int numitems)
352 if (numitems > menu->menuheight) numitems = menu->menuheight;
353 clearwindow(top,left-2, top+numitems+1, left+menu->menuwidth+4,
354 ms->menupage, ms->fillchar, ms->fillattr); // Clear the shadow
355 clearwindow(top-1, left-3, top+numitems, left+menu->menuwidth+3,
356 ms->menupage, ms->fillchar, ms->fillattr); // main window
359 /* Handle a radio menu */
360 pt_menuitem getradiooption(pt_menu menu, uchar top, uchar left, uchar startopt)
361 // Return item chosen or NULL if ESC was hit.
363 int curr,i,first,tmp;
364 uchar asc,scan;
365 uchar numitems;
366 pt_menuitem ci; // Current item
368 numitems = calc_visible(menu,0);
369 // Setup status line
370 gotoxy(ms->minrow+ms->statline,ms->mincol,ms->menupage);
371 cprint(ms->spacechar,ms->statusattr[NOHLITE],ms->numcols,ms->menupage);
373 // Initialise current menu item
374 curr = next_visible(menu,startopt);
376 gotoxy(ms->minrow+ms->statline,ms->mincol,ms->menupage);
377 cprint(ms->spacechar,ms->statusattr[NOHLITE],ms->numcols,1);
378 gotoxy(ms->minrow+ms->statline,ms->mincol,ms->menupage);
379 printmenuitem(menu->items[curr]->status,ms->statusattr);
380 first = calc_first_early(menu,curr);
381 while (1) // Forever
383 printradiomenu(menu,curr,top,left,first);
384 ci = menu->items[curr];
386 asc = getch(&scan);
387 switch (scan)
389 case HOMEKEY:
390 curr = next_visible(menu,0);
391 first = calc_first_early(menu,curr);
392 break;
393 case ENDKEY:
394 curr = prev_visible(menu,numitems-1);
395 first = calc_first_late(menu,curr);
396 break;
397 case PAGEDN:
398 for (i=0; i < 5; i++) curr = next_visible(menu,curr+1);
399 first = calc_first_late(menu,curr);
400 break;
401 case PAGEUP:
402 for (i=0; i < 5; i++) curr = prev_visible(menu,curr-1);
403 first = calc_first_early(menu,curr);
404 break;
405 case UPARROW:
406 curr = prev_visible(menu,curr-1);
407 if (curr < first) first = calc_first_early(menu,curr);
408 break;
409 case DNARROW:
410 curr = next_visible(menu,curr+1);
411 if (! isvisible(menu,first,curr))
412 first = calc_first_late(menu,curr);
413 break;
414 case LTARROW:
415 case ESCAPE:
416 return NULL;
417 break;
418 case RTARROW:
419 case ENTERA:
420 case ENTERB:
421 if (ci->action == OPT_INACTIVE) break;
422 if (ci->action == OPT_SEP) break;
423 return ci;
424 break;
425 default:
426 // Check if this is a shortcut key
427 if (((asc >= 'A') && (asc <= 'Z')) ||
428 ((asc >= 'a') && (asc <= 'z')) ||
429 ((asc >= '0') && (asc <= '9')))
431 tmp = find_shortcut(menu,asc,curr);
432 if ((tmp > curr) && (! isvisible(menu,first,tmp)))
433 first = calc_first_late(menu,tmp);
434 if (tmp < curr)
435 first = calc_first_early(menu,tmp);
436 curr = tmp;
438 else {
439 if (ms->keys_handler) // Call extra keys handler
440 ms->keys_handler(ms,menu->items[curr],(scan << 8) | asc);
442 break;
444 // Update status line
445 gotoxy(ms->minrow+ms->statline,ms->mincol,ms->menupage);
446 cprint(ms->spacechar,ms->statusattr[NOHLITE],ms->numcols,ms->menupage);
447 printmenuitem(menu->items[curr]->status,ms->statusattr);
449 return NULL; // Should never come here
452 /* Handle one menu */
453 pt_menuitem getmenuoption(pt_menu menu, uchar top, uchar left, uchar startopt)
454 // Return item chosen or NULL if ESC was hit.
456 int curr,i,first,tmp;
457 uchar asc,scan;
458 uchar numitems;
459 pt_menuitem ci; // Current item
460 t_handler_return hr; // Return value of handler
462 numitems = calc_visible(menu,0);
463 // Setup status line
464 gotoxy(ms->minrow+ms->statline,ms->mincol,ms->menupage);
465 cprint(ms->spacechar,ms->statusattr[NOHLITE],ms->numcols,ms->menupage);
467 // Initialise current menu item
468 curr = next_visible(menu,startopt);
470 gotoxy(ms->minrow+ms->statline,ms->mincol,ms->menupage);
471 cprint(ms->spacechar,ms->statusattr[NOHLITE],ms->numcols,1);
472 gotoxy(ms->minrow+ms->statline,ms->mincol,ms->menupage);
473 printmenuitem(menu->items[curr]->status,ms->statusattr);
474 first = calc_first_early(menu,curr);
475 while (1) // Forever
477 printmenu(menu,curr,top,left,first);
478 ci = menu->items[curr];
479 asc = getch(&scan);
480 switch (scan)
482 case HOMEKEY:
483 curr = next_visible(menu,0);
484 first = calc_first_early(menu,curr);
485 break;
486 case ENDKEY:
487 curr = prev_visible(menu,numitems-1);
488 first = calc_first_late(menu,curr);
489 break;
490 case PAGEDN:
491 for (i=0; i < 5; i++) curr = next_visible(menu,curr+1);
492 first = calc_first_late(menu,curr);
493 break;
494 case PAGEUP:
495 for (i=0; i < 5; i++) curr = prev_visible(menu,curr-1);
496 first = calc_first_early(menu,curr);
497 break;
498 case UPARROW:
499 curr = prev_visible(menu,curr-1);
500 if (curr < first) first = calc_first_early(menu,curr);
501 break;
502 case DNARROW:
503 curr = next_visible(menu,curr+1);
504 if (! isvisible(menu,first,curr))
505 first = calc_first_late(menu,curr);
506 break;
507 case LTARROW:
508 case ESCAPE:
509 return NULL;
510 break;
511 case RTARROW:
512 case ENTERA:
513 case ENTERB:
514 if (ci->action == OPT_INACTIVE) break;
515 if (ci->action == OPT_CHECKBOX) break;
516 if (ci->action == OPT_SEP) break;
517 if (ci->action == OPT_EXITMENU) return NULL; // As if we hit Esc
518 // If we are going into a radio menu, dont call handler, return ci
519 if (ci->action == OPT_RADIOMENU) return ci;
520 if (ci->handler != NULL) // Do we have a handler
522 hr = ci->handler(ms,ci);
523 if (hr.refresh) // Do we need to refresh
525 // Cleanup menu using old number of items
526 cleanupmenu(menu,top,left,numitems);
527 // Recalculate the number of items
528 numitems = calc_visible(menu,0);
529 // Reprint the menu
530 printmenu(menu,curr,top,left,first);
532 if (hr.valid) return ci;
534 else return ci;
535 break;
536 case SPACEKEY:
537 if (ci->action != OPT_CHECKBOX) break;
538 ci->itemdata.checked = !ci->itemdata.checked;
539 if (ci->handler != NULL) // Do we have a handler
541 hr = ci->handler(ms,ci);
542 if (hr.refresh) // Do we need to refresh
544 // Cleanup menu using old number of items
545 cleanupmenu(menu,top,left,numitems);
546 // Recalculate the number of items
547 numitems = calc_visible(menu,0);
548 // Reprint the menu
549 printmenu(menu,curr,top,left,first);
552 break;
553 default:
554 // Check if this is a shortcut key
555 if (((asc >= 'A') && (asc <= 'Z')) ||
556 ((asc >= 'a') && (asc <= 'z')) ||
557 ((asc >= '0') && (asc <= '9')))
559 tmp = find_shortcut(menu,asc,curr);
560 if ((tmp > curr) && (! isvisible(menu,first,tmp)))
561 first = calc_first_late(menu,tmp);
562 if (tmp < curr)
563 first = calc_first_early(menu,tmp);
564 curr = tmp;
566 else {
567 if (ms->keys_handler) // Call extra keys handler
568 ms->keys_handler(ms,menu->items[curr],(scan << 8) | asc);
570 break;
572 // Update status line
573 gotoxy(ms->minrow+ms->statline,ms->mincol,ms->menupage);
574 cprint(ms->spacechar,ms->statusattr[NOHLITE],ms->numcols,ms->menupage);
575 printmenuitem(menu->items[curr]->status,ms->statusattr);
577 return NULL; // Should never come here
580 /* Handle the entire system of menu's. */
581 pt_menuitem runmenusystem(uchar top, uchar left, pt_menu cmenu, uchar startopt, uchar menutype)
583 * cmenu
584 * Which menu should be currently displayed
585 * top,left
586 * What is the position of the top,left corner of the menu
587 * startopt
588 * which menu item do I start with
589 * menutype
590 * NORMALMENU or RADIOMENU
592 * Return Value:
593 * Returns a pointer to the final item chosen, or NULL if nothing chosen.
596 pt_menuitem opt,choice;
597 uchar startat,mt;
598 uchar row,col;
600 if (cmenu == NULL) return NULL;
601 startover:
602 // Set the menu height
603 cmenu->menuheight = ms->maxrow - top-3;
604 if (cmenu->menuheight > ms->maxmenuheight)
605 cmenu->menuheight = ms->maxmenuheight;
606 if (menutype == NORMALMENU)
607 opt = getmenuoption(cmenu,top,left,startopt);
608 else // menutype == RADIOMENU
609 opt = getradiooption(cmenu,top,left,startopt);
611 if (opt == NULL)
613 // User hit Esc
614 cleanupmenu(cmenu,top,left,calc_visible(cmenu,0));
615 return NULL;
617 // Are we done with the menu system?
618 if ((opt->action != OPT_SUBMENU) && (opt->action != OPT_RADIOMENU))
620 cleanupmenu(cmenu,top,left,calc_visible(cmenu,0));
621 return opt; // parent cleanup other menus
623 // Either radiomenu or submenu
624 // Do we have a valid menu number? The next hack uses the fact that
625 // itemdata.submenunum = itemdata.radiomenunum (since enum data type)
626 if (opt->itemdata.submenunum >= ms->nummenus) // This is Bad....
628 gotoxy(12,12,ms->menupage); // Middle of screen
629 csprint("ERROR: Invalid submenu requested.",0x07);
630 cleanupmenu(cmenu,top,left,calc_visible(cmenu,0));
631 return NULL; // Pretend user hit esc
633 // Call recursively for submenu
634 // Position the submenu below the current item,
635 // covering half the current window (horizontally)
636 row = ms->menus[(unsigned int)opt->itemdata.submenunum]->row;
637 col = ms->menus[(unsigned int)opt->itemdata.submenunum]->col;
638 if (row == 0xFF) row = top+opt->index+2;
639 if (col == 0xFF) col = left+3+(cmenu->menuwidth >> 1);
640 mt = (opt->action == OPT_SUBMENU ? NORMALMENU : RADIOMENU );
641 startat = 0;
642 if ((opt->action == OPT_RADIOMENU) && (opt->data != NULL))
643 startat = ((t_menuitem *)opt->data)->index;
645 choice = runmenusystem(row, col,
646 ms->menus[(unsigned int)opt->itemdata.submenunum],
647 startat, mt );
648 if (opt->action == OPT_RADIOMENU)
650 if (choice != NULL) opt->data = (void *)choice; // store choice in data field
651 if (opt->handler != NULL) opt->handler(ms,opt);
652 choice = NULL; // Pretend user hit esc
654 if (choice==NULL) // User hit Esc in submenu
656 // Startover
657 startopt = opt->index;
658 goto startover;
660 else
662 cleanupmenu(cmenu,top,left,calc_visible(cmenu,0));
663 return choice;
667 // Finds the indexof the menu with given name
668 uchar find_menu_num(const char *name)
670 int i;
671 pt_menu m;
673 if (name == NULL) return (uchar)(-1);
674 for (i=0; i < ms->nummenus; i++)
676 m = ms->menus[i];
677 if ((m->name) && (strcmp(m->name,name)==0)) return i;
679 return (uchar)(-1);
682 // Run through all items and if they are submenus
683 // with a non-trivial "action" and trivial submenunum
684 // replace submenunum with the menu with name "action"
685 void fix_submenus()
687 int i,j;
688 pt_menu m;
689 pt_menuitem mi;
691 i = 0;
692 for (i=0; i < ms->nummenus; i++)
694 m = ms->menus[i];
695 for (j=0; j < m->numitems; j++)
697 mi = m->items[j];
698 // if submenu with non-trivial data string
699 // again using hack that itemdata is a union data type
700 if ( mi->data && ((mi->action == OPT_SUBMENU) || (mi->action == OPT_RADIOMENU)) )
701 mi->itemdata.submenunum = find_menu_num (mi->data);
706 /* User Callable functions */
708 pt_menuitem showmenus(uchar startmenu)
710 pt_menuitem rv;
711 uchar oldpage,tpos;
713 fix_submenus(); // Fix submenu numbers incase nick names were used
715 // Setup screen for menusystem
716 oldpage = getdisppage();
717 setdisppage(ms->menupage);
718 cls();
719 clearwindow(ms->minrow, ms->mincol, ms->maxrow, ms->maxcol,
720 ms->menupage, ms->fillchar, ms->fillattr);
721 tpos = (ms->numcols - strlen(ms->title) - 1) >> 1; // center it on line
722 gotoxy(ms->minrow,ms->mincol,ms->menupage);
723 cprint(ms->tfillchar,ms->titleattr,ms->numcols,ms->menupage);
724 gotoxy(ms->minrow,ms->mincol+tpos,ms->menupage);
725 csprint(ms->title,ms->titleattr);
727 cursoroff(); // Doesn't seem to work?
730 // Go, main menu cannot be a radio menu
731 rv = runmenusystem(ms->minrow+MENUROW, ms->mincol+MENUCOL,
732 ms->menus[(unsigned int)startmenu], 0, NORMALMENU);
734 // Hide the garbage we left on the screen
735 cursoron();
736 if (oldpage == ms->menupage) cls(); else setdisppage(oldpage);
738 // Return user choice
739 return rv;
742 pt_menusystem init_menusystem(const char *title)
744 int i;
746 ms = NULL;
747 ms = (pt_menusystem) malloc(sizeof(t_menusystem));
748 if (ms == NULL) return NULL;
749 ms->nummenus = 0;
750 // Initialise all menu pointers
751 for (i=0; i < MAXMENUS; i++) ms->menus[i] = NULL;
753 ms->title = (char *)malloc(TITLELEN+1);
754 if (title == NULL)
755 strcpy(ms->title,TITLESTR); // Copy string
756 else strcpy(ms->title,title);
758 // Timeout settings
759 ms->tm_stepsize = TIMEOUTSTEPSIZE;
760 ms->tm_numsteps = TIMEOUTNUMSTEPS;
762 ms->normalattr[NOHLITE] = NORMALATTR;
763 ms->normalattr[HLITE] = NORMALHLITE;
765 ms->reverseattr[NOHLITE] = REVERSEATTR;
766 ms->reverseattr[HLITE] = REVERSEHLITE;
768 ms->inactattr[NOHLITE] = INACTATTR;
769 ms->inactattr[HLITE] = INACTHLITE;
771 ms->revinactattr[NOHLITE] = REVINACTATTR;
772 ms->revinactattr[HLITE] = REVINACTHLITE;
774 ms->statusattr[NOHLITE] = STATUSATTR;
775 ms->statusattr[HLITE] = STATUSHLITE;
777 ms->statline = STATLINE;
778 ms->tfillchar= TFILLCHAR;
779 ms->titleattr= TITLEATTR;
781 ms->fillchar = FILLCHAR;
782 ms->fillattr = FILLATTR;
783 ms->spacechar= SPACECHAR;
784 ms->shadowattr = SHADOWATTR;
786 ms->menupage = MENUPAGE; // Usually no need to change this at all
788 // Initialise all handlers
789 ms->handler = NULL;
790 ms->keys_handler = NULL;
791 ms->ontimeout=NULL; // No timeout handler
792 ms->tm_total_timeout = 0;
793 ms->tm_sofar_timeout = 0;
794 ms->ontotaltimeout = NULL;
796 // Setup ACTION_{,IN}VALID
797 ACTION_VALID.valid=1;
798 ACTION_VALID.refresh=0;
799 ACTION_INVALID.valid = 0;
800 ACTION_INVALID.refresh = 0;
802 // Figure out the size of the screen we are in now.
803 // By default we use the whole screen for our menu
804 ms->minrow = ms->mincol = 0;
805 ms->numcols = getnumcols();
806 ms->numrows = getnumrows();
807 ms->maxcol = ms->numcols - 1;
808 ms->maxrow = ms->numrows - 1;
810 // How many entries per menu can we display at a time
811 ms->maxmenuheight = ms->maxrow - ms->minrow - 3;
812 if (ms->maxmenuheight > MAXMENUHEIGHT)
813 ms->maxmenuheight= MAXMENUHEIGHT;
815 // Set up the look of the box
816 set_box_type(MENUBOXTYPE);
817 return ms;
820 void set_normal_attr(uchar normal, uchar selected, uchar inactivenormal, uchar inactiveselected)
822 if (normal != 0xFF) ms->normalattr[0] = normal;
823 if (selected != 0xFF) ms->reverseattr[0] = selected;
824 if (inactivenormal != 0xFF) ms->inactattr[0] = inactivenormal;
825 if (inactiveselected != 0xFF) ms->revinactattr[0] = inactiveselected;
828 void set_normal_hlite(uchar normal, uchar selected, uchar inactivenormal, uchar inactiveselected)
830 if (normal != 0xFF) ms->normalattr[1] = normal;
831 if (selected != 0xFF) ms->reverseattr[1] = selected;
832 if (inactivenormal != 0xFF) ms->inactattr[1] = inactivenormal;
833 if (inactiveselected != 0xFF) ms->revinactattr[1] = inactiveselected;
836 void set_status_info(uchar statusattr, uchar statushlite, uchar statline)
838 if (statusattr != 0xFF) ms->statusattr[NOHLITE] = statusattr;
839 if (statushlite!= 0xFF) ms->statusattr[HLITE] = statushlite;
840 // statline is relative to minrow
841 if (statline >= ms->numrows) statline = ms->numrows - 1;
842 ms->statline = statline; // relative to ms->minrow, 0 based
845 void set_title_info(uchar tfillchar, uchar titleattr)
847 if (tfillchar != 0xFF) ms->tfillchar = tfillchar;
848 if (titleattr != 0xFF) ms->titleattr = titleattr;
851 void set_misc_info(uchar fillchar, uchar fillattr,uchar spacechar, uchar shadowattr)
853 if (fillchar != 0xFF) ms->fillchar = fillchar;
854 if (fillattr != 0xFF) ms->fillattr = fillattr;
855 if (spacechar != 0xFF) ms->spacechar = spacechar;
856 if (shadowattr!= 0xFF) ms->shadowattr= shadowattr;
859 void set_box_type(boxtype bt)
861 uchar *bxc;
862 ms->menubt = bt;
863 bxc = getboxchars(bt);
864 ms->box_horiz = bxc[BOX_HORIZ]; // The char used to draw top line
865 ms->box_ltrt = bxc[BOX_LTRT];
866 ms->box_rtlt = bxc[BOX_RTLT];
869 void set_menu_options(uchar maxmenuheight)
871 if (maxmenuheight != 0xFF) ms->maxmenuheight = maxmenuheight;
874 // Set the window which menusystem should use
875 void set_window_size(uchar top, uchar left, uchar bot, uchar right)
878 uchar nr,nc;
879 if ((top > bot) || (left > right)) return; // Sorry no change will happen here
880 nr = getnumrows();
881 nc = getnumcols();
882 if (bot >= nr) bot = nr-1;
883 if (right >= nc) right = nc-1;
884 ms->minrow = top;
885 ms->mincol = left;
886 ms->maxrow = bot;
887 ms->maxcol = right;
888 ms->numcols = right - left + 1;
889 ms->numrows = bot - top + 1;
890 if (ms->statline >= ms->numrows) ms->statline = ms->numrows - 1; // Clip statline if need be
893 void reg_handler( t_handler htype, void * handler)
895 // If bad value set to default screen handler
896 switch(htype) {
897 case HDLR_KEYS:
898 ms->keys_handler = (t_keys_handler) handler;
899 break;
900 default:
901 ms->handler = (t_menusystem_handler) handler;
902 break;
906 void unreg_handler(t_handler htype)
908 switch(htype) {
909 case HDLR_KEYS:
910 ms->keys_handler = NULL;
911 break;
912 default:
913 ms->handler = NULL;
914 break;
918 void reg_ontimeout(t_timeout_handler handler, unsigned int numsteps, unsigned int stepsize)
920 ms->ontimeout = handler;
921 if (numsteps != 0) ms->tm_numsteps = numsteps;
922 if (stepsize != 0) ms->tm_stepsize = stepsize;
925 void unreg_ontimeout()
927 ms->ontimeout = NULL;
930 void reg_ontotaltimeout (t_timeout_handler handler, unsigned long numcentiseconds)
932 if (numcentiseconds != 0) {
933 ms->ontotaltimeout = handler;
934 ms->tm_total_timeout = numcentiseconds*10; // to convert to milliseconds
935 ms->tm_sofar_timeout = 0;
939 void unreg_ontotaltimeout()
941 ms->ontotaltimeout = NULL;
945 int next_visible(pt_menu menu, int index)
947 int ans;
948 if (index < 0) ans = 0 ;
949 else if (index >= menu->numitems) ans = menu->numitems-1;
950 else ans = index;
951 while ((ans < menu->numitems-1) &&
952 ((menu->items[ans]->action == OPT_INVISIBLE) ||
953 (menu->items[ans]->action == OPT_SEP)))
954 ans++;
955 return ans;
958 int prev_visible(pt_menu menu, int index) // Return index of prev visible
960 int ans;
961 if (index < 0) ans = 0;
962 else if (index >= menu->numitems) ans = menu->numitems-1;
963 else ans = index;
964 while ((ans > 0) &&
965 ((menu->items[ans]->action == OPT_INVISIBLE) ||
966 (menu->items[ans]->action == OPT_SEP)))
967 ans--;
968 return ans;
971 int next_visible_sep(pt_menu menu, int index)
973 int ans;
974 if (index < 0) ans = 0 ;
975 else if (index >= menu->numitems) ans = menu->numitems-1;
976 else ans = index;
977 while ((ans < menu->numitems-1) &&
978 (menu->items[ans]->action == OPT_INVISIBLE))
979 ans++;
980 return ans;
983 int prev_visible_sep(pt_menu menu, int index) // Return index of prev visible
985 int ans;
986 if (index < 0) ans = 0;
987 else if (index >= menu->numitems) ans = menu->numitems-1;
988 else ans = index;
989 while ((ans > 0) &&
990 (menu->items[ans]->action == OPT_INVISIBLE))
991 ans--;
992 return ans;
995 int calc_visible(pt_menu menu,int first)
997 int ans,i;
999 if (menu == NULL) return 0;
1000 ans = 0;
1001 for (i=first; i < menu->numitems; i++)
1002 if (menu->items[i]->action != OPT_INVISIBLE) ans++;
1003 return ans;
1006 // is curr visible if first entry is first?
1007 int isvisible(pt_menu menu,int first, int curr)
1009 if (curr < first) return 0;
1010 return (calc_visible(menu,first)-calc_visible(menu,curr) < menu->menuheight);
1013 // Calculate the first entry to be displayed
1014 // so that curr is visible and make curr as late as possible
1015 int calc_first_late(pt_menu menu,int curr)
1017 int ans,i,nv;
1019 nv = calc_visible(menu,0);
1020 if (nv <= menu->menuheight) return 0;
1021 // Start with curr and go back menu->menuheight times
1022 ans = curr+1;
1023 for (i=0; i < menu->menuheight; i++)
1024 ans = prev_visible_sep(menu,ans-1);
1025 return ans;
1028 // Calculate the first entry to be displayed
1029 // so that curr is visible and make curr as early as possible
1030 int calc_first_early(pt_menu menu,int curr)
1032 int ans,i,nv;
1034 nv = calc_visible(menu,0);
1035 if (nv <= menu->menuheight) return 0;
1036 // Start with curr and go back till >= menu->menuheight
1037 // items are visible
1038 nv = calc_visible(menu,curr); // Already nv of them are visible
1039 ans = curr;
1040 for (i=0; i < menu->menuheight - nv; i++)
1041 ans = prev_visible_sep(menu,ans-1);
1042 return ans;
1045 // Create a new menu and return its position
1046 uchar add_menu(const char *title, int maxmenusize)
1048 int num,i;
1049 pt_menu m;
1051 num = ms->nummenus;
1052 if (num >= MAXMENUS) return -1;
1053 m = NULL;
1054 m = (pt_menu) malloc(sizeof(t_menu));
1055 if (m == NULL) return -1;
1056 ms->menus[num] = m;
1057 m->numitems = 0;
1058 m->name = NULL;
1059 m->row = 0xFF;
1060 m->col = 0xFF;
1061 if (maxmenusize < 1)
1062 m->maxmenusize = MAXMENUSIZE;
1063 else m->maxmenusize = maxmenusize;
1064 m->items = (pt_menuitem *) malloc(sizeof(pt_menuitem)*(m->maxmenusize));
1065 for (i=0; i < m->maxmenusize; i++) m->items[i] = NULL;
1067 m->title = (char *)malloc(MENULEN+1);
1068 if (title)
1070 if (strlen(title) > MENULEN - 2)
1071 strcpy(m->title,TITLELONG);
1072 else strcpy(m->title,title);
1074 else strcpy(m->title,EMPTYSTR);
1075 m ->menuwidth = strlen(m->title);
1076 ms->nummenus ++;
1077 return ms->nummenus - 1;
1080 void set_menu_name(const char *name) // Set the "name" of this menu
1082 pt_menu m;
1084 m = ms->menus[ms->nummenus-1];
1085 if (m->name) // Free up previous name
1087 free(m->name);
1088 m -> name = NULL;
1091 if (name)
1093 m->name = (char *)malloc(strlen(name)+1);
1094 strcpy(m->name,name);
1098 // Create a new named menu and return its position
1099 uchar add_named_menu(const char * name, const char *title, int maxmenusize)
1101 add_menu(title,maxmenusize);
1102 set_menu_name(name);
1103 return ms->nummenus - 1;
1106 void set_menu_pos(uchar row,uchar col) // Set the position of this menu.
1108 pt_menu m;
1110 m = ms->menus[ms->nummenus-1];
1111 m->row = row;
1112 m->col = col;
1115 pt_menuitem add_sep() // Add a separator to current menu
1117 pt_menuitem mi;
1118 pt_menu m;
1120 m = (ms->menus[ms->nummenus-1]);
1121 mi = NULL;
1122 mi = (pt_menuitem) malloc(sizeof(t_menuitem));
1123 if (mi == NULL) return NULL;
1124 m->items[(unsigned int)m->numitems] = mi;
1125 mi->handler = NULL; // No handler
1126 mi->item = mi->status = mi->data = NULL;
1127 mi->action = OPT_SEP;
1128 mi->index = m->numitems++;
1129 mi->parindex = ms->nummenus-1;
1130 mi->shortcut = 0;
1131 mi->helpid=0;
1132 return mi;
1135 // Add item to the "current" menu
1136 pt_menuitem add_item(const char *item, const char *status, t_action action,
1137 const char *data, uchar itemdata)
1139 pt_menuitem mi;
1140 pt_menu m;
1141 const char *str;
1142 uchar inhlite=0; // Are we inside hlite area
1144 m = (ms->menus[ms->nummenus-1]);
1145 mi = NULL;
1146 mi = (pt_menuitem) malloc(sizeof(t_menuitem));
1147 if (mi == NULL) return NULL;
1148 m->items[(unsigned int) m->numitems] = mi;
1149 mi->handler = NULL; // No handler
1151 // Allocate space to store stuff
1152 mi->item = (char *)malloc(MENULEN+1);
1153 mi->status = (char *)malloc(STATLEN+1);
1154 mi->data = (char *)malloc(ACTIONLEN+1);
1156 if (item) {
1157 if (strlen(item) > MENULEN) {
1158 strcpy(mi->item,ITEMLONG);
1159 } else {
1160 strcpy(mi->item,item);
1162 if (strlen(mi->item) > m->menuwidth) m->menuwidth = strlen(mi->item);
1163 } else strcpy(mi->item,EMPTYSTR);
1165 if (status) {
1166 if (strlen(status) > STATLEN) {
1167 strcpy(mi->status,STATUSLONG);
1168 } else {
1169 strcpy(mi->status,status);
1171 } else strcpy(mi->status,EMPTYSTR);
1173 mi->action=action;
1174 str = mi->item;
1175 mi->shortcut = 0;
1176 mi->helpid = 0xFFFF;
1177 inhlite = 0; // We have not yet seen an ENABLEHLITE char
1178 // Find the first char in [A-Za-z0-9] after ENABLEHLITE and not arg to control char
1179 while (*str)
1181 if (*str == ENABLEHLITE)
1183 inhlite=1;
1185 if (*str == DISABLEHLITE)
1187 inhlite = 0;
1189 if ( (inhlite == 1) &&
1190 (((*str >= 'A') && (*str <= 'Z')) ||
1191 ((*str >= 'a') && (*str <= 'z')) ||
1192 ((*str >= '0') && (*str <= '9'))))
1194 mi->shortcut=*str;
1195 break;
1197 ++str;
1199 if ((mi->shortcut >= 'A') && (mi->shortcut <= 'Z')) // Make lower case
1200 mi->shortcut = mi->shortcut -'A'+'a';
1202 if (data) {
1203 if (strlen(data) > ACTIONLEN) {
1204 strcpy(mi->data,ACTIONLONG);
1205 } else {
1206 strcpy(mi->data,data);
1208 } else strcpy(mi->data,EMPTYSTR);
1210 switch (action)
1212 case OPT_SUBMENU:
1213 mi->itemdata.submenunum = itemdata;
1214 break;
1215 case OPT_CHECKBOX:
1216 mi->itemdata.checked = itemdata;
1217 break;
1218 case OPT_RADIOMENU:
1219 mi->itemdata.radiomenunum = itemdata;
1220 if (mi->data) free(mi->data);
1221 mi->data = NULL; // No selection made
1222 break;
1223 default: // to keep the compiler happy
1224 break;
1226 mi->index = m->numitems++;
1227 mi->parindex = ms->nummenus-1;
1228 return mi;
1231 // Set the shortcut key for the current item
1232 void set_item_options(uchar shortcut,int helpid)
1234 pt_menuitem mi;
1235 pt_menu m;
1237 m = (ms->menus[ms->nummenus-1]);
1238 if (m->numitems <= 0) return;
1239 mi = m->items[(unsigned int) m->numitems-1];
1241 if (shortcut != 0xFF) mi->shortcut = shortcut;
1242 if (helpid != 0xFFFF) mi->helpid = helpid;
1245 // Free internal datasutructures
1246 void close_menusystem(void)
1250 // append_line_helper(pt_menu menu,char *line)
1251 void append_line_helper(int menunum, char *line)
1253 pt_menu menu;
1254 pt_menuitem mi,ri;
1255 char *app;
1256 int ctr;
1257 char dp;
1260 dp = getdisppage();
1261 menu = ms->menus[menunum];
1262 for (ctr = 0; ctr < (int) menu->numitems; ctr++)
1264 mi = menu->items[ctr];
1265 app = NULL; //What to append
1266 switch (mi->action) {
1267 case OPT_CHECKBOX:
1268 if (mi->itemdata.checked) app = mi->data;
1269 break;
1270 case OPT_RADIOMENU:
1271 if (mi->data) { // Some selection has been made
1272 ri = (pt_menuitem) (mi->data);
1273 app = ri->data;
1275 break;
1276 case OPT_SUBMENU:
1277 append_line_helper(mi->itemdata.submenunum,line);
1278 break;
1279 default:
1280 break;
1282 if (app) {
1283 strcat(line," ");
1284 strcat(line,app);
1290 // Generate string based on state of checkboxes and radioitem in given menu
1291 // Assume line points to large enough buffer
1292 void gen_append_line(const char *menu_name,char *line)
1294 int menunum;
1296 menunum = find_menu_num(menu_name);
1297 if (menunum < 0) return; // No such menu
1298 append_line_helper(menunum,line);