ftp.nice.ch/pub/next/tools/emulators/vice.0.15.0.NeXT.sd.tgz#/vice-0.15.0/src/arch/msdos/tuimenu.c

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.