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. 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 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 */ 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 */ 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 **/ 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 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 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 */ 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. */ 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 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" 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 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 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 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 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 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 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 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 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 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 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 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 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 877 void unreg_ontimeout(void) 878 { 879 ms->ontimeout = NULL; 880 } 881 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 892 void unreg_ontotaltimeout(void) 893 { 894 ms->ontotaltimeout = NULL; 895 } 896 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 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 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 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 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? 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 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 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 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 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 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 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 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 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 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 1223 void close_menusystem(void) 1224 { 1225 } 1226 1227 // append_line_helper(pt_menu menu,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 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