| /* -*- c -*- ------------------------------------------------------------- * |
| * |
| * Copyright 2004-2005 Murali Krishnan Ganapathy - All Rights Reserved |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation, Inc., 53 Temple Place Ste 330, |
| * Boston MA 02111-1307, USA; either version 2 of the License, or |
| * (at your option) any later version; incorporated herein by reference. |
| * |
| * ----------------------------------------------------------------------- */ |
| |
| #include "cmenu.h" |
| #include "com32io.h" |
| #include <stdlib.h> |
| #include <console.h> |
| |
| // Local Variables |
| static pt_menusystem ms; // Pointer to the menusystem |
| char TITLESTR[] = |
| "COMBOOT Menu System for SYSLINUX developed by Murali Krishnan Ganapathy"; |
| char TITLELONG[] = " TITLE too long "; |
| char ITEMLONG[] = " ITEM too long "; |
| char ACTIONLONG[] = " ACTION too long "; |
| char STATUSLONG[] = " STATUS too long "; |
| char EMPTYSTR[] = ""; |
| |
| /* Forward declarations */ |
| int calc_visible(pt_menu menu, int first); |
| int next_visible(pt_menu menu, int index); |
| int prev_visible(pt_menu menu, int index); |
| int next_visible_sep(pt_menu menu, int index); |
| int prev_visible_sep(pt_menu menu, int index); |
| int calc_first_early(pt_menu menu, int curr); |
| int calc_first_late(pt_menu menu, int curr); |
| int isvisible(pt_menu menu, int first, int curr); |
| |
| /* Basic Menu routines */ |
| |
| // This is same as inputc except it honors the ontimeout handler |
| // and calls it when needed. For the callee, there is no difference |
| // as this will not return unless a key has been pressed. |
| static int getch(void) |
| { |
| t_timeout_handler th; |
| int key; |
| unsigned long i; |
| |
| // Wait until keypress if no handler specified |
| if ((ms->ontimeout == NULL) && (ms->ontotaltimeout == NULL)) |
| return get_key(stdin, 0); |
| |
| th = ms->ontimeout; |
| for (;;) { |
| for (i = 0; i < ms->tm_numsteps; i++) { |
| key = get_key(stdin, ms->tm_stepsize); |
| if (key != KEY_NONE) |
| return key; |
| |
| if ((ms->tm_total_timeout == 0) || (ms->ontotaltimeout == NULL)) |
| continue; // Dont bother with calculations if no handler |
| ms->tm_sofar_timeout += ms->tm_stepsize; |
| if (ms->tm_sofar_timeout >= ms->tm_total_timeout) { |
| th = ms->ontotaltimeout; |
| ms->tm_sofar_timeout = 0; |
| break; // Get out of the for loop |
| } |
| } |
| if (!th) |
| continue; // no handler |
| key = th(); |
| switch (key) { |
| case CODE_ENTER: // Pretend user hit enter |
| return KEY_ENTER; |
| case CODE_ESCAPE: // Pretend user hit escape |
| return KEY_ESC; |
| default: |
| break; |
| } |
| } |
| return KEY_NONE; |
| } |
| |
| int find_shortcut(pt_menu menu, uchar shortcut, int index) |
| // Find the next index with specified shortcut key |
| { |
| int ans; |
| pt_menuitem mi; |
| |
| // Garbage in garbage out |
| if ((index < 0) || (index >= menu->numitems)) |
| return index; |
| ans = index + 1; |
| // Go till end of menu |
| while (ans < menu->numitems) { |
| mi = menu->items[ans]; |
| if ((mi->action == OPT_INVISIBLE) || (mi->action == OPT_SEP) |
| || (mi->shortcut != shortcut)) |
| ans++; |
| else |
| return ans; |
| } |
| // Start at the beginning and try again |
| ans = 0; |
| while (ans < index) { |
| mi = menu->items[ans]; |
| if ((mi->action == OPT_INVISIBLE) || (mi->action == OPT_SEP) |
| || (mi->shortcut != shortcut)) |
| ans++; |
| else |
| return ans; |
| } |
| return index; // Sorry not found |
| } |
| |
| /* Redraw background and title */ |
| static void reset_ui(void) |
| { |
| uchar tpos; |
| |
| cls(); |
| clearwindow(ms->minrow, ms->mincol, ms->maxrow, ms->maxcol, |
| ms->fillchar, ms->fillattr); |
| |
| tpos = (ms->numcols - strlen(ms->title) - 1) >> 1; // center it on line |
| gotoxy(ms->minrow, ms->mincol); |
| cprint(ms->tfillchar, ms->titleattr, ms->numcols); |
| gotoxy(ms->minrow, ms->mincol + tpos); |
| csprint(ms->title, ms->titleattr); |
| |
| cursoroff(); |
| } |
| |
| /* |
| * Print a menu item |
| * |
| * attr[0] is non-hilite attr, attr[1] is highlight attr |
| */ |
| void printmenuitem(const char *str, uchar * attr) |
| { |
| int hlite = NOHLITE; // Initially no highlighting |
| |
| while (*str) { |
| switch (*str) { |
| case BELL: // No Bell Char |
| break; |
| case ENABLEHLITE: // Switch on highlighting |
| hlite = HLITE; |
| break; |
| case DISABLEHLITE: // Turn off highlighting |
| hlite = NOHLITE; |
| break; |
| default: |
| putch(*str, attr[hlite]); |
| } |
| str++; |
| } |
| } |
| |
| |
| /** |
| * print_line - Print a whole line in a menu |
| * @menu: current menu to handle |
| * @curr: index of the current entry highlighted |
| * @top: top coordinate of the @menu |
| * @left: left coordinate of the @menu |
| * @x: index in the menu of curr |
| * @row: row currently displayed |
| * @radio: radio item? |
| **/ |
| static void print_line(pt_menu menu, int curr, uchar top, uchar left, |
| int x, int row, bool radio) |
| { |
| pt_menuitem ci; |
| char fchar[6], lchar[6]; // The first and last char in for each entry |
| const char *str; // Item string (cf printmenuitem) |
| char sep[MENULEN]; // Separator (OPT_SEP) |
| uchar *attr; // Attribute |
| int menuwidth = menu->menuwidth + 3; |
| |
| if (row >= menu->menuheight) |
| return; |
| |
| ci = menu->items[x]; |
| |
| memset(sep, ms->box_horiz, menuwidth); |
| sep[menuwidth - 1] = 0; |
| |
| // Setup the defaults now |
| if (radio) { |
| fchar[0] = '\b'; |
| fchar[1] = SO; |
| fchar[2] = (x == curr ? RADIOSEL : RADIOUNSEL); |
| fchar[3] = SI; |
| fchar[4] = '\0'; // Unselected ( ) |
| lchar[0] = '\0'; // Nothing special after |
| attr = ms->normalattr; // Always same attribute |
| } else { |
| lchar[0] = fchar[0] = ' '; |
| lchar[1] = fchar[1] = '\0'; // fchar and lchar are just spaces |
| attr = (x == curr ? ms->reverseattr : ms->normalattr); // Normal attributes |
| } |
| str = ci->item; // Pointer to item string |
| switch (ci->action) // set up attr,str,fchar,lchar for everything |
| { |
| case OPT_INACTIVE: |
| if (radio) |
| attr = ms->inactattr; |
| else |
| attr = (x == curr ? ms->revinactattr : ms->inactattr); |
| break; |
| case OPT_SUBMENU: |
| if (radio) |
| break; // Not supported for radio menu |
| lchar[0] = '>'; |
| lchar[1] = 0; |
| break; |
| case OPT_RADIOMENU: |
| if (radio) |
| break; // Not supported for radio menu |
| lchar[0] = RADIOMENUCHAR; |
| lchar[1] = 0; |
| break; |
| case OPT_CHECKBOX: |
| if (radio) |
| break; // Not supported for radio menu |
| lchar[0] = '\b'; |
| lchar[1] = SO; |
| lchar[2] = (ci->itemdata.checked ? CHECKED : UNCHECKED); |
| lchar[3] = SI; |
| lchar[4] = 0; |
| break; |
| case OPT_SEP: |
| fchar[0] = '\b'; |
| fchar[1] = SO; |
| fchar[2] = LEFT_MIDDLE_BORDER; |
| fchar[3] = MIDDLE_BORDER; |
| fchar[4] = MIDDLE_BORDER; |
| fchar[5] = 0; |
| memset(sep, MIDDLE_BORDER, menuwidth); |
| sep[menuwidth - 1] = 0; |
| str = sep; |
| lchar[0] = MIDDLE_BORDER; |
| lchar[1] = RIGHT_MIDDLE_BORDER; |
| lchar[2] = SI; |
| lchar[3] = 0; |
| break; |
| case OPT_EXITMENU: |
| if (radio) |
| break; // Not supported for radio menu |
| fchar[0] = '<'; |
| fchar[1] = 0; |
| break; |
| default: // Just to keep the compiler happy |
| break; |
| } |
| |
| // Wipe area with spaces |
| gotoxy(top + row, left - 2); |
| cprint(ms->spacechar, attr[NOHLITE], menuwidth + 2); |
| |
| // Print first part |
| gotoxy(top + row, left - 2); |
| csprint(fchar, attr[NOHLITE]); |
| |
| // Print main part |
| gotoxy(top + row, left); |
| printmenuitem(str, attr); |
| |
| // Print last part |
| gotoxy(top + row, left + menuwidth - 1); |
| csprint(lchar, attr[NOHLITE]); |
| } |
| |
| // print the menu starting from FIRST |
| // will print a maximum of menu->menuheight items |
| static void printmenu(pt_menu menu, int curr, uchar top, uchar left, uchar first, bool radio) |
| { |
| int x, row; // x = index, row = position from top |
| int numitems, menuwidth; |
| pt_menuitem ci; |
| |
| numitems = calc_visible(menu, first); |
| if (numitems > menu->menuheight) |
| numitems = menu->menuheight; |
| |
| menuwidth = menu->menuwidth + 3; |
| clearwindow(top, left - 2, top + numitems + 1, left + menuwidth + 1, |
| ms->fillchar, ms->shadowattr); |
| drawbox(top - 1, left - 3, top + numitems, left + menuwidth, |
| ms->normalattr[NOHLITE]); |
| |
| // Menu title |
| x = (menuwidth - strlen(menu->title) - 1) >> 1; |
| gotoxy(top - 1, left + x); |
| printmenuitem(menu->title, ms->normalattr); |
| |
| // All lines in the menu |
| row = -1; // 1 less than inital value of x |
| for (x = first; x < menu->numitems; x++) { |
| ci = menu->items[x]; |
| if (ci->action == OPT_INVISIBLE) |
| continue; |
| row++; |
| if (row >= numitems) |
| break; // Already have enough number of items |
| print_line(menu, curr, top, left, x, row, radio); |
| } |
| // Check if we need to MOREABOVE and MOREBELOW to be added |
| // reuse x |
| row = 0; |
| x = next_visible_sep(menu, 0); // First item |
| if (!isvisible(menu, first, x)) // There is more above |
| { |
| row = 1; |
| gotoxy(top, left + menuwidth); |
| cprint(MOREABOVE, ms->normalattr[NOHLITE], 1); |
| } |
| x = prev_visible_sep(menu, menu->numitems); // last item |
| if (!isvisible(menu, first, x)) // There is more above |
| { |
| row = 1; |
| gotoxy(top + numitems - 1, left + menuwidth); |
| cprint(MOREBELOW, ms->normalattr[NOHLITE], 1); |
| } |
| // Add a scroll box |
| x = ((numitems - 1) * curr) / (menu->numitems); |
| if ((x > 0) && (row == 1)) { |
| gotoxy(top + x, left + menuwidth); |
| csprint("\016\141\017", ms->normalattr[NOHLITE]); |
| } |
| if (ms->handler) |
| ms->handler(ms, menu->items[curr]); |
| } |
| |
| void cleanupmenu(pt_menu menu, uchar top, uchar left, int numitems) |
| { |
| if (numitems > menu->menuheight) |
| numitems = menu->menuheight; |
| clearwindow(top, left - 2, top + numitems + 1, left + menu->menuwidth + 4, ms->fillchar, ms->fillattr); // Clear the shadow |
| clearwindow(top - 1, left - 3, top + numitems, left + menu->menuwidth + 3, ms->fillchar, ms->fillattr); // main window |
| } |
| |
| |
| /* Handle one menu */ |
| static pt_menuitem getmenuoption(pt_menu menu, uchar top, uchar left, uchar startopt, bool radio) |
| // Return item chosen or NULL if ESC was hit. |
| { |
| int prev, prev_first, curr, i, first, tmp; |
| int asc = 0; |
| bool redraw = true; // Need to draw the menu the first time |
| uchar numitems; |
| pt_menuitem ci; // Current item |
| t_handler_return hr; // Return value of handler |
| |
| numitems = calc_visible(menu, 0); |
| // Setup status line |
| gotoxy(ms->minrow + ms->statline, ms->mincol); |
| cprint(ms->spacechar, ms->statusattr[NOHLITE], ms->numcols); |
| |
| // Initialise current menu item |
| curr = next_visible(menu, startopt); |
| prev = curr; |
| |
| gotoxy(ms->minrow + ms->statline, ms->mincol); |
| cprint(ms->spacechar, ms->statusattr[NOHLITE], ms->numcols); |
| gotoxy(ms->minrow + ms->statline, ms->mincol); |
| printmenuitem(menu->items[curr]->status, ms->statusattr); |
| first = calc_first_early(menu, curr); |
| prev_first = first; |
| while (1) // Forever |
| { |
| /* Redraw everything if: |
| * + we need to scroll (take care of scroll bars, ...) |
| * + menuoption |
| */ |
| if (prev_first != first || redraw) { |
| printmenu(menu, curr, top, left, first, radio); |
| } else { |
| /* Redraw only the highlighted entry */ |
| print_line(menu, curr, top, left, prev, prev - first, radio); |
| print_line(menu, curr, top, left, curr, curr - first, radio); |
| } |
| redraw = false; |
| prev = curr; |
| prev_first = first; |
| ci = menu->items[curr]; |
| asc = getch(); |
| switch (asc) { |
| case KEY_CTRL('L'): |
| redraw = true; |
| break; |
| case KEY_HOME: |
| curr = next_visible(menu, 0); |
| first = calc_first_early(menu, curr); |
| break; |
| case KEY_END: |
| curr = prev_visible(menu, numitems - 1); |
| first = calc_first_late(menu, curr); |
| break; |
| case KEY_PGDN: |
| for (i = 0; i < 5; i++) |
| curr = next_visible(menu, curr + 1); |
| first = calc_first_late(menu, curr); |
| break; |
| case KEY_PGUP: |
| for (i = 0; i < 5; i++) |
| curr = prev_visible(menu, curr - 1); |
| first = calc_first_early(menu, curr); |
| break; |
| case KEY_UP: |
| curr = prev_visible(menu, curr - 1); |
| if (curr < first) |
| first = calc_first_early(menu, curr); |
| break; |
| case KEY_DOWN: |
| curr = next_visible(menu, curr + 1); |
| if (!isvisible(menu, first, curr)) |
| first = calc_first_late(menu, curr); |
| break; |
| case KEY_LEFT: |
| case KEY_ESC: |
| return NULL; |
| break; |
| case KEY_RIGHT: |
| case KEY_ENTER: |
| if (ci->action == OPT_INACTIVE) |
| break; |
| if (ci->action == OPT_CHECKBOX) |
| break; |
| if (ci->action == OPT_SEP) |
| break; |
| if (ci->action == OPT_EXITMENU) |
| return NULL; // As if we hit Esc |
| // If we are going into a radio menu, dont call handler, return ci |
| if (ci->action == OPT_RADIOMENU) |
| return ci; |
| if (ci->handler != NULL) // Do we have a handler |
| { |
| hr = ci->handler(ms, ci); |
| if (hr.refresh) // Do we need to refresh |
| { |
| // Cleanup menu using old number of items |
| cleanupmenu(menu, top, left, numitems); |
| // Recalculate the number of items |
| numitems = calc_visible(menu, 0); |
| // Reprint the menu |
| printmenu(menu, curr, top, left, first, radio); |
| } |
| if (hr.valid) |
| return ci; |
| } else |
| return ci; |
| break; |
| case SPACECHAR: |
| if (ci->action != OPT_CHECKBOX) |
| break; |
| ci->itemdata.checked = !ci->itemdata.checked; |
| if (ci->handler != NULL) // Do we have a handler |
| { |
| hr = ci->handler(ms, ci); |
| if (hr.refresh) // Do we need to refresh |
| { |
| // Cleanup menu using old number of items |
| cleanupmenu(menu, top, left, numitems); |
| // Recalculate the number of items |
| numitems = calc_visible(menu, 0); |
| // Reprint the menu |
| printmenu(menu, curr, top, left, first, radio); |
| } |
| } |
| break; |
| default: |
| // Check if this is a shortcut key |
| if (((asc >= 'A') && (asc <= 'Z')) || |
| ((asc >= 'a') && (asc <= 'z')) || |
| ((asc >= '0') && (asc <= '9'))) { |
| tmp = find_shortcut(menu, asc, curr); |
| if ((tmp > curr) && (!isvisible(menu, first, tmp))) |
| first = calc_first_late(menu, tmp); |
| if (tmp < curr) |
| first = calc_first_early(menu, tmp); |
| curr = tmp; |
| } else { |
| if (ms->keys_handler) // Call extra keys handler |
| ms->keys_handler(ms, menu->items[curr], asc); |
| |
| /* The handler may have changed the UI, reset it on exit */ |
| reset_ui(); |
| // Cleanup menu using old number of items |
| cleanupmenu(menu, top, left, numitems); |
| // Recalculate the number of items |
| numitems = calc_visible(menu, 0); |
| // Reprint the menu |
| printmenu(menu, curr, top, left, first, radio); |
| } |
| break; |
| } |
| // Update status line |
| /* Erase the previous status */ |
| gotoxy(ms->minrow + ms->statline, ms->mincol); |
| cprint(ms->spacechar, ms->statusattr[NOHLITE], ms->numcols); |
| /* Print the new status */ |
| gotoxy(ms->minrow + ms->statline, ms->mincol); |
| printmenuitem(menu->items[curr]->status, ms->statusattr); |
| } |
| return NULL; // Should never come here |
| } |
| |
| /* Handle the entire system of menu's. */ |
| pt_menuitem runmenusystem(uchar top, uchar left, pt_menu cmenu, uchar startopt, |
| uchar menutype) |
| /* |
| * cmenu |
| * Which menu should be currently displayed |
| * top,left |
| * What is the position of the top,left corner of the menu |
| * startopt |
| * which menu item do I start with |
| * menutype |
| * NORMALMENU or RADIOMENU |
| * |
| * Return Value: |
| * Returns a pointer to the final item chosen, or NULL if nothing chosen. |
| */ |
| { |
| pt_menuitem opt, choice; |
| uchar startat, mt; |
| uchar row, col; |
| |
| if (cmenu == NULL) |
| return NULL; |
| |
| startover: |
| // Set the menu height |
| cmenu->menuheight = ms->maxrow - top - 3; |
| if (cmenu->menuheight > ms->maxmenuheight) |
| cmenu->menuheight = ms->maxmenuheight; |
| if (menutype == NORMALMENU) |
| opt = getmenuoption(cmenu, top, left, startopt, false); |
| else // menutype == RADIOMENU |
| opt = getmenuoption(cmenu, top, left, startopt, true); |
| |
| if (opt == NULL) { |
| // User hit Esc |
| cleanupmenu(cmenu, top, left, calc_visible(cmenu, 0)); |
| return NULL; |
| } |
| // Are we done with the menu system? |
| if ((opt->action != OPT_SUBMENU) && (opt->action != OPT_RADIOMENU)) { |
| cleanupmenu(cmenu, top, left, calc_visible(cmenu, 0)); |
| return opt; // parent cleanup other menus |
| } |
| // Either radiomenu or submenu |
| // Do we have a valid menu number? The next hack uses the fact that |
| // itemdata.submenunum = itemdata.radiomenunum (since enum data type) |
| if (opt->itemdata.submenunum >= ms->nummenus) // This is Bad.... |
| { |
| gotoxy(12, 12); // Middle of screen |
| csprint("ERROR: Invalid submenu requested.", 0x07); |
| cleanupmenu(cmenu, top, left, calc_visible(cmenu, 0)); |
| return NULL; // Pretend user hit esc |
| } |
| // Call recursively for submenu |
| // Position the submenu below the current item, |
| // covering half the current window (horizontally) |
| row = ms->menus[(unsigned int)opt->itemdata.submenunum]->row; |
| col = ms->menus[(unsigned int)opt->itemdata.submenunum]->col; |
| if (row == 0xFF) |
| row = top + opt->index + 2; |
| if (col == 0xFF) |
| col = left + 3 + (cmenu->menuwidth >> 1); |
| mt = (opt->action == OPT_SUBMENU ? NORMALMENU : RADIOMENU); |
| startat = 0; |
| if ((opt->action == OPT_RADIOMENU) && (opt->data != NULL)) |
| startat = ((t_menuitem *) opt->data)->index; |
| |
| choice = runmenusystem(row, col, |
| ms->menus[(unsigned int)opt->itemdata.submenunum], |
| startat, mt); |
| if (opt->action == OPT_RADIOMENU) { |
| if (choice != NULL) |
| opt->data = (void *)choice; // store choice in data field |
| if (opt->handler != NULL) |
| opt->handler(ms, opt); |
| choice = NULL; // Pretend user hit esc |
| } |
| if (choice == NULL) // User hit Esc in submenu |
| { |
| // Startover |
| startopt = opt->index; |
| goto startover; |
| } else { |
| cleanupmenu(cmenu, top, left, calc_visible(cmenu, 0)); |
| return choice; |
| } |
| } |
| |
| // Finds the indexof the menu with given name |
| uchar find_menu_num(const char *name) |
| { |
| int i; |
| pt_menu m; |
| |
| if (name == NULL) |
| return (uchar) (-1); |
| for (i = 0; i < ms->nummenus; i++) { |
| m = ms->menus[i]; |
| if ((m->name) && (strcmp(m->name, name) == 0)) |
| return i; |
| } |
| return (uchar) (-1); |
| } |
| |
| // Run through all items and if they are submenus |
| // with a non-trivial "action" and trivial submenunum |
| // replace submenunum with the menu with name "action" |
| void fix_submenus(void) |
| { |
| int i, j; |
| pt_menu m; |
| pt_menuitem mi; |
| |
| i = 0; |
| for (i = 0; i < ms->nummenus; i++) { |
| m = ms->menus[i]; |
| for (j = 0; j < m->numitems; j++) { |
| mi = m->items[j]; |
| // if item is a submenu and has non-empty non-trivial data string |
| if (mi->data && strlen(mi->data) > 0 && |
| ((mi->action == OPT_SUBMENU) |
| || (mi->action == OPT_RADIOMENU))) { |
| mi->itemdata.submenunum = find_menu_num(mi->data); |
| } |
| } |
| } |
| } |
| |
| /* User Callable functions */ |
| |
| pt_menuitem showmenus(uchar startmenu) |
| { |
| pt_menuitem rv; |
| |
| fix_submenus(); // Fix submenu numbers incase nick names were used |
| |
| /* Turn autowrap off, to avoid scrolling the menu */ |
| printf(CSI "?7l"); |
| |
| // Setup screen for menusystem |
| reset_ui(); |
| |
| // Go, main menu cannot be a radio menu |
| rv = runmenusystem(ms->minrow + MENUROW, ms->mincol + MENUCOL, |
| ms->menus[(unsigned int)startmenu], 0, NORMALMENU); |
| |
| // Hide the garbage we left on the screen |
| cls(); |
| gotoxy(ms->minrow, ms->mincol); |
| cursoron(); |
| |
| // Return user choice |
| return rv; |
| } |
| |
| pt_menusystem init_menusystem(const char *title) |
| { |
| int i; |
| |
| ms = NULL; |
| ms = (pt_menusystem) malloc(sizeof(t_menusystem)); |
| if (ms == NULL) |
| return NULL; |
| ms->nummenus = 0; |
| // Initialise all menu pointers |
| for (i = 0; i < MAXMENUS; i++) |
| ms->menus[i] = NULL; |
| |
| ms->title = (char *)malloc(TITLELEN + 1); |
| if (title == NULL) |
| strcpy(ms->title, TITLESTR); // Copy string |
| else |
| strcpy(ms->title, title); |
| |
| // Timeout settings |
| ms->tm_stepsize = TIMEOUTSTEPSIZE; |
| ms->tm_numsteps = TIMEOUTNUMSTEPS; |
| |
| ms->normalattr[NOHLITE] = NORMALATTR; |
| ms->normalattr[HLITE] = NORMALHLITE; |
| |
| ms->reverseattr[NOHLITE] = REVERSEATTR; |
| ms->reverseattr[HLITE] = REVERSEHLITE; |
| |
| ms->inactattr[NOHLITE] = INACTATTR; |
| ms->inactattr[HLITE] = INACTHLITE; |
| |
| ms->revinactattr[NOHLITE] = REVINACTATTR; |
| ms->revinactattr[HLITE] = REVINACTHLITE; |
| |
| ms->statusattr[NOHLITE] = STATUSATTR; |
| ms->statusattr[HLITE] = STATUSHLITE; |
| |
| ms->statline = STATLINE; |
| ms->tfillchar = TFILLCHAR; |
| ms->titleattr = TITLEATTR; |
| |
| ms->fillchar = FILLCHAR; |
| ms->fillattr = FILLATTR; |
| ms->spacechar = SPACECHAR; |
| ms->shadowattr = SHADOWATTR; |
| |
| ms->menupage = MENUPAGE; // Usually no need to change this at all |
| |
| // Initialise all handlers |
| ms->handler = NULL; |
| ms->keys_handler = NULL; |
| ms->ontimeout = NULL; // No timeout handler |
| ms->tm_total_timeout = 0; |
| ms->tm_sofar_timeout = 0; |
| ms->ontotaltimeout = NULL; |
| |
| // Setup ACTION_{,IN}VALID |
| ACTION_VALID.valid = 1; |
| ACTION_VALID.refresh = 0; |
| ACTION_INVALID.valid = 0; |
| ACTION_INVALID.refresh = 0; |
| |
| // Figure out the size of the screen we are in now. |
| // By default we use the whole screen for our menu |
| if (getscreensize(1, &ms->numrows, &ms->numcols)) { |
| /* Unknown screen size? */ |
| ms->numcols = 80; |
| ms->numrows = 24; |
| } |
| ms->minrow = ms->mincol = 0; |
| ms->maxcol = ms->numcols - 1; |
| ms->maxrow = ms->numrows - 1; |
| |
| // How many entries per menu can we display at a time |
| ms->maxmenuheight = ms->maxrow - ms->minrow - 3; |
| if (ms->maxmenuheight > MAXMENUHEIGHT) |
| ms->maxmenuheight = MAXMENUHEIGHT; |
| |
| console_ansi_raw(); |
| |
| return ms; |
| } |
| |
| void set_normal_attr(uchar normal, uchar selected, uchar inactivenormal, |
| uchar inactiveselected) |
| { |
| if (normal != 0xFF) |
| ms->normalattr[0] = normal; |
| if (selected != 0xFF) |
| ms->reverseattr[0] = selected; |
| if (inactivenormal != 0xFF) |
| ms->inactattr[0] = inactivenormal; |
| if (inactiveselected != 0xFF) |
| ms->revinactattr[0] = inactiveselected; |
| } |
| |
| void set_normal_hlite(uchar normal, uchar selected, uchar inactivenormal, |
| uchar inactiveselected) |
| { |
| if (normal != 0xFF) |
| ms->normalattr[1] = normal; |
| if (selected != 0xFF) |
| ms->reverseattr[1] = selected; |
| if (inactivenormal != 0xFF) |
| ms->inactattr[1] = inactivenormal; |
| if (inactiveselected != 0xFF) |
| ms->revinactattr[1] = inactiveselected; |
| } |
| |
| void set_status_info(uchar statusattr, uchar statushlite, uchar statline) |
| { |
| if (statusattr != 0xFF) |
| ms->statusattr[NOHLITE] = statusattr; |
| if (statushlite != 0xFF) |
| ms->statusattr[HLITE] = statushlite; |
| // statline is relative to minrow |
| if (statline >= ms->numrows) |
| statline = ms->numrows - 1; |
| ms->statline = statline; // relative to ms->minrow, 0 based |
| } |
| |
| void set_title_info(uchar tfillchar, uchar titleattr) |
| { |
| if (tfillchar != 0xFF) |
| ms->tfillchar = tfillchar; |
| if (titleattr != 0xFF) |
| ms->titleattr = titleattr; |
| } |
| |
| void set_misc_info(uchar fillchar, uchar fillattr, uchar spacechar, |
| uchar shadowattr) |
| { |
| if (fillchar != 0xFF) |
| ms->fillchar = fillchar; |
| if (fillattr != 0xFF) |
| ms->fillattr = fillattr; |
| if (spacechar != 0xFF) |
| ms->spacechar = spacechar; |
| if (shadowattr != 0xFF) |
| ms->shadowattr = shadowattr; |
| } |
| |
| void set_menu_options(uchar maxmenuheight) |
| { |
| if (maxmenuheight != 0xFF) |
| ms->maxmenuheight = maxmenuheight; |
| } |
| |
| // Set the window which menusystem should use |
| void set_window_size(uchar top, uchar left, uchar bot, uchar right) |
| { |
| int nr, nc; |
| |
| if ((top > bot) || (left > right)) |
| return; // Sorry no change will happen here |
| |
| if (getscreensize(1, &nr, &nc)) { |
| /* Unknown screen size? */ |
| nr = 80; |
| nc = 24; |
| } |
| if (bot >= nr) |
| bot = nr - 1; |
| if (right >= nc) |
| right = nc - 1; |
| ms->minrow = top; |
| ms->mincol = left; |
| ms->maxrow = bot; |
| ms->maxcol = right; |
| ms->numcols = right - left + 1; |
| ms->numrows = bot - top + 1; |
| if (ms->statline >= ms->numrows) |
| ms->statline = ms->numrows - 1; // Clip statline if need be |
| } |
| |
| void reg_handler(t_handler htype, void *handler) |
| { |
| // If bad value set to default screen handler |
| switch (htype) { |
| case HDLR_KEYS: |
| ms->keys_handler = (t_keys_handler) handler; |
| break; |
| default: |
| ms->handler = (t_menusystem_handler) handler; |
| break; |
| } |
| } |
| |
| void unreg_handler(t_handler htype) |
| { |
| switch (htype) { |
| case HDLR_KEYS: |
| ms->keys_handler = NULL; |
| break; |
| default: |
| ms->handler = NULL; |
| break; |
| } |
| } |
| |
| void reg_ontimeout(t_timeout_handler handler, unsigned int numsteps, |
| unsigned int stepsize) |
| { |
| ms->ontimeout = handler; |
| if (numsteps != 0) |
| ms->tm_numsteps = numsteps; |
| if (stepsize != 0) |
| ms->tm_stepsize = stepsize; |
| } |
| |
| void unreg_ontimeout(void) |
| { |
| ms->ontimeout = NULL; |
| } |
| |
| void reg_ontotaltimeout(t_timeout_handler handler, |
| unsigned long numcentiseconds) |
| { |
| if (numcentiseconds != 0) { |
| ms->ontotaltimeout = handler; |
| ms->tm_total_timeout = numcentiseconds * 10; // to convert to milliseconds |
| ms->tm_sofar_timeout = 0; |
| } |
| } |
| |
| void unreg_ontotaltimeout(void) |
| { |
| ms->ontotaltimeout = NULL; |
| } |
| |
| int next_visible(pt_menu menu, int index) |
| { |
| int ans; |
| if (index < 0) |
| ans = 0; |
| else if (index >= menu->numitems) |
| ans = menu->numitems - 1; |
| else |
| ans = index; |
| while ((ans < menu->numitems - 1) && |
| ((menu->items[ans]->action == OPT_INVISIBLE) || |
| (menu->items[ans]->action == OPT_SEP))) |
| ans++; |
| return ans; |
| } |
| |
| int prev_visible(pt_menu menu, int index) // Return index of prev visible |
| { |
| int ans; |
| if (index < 0) |
| ans = 0; |
| else if (index >= menu->numitems) |
| ans = menu->numitems - 1; |
| else |
| ans = index; |
| while ((ans > 0) && |
| ((menu->items[ans]->action == OPT_INVISIBLE) || |
| (menu->items[ans]->action == OPT_SEP))) |
| ans--; |
| return ans; |
| } |
| |
| int next_visible_sep(pt_menu menu, int index) |
| { |
| int ans; |
| if (index < 0) |
| ans = 0; |
| else if (index >= menu->numitems) |
| ans = menu->numitems - 1; |
| else |
| ans = index; |
| while ((ans < menu->numitems - 1) && |
| (menu->items[ans]->action == OPT_INVISIBLE)) |
| ans++; |
| return ans; |
| } |
| |
| int prev_visible_sep(pt_menu menu, int index) // Return index of prev visible |
| { |
| int ans; |
| if (index < 0) |
| ans = 0; |
| else if (index >= menu->numitems) |
| ans = menu->numitems - 1; |
| else |
| ans = index; |
| while ((ans > 0) && (menu->items[ans]->action == OPT_INVISIBLE)) |
| ans--; |
| return ans; |
| } |
| |
| int calc_visible(pt_menu menu, int first) |
| { |
| int ans, i; |
| |
| if (menu == NULL) |
| return 0; |
| ans = 0; |
| for (i = first; i < menu->numitems; i++) |
| if (menu->items[i]->action != OPT_INVISIBLE) |
| ans++; |
| return ans; |
| } |
| |
| // is curr visible if first entry is first? |
| int isvisible(pt_menu menu, int first, int curr) |
| { |
| if (curr < first) |
| return 0; |
| return (calc_visible(menu, first) - calc_visible(menu, curr) < |
| menu->menuheight); |
| } |
| |
| // Calculate the first entry to be displayed |
| // so that curr is visible and make curr as late as possible |
| int calc_first_late(pt_menu menu, int curr) |
| { |
| int ans, i, nv; |
| |
| nv = calc_visible(menu, 0); |
| if (nv <= menu->menuheight) |
| return 0; |
| // Start with curr and go back menu->menuheight times |
| ans = curr + 1; |
| for (i = 0; i < menu->menuheight; i++) |
| ans = prev_visible_sep(menu, ans - 1); |
| return ans; |
| } |
| |
| // Calculate the first entry to be displayed |
| // so that curr is visible and make curr as early as possible |
| int calc_first_early(pt_menu menu, int curr) |
| { |
| int ans, i, nv; |
| |
| nv = calc_visible(menu, 0); |
| if (nv <= menu->menuheight) |
| return 0; |
| // Start with curr and go back till >= menu->menuheight |
| // items are visible |
| nv = calc_visible(menu, curr); // Already nv of them are visible |
| ans = curr; |
| for (i = 0; i < menu->menuheight - nv; i++) |
| ans = prev_visible_sep(menu, ans - 1); |
| return ans; |
| } |
| |
| // Create a new menu and return its position |
| uchar add_menu(const char *title, int maxmenusize) |
| { |
| int num, i; |
| pt_menu m; |
| |
| num = ms->nummenus; |
| if (num >= MAXMENUS) |
| return -1; |
| m = NULL; |
| m = (pt_menu) malloc(sizeof(t_menu)); |
| if (m == NULL) |
| return -1; |
| ms->menus[num] = m; |
| m->numitems = 0; |
| m->name = NULL; |
| m->row = 0xFF; |
| m->col = 0xFF; |
| if (maxmenusize < 1) |
| m->maxmenusize = MAXMENUSIZE; |
| else |
| m->maxmenusize = maxmenusize; |
| m->items = (pt_menuitem *) malloc(sizeof(pt_menuitem) * (m->maxmenusize)); |
| for (i = 0; i < m->maxmenusize; i++) |
| m->items[i] = NULL; |
| |
| m->title = (char *)malloc(MENULEN + 1); |
| if (title) { |
| if (strlen(title) > MENULEN - 2) |
| strcpy(m->title, TITLELONG); |
| else |
| strcpy(m->title, title); |
| } else |
| strcpy(m->title, EMPTYSTR); |
| m->menuwidth = strlen(m->title); |
| ms->nummenus++; |
| return ms->nummenus - 1; |
| } |
| |
| void set_menu_name(const char *name) // Set the "name" of this menu |
| { |
| pt_menu m; |
| |
| m = ms->menus[ms->nummenus - 1]; |
| if (m->name) // Free up previous name |
| { |
| free(m->name); |
| m->name = NULL; |
| } |
| |
| if (name) { |
| m->name = (char *)malloc(strlen(name) + 1); |
| strcpy(m->name, name); |
| } |
| } |
| |
| // Create a new named menu and return its position |
| uchar add_named_menu(const char *name, const char *title, int maxmenusize) |
| { |
| add_menu(title, maxmenusize); |
| set_menu_name(name); |
| return ms->nummenus - 1; |
| } |
| |
| void set_menu_pos(uchar row, uchar col) // Set the position of this menu. |
| { |
| pt_menu m; |
| |
| m = ms->menus[ms->nummenus - 1]; |
| m->row = row; |
| m->col = col; |
| } |
| |
| pt_menuitem add_sep(void) // Add a separator to current menu |
| { |
| pt_menuitem mi; |
| pt_menu m; |
| |
| m = (ms->menus[ms->nummenus - 1]); |
| mi = NULL; |
| mi = (pt_menuitem) malloc(sizeof(t_menuitem)); |
| if (mi == NULL) |
| return NULL; |
| m->items[(unsigned int)m->numitems] = mi; |
| mi->handler = NULL; // No handler |
| mi->item = mi->status = mi->data = NULL; |
| mi->action = OPT_SEP; |
| mi->index = m->numitems++; |
| mi->parindex = ms->nummenus - 1; |
| mi->shortcut = 0; |
| mi->helpid = 0; |
| return mi; |
| } |
| |
| // Add item to the "current" menu |
| pt_menuitem add_item(const char *item, const char *status, t_action action, |
| const char *data, uchar itemdata) |
| { |
| pt_menuitem mi; |
| pt_menu m; |
| const char *str; |
| uchar inhlite = 0; // Are we inside hlite area |
| |
| m = (ms->menus[ms->nummenus - 1]); |
| mi = NULL; |
| mi = (pt_menuitem) malloc(sizeof(t_menuitem)); |
| if (mi == NULL) |
| return NULL; |
| m->items[(unsigned int)m->numitems] = mi; |
| mi->handler = NULL; // No handler |
| |
| // Allocate space to store stuff |
| mi->item = (char *)malloc(MENULEN + 1); |
| mi->status = (char *)malloc(STATLEN + 1); |
| mi->data = (char *)malloc(ACTIONLEN + 1); |
| |
| if (item) { |
| if (strlen(item) > MENULEN) { |
| strcpy(mi->item, ITEMLONG); |
| } else { |
| strcpy(mi->item, item); |
| } |
| if (strlen(mi->item) > m->menuwidth) |
| m->menuwidth = strlen(mi->item); |
| } else |
| strcpy(mi->item, EMPTYSTR); |
| |
| if (status) { |
| if (strlen(status) > STATLEN) { |
| strcpy(mi->status, STATUSLONG); |
| } else { |
| strcpy(mi->status, status); |
| } |
| } else |
| strcpy(mi->status, EMPTYSTR); |
| |
| mi->action = action; |
| str = mi->item; |
| mi->shortcut = 0; |
| mi->helpid = 0xFFFF; |
| inhlite = 0; // We have not yet seen an ENABLEHLITE char |
| // Find the first char in [A-Za-z0-9] after ENABLEHLITE and not arg to control char |
| while (*str) { |
| if (*str == ENABLEHLITE) { |
| inhlite = 1; |
| } |
| if (*str == DISABLEHLITE) { |
| inhlite = 0; |
| } |
| if ((inhlite == 1) && |
| (((*str >= 'A') && (*str <= 'Z')) || |
| ((*str >= 'a') && (*str <= 'z')) || |
| ((*str >= '0') && (*str <= '9')))) { |
| mi->shortcut = *str; |
| break; |
| } |
| ++str; |
| } |
| if ((mi->shortcut >= 'A') && (mi->shortcut <= 'Z')) // Make lower case |
| mi->shortcut = mi->shortcut - 'A' + 'a'; |
| |
| if (data) { |
| if (strlen(data) > ACTIONLEN) { |
| strcpy(mi->data, ACTIONLONG); |
| } else { |
| strcpy(mi->data, data); |
| } |
| } else |
| strcpy(mi->data, EMPTYSTR); |
| |
| switch (action) { |
| case OPT_SUBMENU: |
| mi->itemdata.submenunum = itemdata; |
| break; |
| case OPT_CHECKBOX: |
| mi->itemdata.checked = itemdata; |
| break; |
| case OPT_RADIOMENU: |
| mi->itemdata.radiomenunum = itemdata; |
| if (mi->data) |
| free(mi->data); |
| mi->data = NULL; // No selection made |
| break; |
| default: // to keep the compiler happy |
| break; |
| } |
| mi->index = m->numitems++; |
| mi->parindex = ms->nummenus - 1; |
| return mi; |
| } |
| |
| // Set the shortcut key for the current item |
| void set_item_options(uchar shortcut, int helpid) |
| { |
| pt_menuitem mi; |
| pt_menu m; |
| |
| m = (ms->menus[ms->nummenus - 1]); |
| if (m->numitems <= 0) |
| return; |
| mi = m->items[(unsigned int)m->numitems - 1]; |
| |
| if (shortcut != 0xFF) |
| mi->shortcut = shortcut; |
| if (helpid != 0xFFFF) |
| mi->helpid = helpid; |
| } |
| |
| // Free internal datasutructures |
| void close_menusystem(void) |
| { |
| } |
| |
| // append_line_helper(pt_menu menu,char *line) |
| void append_line_helper(int menunum, char *line) |
| { |
| pt_menu menu; |
| pt_menuitem mi, ri; |
| char *app; |
| int ctr; |
| |
| menu = ms->menus[menunum]; |
| for (ctr = 0; ctr < (int)menu->numitems; ctr++) { |
| mi = menu->items[ctr]; |
| app = NULL; //What to append |
| switch (mi->action) { |
| case OPT_CHECKBOX: |
| if (mi->itemdata.checked) |
| app = mi->data; |
| break; |
| case OPT_RADIOMENU: |
| if (mi->data) { // Some selection has been made |
| ri = (pt_menuitem) (mi->data); |
| app = ri->data; |
| } |
| break; |
| case OPT_SUBMENU: |
| append_line_helper(mi->itemdata.submenunum, line); |
| break; |
| default: |
| break; |
| } |
| if (app) { |
| strcat(line, " "); |
| strcat(line, app); |
| } |
| } |
| } |
| |
| // Generate string based on state of checkboxes and radioitem in given menu |
| // Assume line points to large enough buffer |
| void gen_append_line(const char *menu_name, char *line) |
| { |
| int menunum; |
| |
| menunum = find_menu_num(menu_name); |
| if (menunum < 0) |
| return; // No such menu |
| append_line_helper(menunum, line); |
| } |