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