This is tuimenu.c in view mode; [Download] [Up]
/* * tuimenu.c - A (very) simple text-based menu. * * Written by * Ettore Perazzoli (ettore@comm2000.it) * * This file is part of VICE, the Versatile Commodore Emulator. * See README for copyright notice. * * 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; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA * 02111-1307 USA. * */ #include "vice.h" #include <stdlib.h> #include <strings.h> #include <ctype.h> #include <conio.h> #include <pc.h> #include <keys.h> #include "tui.h" #include "tuimenu.h" #include "utils.h" #undef MENU_STARTS_UNHIGHLIGHTED /* ------------------------------------------------------------------------- */ typedef struct tui_menu_item tui_menu_item_t; struct tui_menu_item { /* Item type. */ enum { TUI_MENU_COMMAND, /* Call a callback function. */ TUI_MENU_SUBMENU, /* Activate a submenu. */ TUI_MENU_SEPARATOR /* Unreachable separator. */ } type; /* Label for this menu item. */ char *label; /* Help string for this menu item. */ char *help_string; /* Hot key. */ char hot_key; /* Place of the hot key on the label. */ int hot_key_offset; /* Function to call when this item is activated. */ tui_menu_callback_t callback; /* Extra parameter to pass to the callback function. */ void *callback_param; /* Optional parameter string. If NULL, no parameter is displayed. (Always NULL for separators and submenus.) */ char *par_string; /* Maximum length of the parameter string. (Always zero for separators and submenus.) */ int par_string_max_len; /* Behavior of this item when activated. */ tui_menu_item_behavior_t behavior; /* Link to the submenu (if needed). */ tui_menu_t submenu; /* Links to the next and previous items. */ tui_menu_item_t *next, *prev; }; struct tui_menu { /* Menu title (can be NULL). */ char *title; /* Number of items in this menu. */ int num_items; /* Maximum width of the largest item. This also considers the parameter on the right. */ int width; /* Specifies the number of lines between menu items; e.g. 1 means one item per line, 2 one item every two lines. */ int spacing; /* Menu items. */ tui_menu_item_t *item_list; /* Default item when the menu is open. */ int default_item; }; /* ------------------------------------------------------------------------- */ static void tui_menu_call_callback(tui_menu_item_t *item, int param, int *become_default) { tui_menu_callback_t callback = item->callback; const char *new_par_string; if (callback == NULL) { if (item->par_string != NULL) free(item->par_string); item->par_string = NULL; return; } new_par_string = (*callback)(param, item->callback_param, become_default); if (new_par_string == NULL) { if (item->par_string != NULL) free(item->par_string); item->par_string = NULL; } else { if (item->par_string != NULL) { item->par_string = xrealloc(item->par_string, strlen(new_par_string) + 1); strcpy(item->par_string, new_par_string); } else { item->par_string = stralloc(new_par_string); } } } void tui_menu_update(tui_menu_t menu) { tui_menu_item_t *p; int i; for (p = menu->item_list, i = 0; p != NULL; p = p->next, i++) { int become_default = 0; switch (p->type) { case TUI_MENU_COMMAND: tui_menu_call_callback(p, 0, &become_default); break; case TUI_MENU_SUBMENU: tui_menu_call_callback(p, 0, &become_default); tui_menu_update(p->submenu); break; default: } if (become_default) menu->default_item = i; } } static void tui_menu_item_free(tui_menu_item_t *item) { if (item->label != NULL) free(item->label); if (item->help_string != NULL) free(item->help_string); if (item->par_string != NULL) free(item->par_string); } tui_menu_t tui_menu_create(const char *title, int spacing) { tui_menu_t new; new = (tui_menu_t) xmalloc(sizeof(struct tui_menu)); if (title != NULL) { new->title = stralloc(title); new->width = strlen(title) + 4; if (new->width % 2 != 0) new->width++; } else { new->title = NULL; new->width = 0; } new->spacing = spacing; new->num_items = 0; new->item_list = NULL; new->default_item = 0; return new; } void tui_menu_free(tui_menu_t menu) { tui_menu_item_t *p, *pnext; if (menu->title != NULL) free(menu->title); for (p = menu->item_list; p != NULL; p = pnext) { pnext = p->next; tui_menu_item_free(p); } } static tui_menu_item_t *tui_menu_find_last_item(tui_menu_t menu) { tui_menu_item_t *p; if (menu->item_list == NULL) return NULL; p = menu->item_list; while (p->next != NULL) p = p->next; return p; } static tui_menu_item_t *tui_menu_add_generic(tui_menu_t menu) { tui_menu_item_t *p; p = tui_menu_find_last_item(menu); if (p == NULL) { p = menu->item_list = (tui_menu_item_t *)xmalloc(sizeof(tui_menu_item_t)); memset(p, 0, sizeof(*p)); } else { p->next = (tui_menu_item_t *) xmalloc(sizeof(tui_menu_item_t)); memset(p->next, 0, sizeof(*p)); p->next->prev = p; p = p->next; } return p; } static int set_label(tui_menu_item_t *item, const char *label) { char *p; /* Find the hot key and allocate the label removing the corresponding hot key prefix. */ p = strchr(label, TUI_MENU_HOT_KEY_PREFIX); if (p == NULL) { item->hot_key_offset = -1; item->hot_key = '\0'; item->label = stralloc(label); return strlen(item->label); } else { item->hot_key_offset = p - label; item->hot_key = toupper(*(p + 1)); item->label = (char *)xmalloc(strlen(label)); if (item->hot_key_offset != 0) memcpy(item->label, label, item->hot_key_offset); strcpy(item->label + item->hot_key_offset, p + 1); return strlen(item->label); } } void tui_menu_add_item(tui_menu_t menu, const char *label, const char *help_string, tui_menu_callback_t callback, void *callback_param, int par_string_max_len, tui_menu_item_behavior_t behavior) { tui_menu_item_t *new = tui_menu_add_generic(menu); int width, dummy; width = set_label(new, label); if (par_string_max_len != 0) width += par_string_max_len + 1; new->type = TUI_MENU_COMMAND; new->callback = callback; new->callback_param = callback_param; new->par_string = NULL; new->par_string_max_len = par_string_max_len; new->behavior = behavior; menu->num_items++; new->help_string = help_string != NULL ? stralloc(help_string) : NULL; if (width > menu->width) menu->width = width; /* Make sure `par_string' is initialized. */ tui_menu_call_callback(new, 0, &dummy); } void tui_menu_add_separator(tui_menu_t menu) { tui_menu_item_t *new = tui_menu_add_generic(menu); new->type = TUI_MENU_SEPARATOR; menu->num_items++; } void tui_menu_add_submenu(tui_menu_t menu, const char *label, const char *help_string, tui_menu_t submenu, tui_menu_callback_t callback, void *callback_param, int par_string_max_len) { tui_menu_item_t *new = tui_menu_add_generic(menu); int width; new->type = TUI_MENU_SUBMENU; new->callback = callback; new->callback_param = callback_param; new->par_string = NULL; new->par_string_max_len = par_string_max_len; new->submenu = submenu; new->behavior = TUI_MENU_BEH_CONTINUE; width = set_label(new, label); if (par_string_max_len != 0) width += par_string_max_len + 1; if (width > menu->width) menu->width = width; new->help_string = help_string != NULL ? stralloc(help_string) : NULL; menu->num_items++; } void tui_menu_add(tui_menu_t menu, const tui_menu_item_def_t *def) { const tui_menu_item_def_t *p = def; while (p->label != NULL) { if (p->submenu != NULL) { tui_menu_t s = tui_menu_create(p->submenu_title, 1); tui_menu_add(s, p->submenu); tui_menu_add_submenu(menu, p->label, p->help_string, s, p->callback, p->callback_param, p->par_string_max_len); } else if (*p->label == '-') { tui_menu_add_separator(menu); } else { tui_menu_add_item(menu, p->label, p->help_string, p->callback, p->callback_param, p->par_string_max_len, p->behavior); } p++; } } /* ------------------------------------------------------------------------- */ static void tui_menu_display_item(tui_menu_item_t *item, int width, int x, int y, int highlight) { int x_stop = x + width - 1; int background_color = highlight ? MENU_HIGHLIGHT : MENU_BACK; int i; tui_set_attr(MENU_FORE, background_color, 0); tui_put_char(x, y, ' '); x++; for (i = 0; item->label[i] != '\0' && x < x_stop; x++, i++) { if (i == item->hot_key_offset) { tui_set_attr(MENU_HOTKEY, background_color, 0); tui_put_char(x, y, item->label[i]); tui_set_attr(MENU_FORE, background_color, 0); } else { tui_put_char(x, y, item->label[i]); } } if (item->par_string != NULL) { int len = strlen(item->par_string); char *p; if (len > item->par_string_max_len) { p = item->par_string + len - item->par_string_max_len; len = item->par_string_max_len; tui_display(x_stop - len, y, len + 1, "%s", p); tui_display(x_stop - len, y, 0, ".."); } else { p = item->par_string; tui_display(x_stop - len, y, len + 1, "%s", p); } tui_hline(x, y, ' ', x_stop - len - x); } else { tui_hline(x, y, ' ', x_stop - x + 1); } } int tui_menu_handle(tui_menu_t menu, char hotkey) { tui_menu_item_t *item_ptr; tui_area_t backing_store = NULL; int total_width, total_height; int menu_x, menu_y; int current_item; int need_update; int y; #ifdef __MSDOS__ _setcursortype(_NOCURSOR); #endif tui_flush_keys(); total_width = menu->width + 4; if (total_width > tui_num_cols() - 2) total_width = tui_num_cols() - 2; total_height = menu->spacing * menu->num_items + 2; if (menu->spacing > 1) total_height -= menu->spacing - 1; menu_x = CENTER_X(total_width); menu_y = CENTER_Y(total_height); tui_display_window(menu_x, menu_y, total_width, total_height, MENU_BORDER, MENU_BACK, menu->title, &backing_store); { int i; current_item = menu->default_item; for (i = 0, item_ptr = menu->item_list; i < current_item; i++) item_ptr = item_ptr->next; } need_update = 1; while (1) { int key; if (need_update) { tui_menu_item_t *p; /* Redraw the menu. */ for (p = menu->item_list, y = menu_y + 1; p != NULL; p = p->next, y += menu->spacing) { if (p->type != TUI_MENU_SEPARATOR) tui_menu_display_item(p, total_width - 2, menu_x + 1, y, 0); } need_update = 0; } y = menu_y + menu->spacing * current_item + 1; if (item_ptr != NULL) { tui_menu_display_item(item_ptr, total_width - 2, menu_x + 1, y, 1); tui_set_attr(FIRST_LINE_FORE, FIRST_LINE_BACK, 0); tui_display(0, tui_num_lines() - 1, tui_num_cols(), "%s", item_ptr->help_string != NULL ? item_ptr->help_string : ""); } else { tui_set_attr(FIRST_LINE_FORE, FIRST_LINE_BACK, 0); tui_display(0, tui_num_lines() - 1, tui_num_cols(), ""); } /* The first keypress is the caller-specified one. */ if (hotkey != 0) { key = hotkey; hotkey = 0; } else key = getkey(); if (item_ptr != NULL) tui_menu_display_item(item_ptr, total_width - 2, menu_x + 1, y, 0); switch (key) { case K_Escape: tui_area_put(backing_store, menu_x, menu_y); tui_area_free(backing_store); return 0; /* Leave this menu. */ case K_Tab: tui_area_put(backing_store, menu_x, menu_y); tui_area_free(backing_store); return 1; /* Resume emulation. */ case K_Up: if (item_ptr == NULL || item_ptr->prev == NULL) { current_item = menu->num_items - 1; item_ptr = tui_menu_find_last_item(menu); } else if (current_item > 0) { do { item_ptr = item_ptr->prev; current_item--; } while (item_ptr != NULL && item_ptr->type == TUI_MENU_SEPARATOR); if (item_ptr == NULL) { current_item = menu->num_items - 1; item_ptr = tui_menu_find_last_item(menu); } } break; case K_Down: if (item_ptr == NULL || item_ptr->next == NULL) { item_ptr = menu->item_list; current_item = 0; } else { do { item_ptr = item_ptr->next; current_item++; } while (item_ptr != NULL && item_ptr->type == TUI_MENU_SEPARATOR); if (item_ptr == NULL) { item_ptr = menu->item_list; current_item = 0; } } break; case ' ': case K_Return: if (item_ptr != NULL) { int ret = 0; int become_default = 1; tui_menu_display_item(item_ptr, total_width - 2, menu_x + 1, y, 1); if (item_ptr->type == TUI_MENU_SUBMENU) ret = tui_menu_handle(item_ptr->submenu, 0); tui_menu_call_callback(item_ptr, 1, &become_default); if (become_default) menu->default_item = current_item; if (ret || item_ptr->behavior != TUI_MENU_BEH_CONTINUE) { tui_area_put(backing_store, menu_x, menu_y); tui_area_free(backing_store); if (item_ptr->behavior == TUI_MENU_BEH_RESUME) return 1; else return ret; } need_update = 1; } break; default: if (key <= 0xff && isalnum((char)key)) { int key_char = toupper((char)key); tui_menu_item_t *p; int i; for (p = menu->item_list, i = 0; p != NULL; p = p->next, i++) { if (p->hot_key == key_char) { int ret = 0; int become_default = 1; item_ptr = p; current_item = i; /* The action could change values in the current menu. */ need_update = 1; y = menu_y + menu->spacing * current_item + 1; tui_menu_display_item(item_ptr, total_width - 2, menu_x + 1, y, 1); if (p->type == TUI_MENU_SUBMENU) ret = tui_menu_handle(p->submenu, 0); tui_menu_call_callback(p, 1, &become_default); if (become_default) menu->default_item = current_item; if (ret || p->behavior != TUI_MENU_BEH_CONTINUE) { tui_area_put(backing_store, menu_x, menu_y); tui_area_free(backing_store); if (p->behavior == TUI_MENU_BEH_RESUME) return 1; else return ret; } break; } } } } } }
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.