Mercurial > grim > libgnt
view gntmenu.c @ 1324:4e6d829dcbc5
Merged in default (pull request #78)
Clear the main loop on WM destruction.
Approved-by: Gary Kramlich
author | Gary Kramlich <grim@reaperworld.com> |
---|---|
date | Tue, 21 May 2019 01:20:54 +0000 |
parents | f4dca145c933 |
children | 23e34422bea8 |
line wrap: on
line source
/* * GNT - The GLib Ncurses Toolkit * * GNT is the legal property of its developers, whose names are too numerous * to list here. Please refer to the COPYRIGHT file distributed with this * source distribution. * * This library 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "gntinternal.h" #include "gntmenu.h" #include "gntmenuitemcheck.h" #include "gntmenuitemprivate.h" #include "gntwidgetprivate.h" #include <ctype.h> #include <string.h> struct _GntMenu { GntTree parent; GntMenuType type; GList *list; guint selected; /* This will keep track of its immediate submenu which is visible so * that keystrokes can be passed to it. */ GntMenu *submenu; GntMenu *parentmenu; }; enum { SIGS = 1, }; enum { ITEM_TEXT = 0, ITEM_TRIGGER, ITEM_SUBMENU, NUM_COLUMNS }; static void (*org_draw)(GntWidget *wid); static void (*org_destroy)(GntWidget *wid); static void (*org_map)(GntWidget *wid); static void (*org_size_request)(GntWidget *wid); static gboolean (*org_key_pressed)(GntWidget *w, const char *t); static gboolean (*org_clicked)(GntWidget *w, GntMouseEvent event, int x, int y); static void menuitem_activate(GntMenu *menu, GntMenuItem *item); G_DEFINE_TYPE(GntMenu, gnt_menu, GNT_TYPE_TREE) static void menu_hide_all(GntMenu *menu) { while (menu->parentmenu) menu = menu->parentmenu; gnt_widget_hide(GNT_WIDGET(menu)); } static void show_submenu(GntMenu *menu) { GntMenuItem *item; if (menu->type != GNT_MENU_TOPLEVEL) return; item = g_list_nth_data(menu->list, menu->selected); if (!item || !gnt_menuitem_get_submenu(item)) { return; } menuitem_activate(menu, item); } static void gnt_menu_draw(GntWidget *widget) { GntMenu *menu = GNT_MENU(widget); GList *iter; chtype type; guint i; if (menu->type == GNT_MENU_TOPLEVEL) { WINDOW *window = gnt_widget_get_window(widget); gint x, y; gnt_widget_get_position(widget, &x, &y); wbkgdset(window, '\0' | gnt_color_pair(GNT_COLOR_HIGHLIGHT)); werase(window); for (i = 0, iter = menu->list; iter; iter = iter->next, i++) { GntMenuItem *item = GNT_MENU_ITEM(iter->data); if (!gnt_menuitem_is_visible(item)) continue; type = ' '; if (gnt_menuitem_has_callback(item) || gnt_menuitem_get_submenu(item)) { type |= gnt_color_pair(GNT_COLOR_HIGHLIGHT); } else { type |= gnt_color_pair(GNT_COLOR_DISABLED); } if (i == menu->selected) type |= A_REVERSE; gnt_menuitem_set_position(item, getcurx(window) + x, getcury(window) + y + 1); wbkgdset(window, type); wprintw(window, " %s ", C_(gnt_menuitem_get_text(item))); } } else { org_draw(widget); } } static void gnt_menu_size_request(GntWidget *widget) { GntMenu *menu = GNT_MENU(widget); if (menu->type == GNT_MENU_TOPLEVEL) { gnt_widget_set_internal_size(widget, getmaxx(stdscr), 1); } else { gint width; org_size_request(widget); gnt_widget_get_internal_size(widget, &width, NULL); gnt_widget_set_internal_size(widget, width, g_list_length(menu->list) + 2); } } static void menu_tree_add(GntMenu *menu, GntMenuItem *item, GntMenuItem *parent) { char trigger[] = "\0 )\0"; g_return_if_fail(item != NULL); if ((trigger[1] = gnt_menuitem_get_trigger(item)) && trigger[1] != ' ') trigger[0] = '('; if (GNT_IS_MENU_ITEM_CHECK(item)) { gnt_tree_add_choice( GNT_TREE(menu), item, gnt_tree_create_row(GNT_TREE(menu), gnt_menuitem_get_text(item), trigger, " "), parent, NULL); gnt_tree_set_choice(GNT_TREE(menu), item, gnt_menuitem_check_get_checked(GNT_MENU_ITEM_CHECK(item))); } else { gnt_tree_add_row_last( GNT_TREE(menu), item, gnt_tree_create_row( GNT_TREE(menu), gnt_menuitem_get_text(item), trigger, gnt_menuitem_get_submenu(item) ? ">" : " "), parent); if (!gnt_menuitem_has_callback(item) && !gnt_menuitem_get_submenu(item)) { gnt_tree_set_row_color(GNT_TREE(menu), item, GNT_COLOR_DISABLED); } } if (0 && gnt_menuitem_get_submenu(item)) { GntMenu *sub = GNT_MENU(gnt_menuitem_get_submenu(item)); GList *iter; for (iter = sub->list; iter; iter = iter->next) { GntMenuItem *it = GNT_MENU_ITEM(iter->data); menu_tree_add(menu, it, item); } } } #define GET_VAL(ch) ((ch >= '0' && ch <= '9') ? (ch - '0') : (ch >= 'a' && ch <= 'z') ? (10 + ch - 'a') : 36) static void assign_triggers(GntMenu *menu) { GList *iter; gboolean bools[37]; memset(bools, 0, sizeof(bools)); bools[36] = 1; for (iter = menu->list; iter; iter = iter->next) { GntMenuItem *item = iter->data; char trigger = tolower(gnt_menuitem_get_trigger(item)); if (trigger == '\0' || trigger == ' ') continue; bools[(int)GET_VAL(trigger)] = 1; } for (iter = menu->list; iter; iter = iter->next) { GntMenuItem *item = iter->data; char trigger = gnt_menuitem_get_trigger(item); const gchar *text = gnt_menuitem_get_text(item); if (trigger != '\0') continue; while (*text) { char ch = tolower(*text++); char t[2] = {ch, '\0'}; if (ch == ' ' || bools[(int)GET_VAL(ch)] || gnt_bindable_check_key(GNT_BINDABLE(menu), t)) continue; trigger = ch; break; } if (trigger == 0) trigger = gnt_menuitem_get_text(item)[0]; gnt_menuitem_set_trigger(item, trigger); bools[(int)GET_VAL(trigger)] = 1; } } static void gnt_menu_map(GntWidget *widget) { GntMenu *menu = GNT_MENU(widget); if (menu->type == GNT_MENU_TOPLEVEL) { gnt_widget_size_request(widget); } else { /* Populate the tree */ GList *iter; gnt_tree_remove_all(GNT_TREE(widget)); /* Try to assign some trigger for the items */ assign_triggers(menu); for (iter = menu->list; iter; iter = iter->next) { GntMenuItem *item = GNT_MENU_ITEM(iter->data); menu_tree_add(menu, item, NULL); } org_map(widget); gnt_tree_adjust_columns(GNT_TREE(widget)); } } static void menuitem_activate(GntMenu *menu, GntMenuItem *item) { if (!item) return; if (gnt_menuitem_activate(item)) { menu_hide_all(menu); } else { GntMenu *sub = GNT_MENU(gnt_menuitem_get_submenu(item)); if (sub) { gint x, y; menu->submenu = sub; sub->type = GNT_MENU_POPUP; /* Submenus are *never* toplevel */ sub->parentmenu = menu; if (menu->type != GNT_MENU_TOPLEVEL) { GntWidget *widget = GNT_WIDGET(menu); gint width; gnt_widget_get_position(widget, &x, &y); gnt_widget_get_internal_size(widget, &width, NULL); gnt_menuitem_set_position( item, x + width - 1, y + gnt_tree_get_selection_visible_line( GNT_TREE(menu))); } gnt_menuitem_get_position(item, &x, &y); gnt_widget_set_position(GNT_WIDGET(sub), x, y); gnt_widget_set_visible(GNT_WIDGET(sub), TRUE); gnt_widget_draw(GNT_WIDGET(sub)); } else { menu_hide_all(menu); } } } static GList* find_item_with_trigger(GList *start, GList *end, char trigger) { GList *iter; for (iter = start; iter != (end ? end : NULL); iter = iter->next) { if (gnt_menuitem_get_trigger(iter->data) == trigger) return iter; } return NULL; } static gboolean check_for_trigger(GntMenu *menu, char trigger) { /* check for a trigger key */ GList *iter; GList *find; GList *nth = g_list_find(menu->list, gnt_tree_get_selection_data(GNT_TREE(menu))); if (nth == NULL) return FALSE; find = find_item_with_trigger(nth->next, NULL, trigger); if (!find) find = find_item_with_trigger(menu->list, nth->next, trigger); if (!find) return FALSE; if (find != nth) { gnt_tree_set_selected(GNT_TREE(menu), find->data); iter = find_item_with_trigger(find->next, NULL, trigger); if (iter != NULL && iter != find) return TRUE; iter = find_item_with_trigger(menu->list, nth, trigger); if (iter != NULL && iter != find) return TRUE; } gnt_widget_activate(GNT_WIDGET(menu)); return TRUE; } static gboolean gnt_menu_key_pressed(GntWidget *widget, const char *text) { GntMenu *menu = GNT_MENU(widget); guint current = menu->selected; if (menu->submenu) { GntMenu *sub = menu; do sub = sub->submenu; while (sub->submenu); if (gnt_widget_key_pressed(GNT_WIDGET(sub), text)) return TRUE; if (menu->type != GNT_MENU_TOPLEVEL) return FALSE; } if ((text[0] == 27 && text[1] == 0) || (menu->type != GNT_MENU_TOPLEVEL && strcmp(text, GNT_KEY_LEFT) == 0)) { /* Escape closes menu */ GntMenu *par = menu->parentmenu; if (par != NULL) { par->submenu = NULL; gnt_widget_hide(widget); } else gnt_widget_hide(widget); if (par && par->type == GNT_MENU_TOPLEVEL) gnt_menu_key_pressed(GNT_WIDGET(par), text); return TRUE; } if (menu->type == GNT_MENU_TOPLEVEL) { GntMenuItem *item; GList *it; if (strcmp(text, GNT_KEY_LEFT) == 0) { do { if (menu->selected == 0) menu->selected = g_list_length(menu->list) - 1; else menu->selected--; it = g_list_nth(menu->list, menu->selected); item = it ? it->data : NULL; } while (!gnt_menuitem_is_visible(item)); } else if (strcmp(text, GNT_KEY_RIGHT) == 0) { do { menu->selected++; if (menu->selected >= g_list_length(menu->list)) menu->selected = 0; it = g_list_nth(menu->list, menu->selected); item = it ? it->data : NULL; } while (!gnt_menuitem_is_visible(item)); } else if (strcmp(text, GNT_KEY_ENTER) == 0 || strcmp(text, GNT_KEY_DOWN) == 0) { gnt_widget_activate(widget); } if (current != menu->selected) { GntMenu *sub = menu->submenu; if (sub) gnt_widget_hide(GNT_WIDGET(sub)); show_submenu(menu); gnt_widget_draw(widget); return TRUE; } } else { if (text[1] == '\0') { if (check_for_trigger(menu, text[0])) return TRUE; } else if (strcmp(text, GNT_KEY_RIGHT) == 0) { GntMenuItem *item = gnt_tree_get_selection_data(GNT_TREE(menu)); if (item && gnt_menuitem_get_submenu(item)) { menuitem_activate(menu, item); return TRUE; } } if (gnt_bindable_perform_action_key(GNT_BINDABLE(widget), text)) return TRUE; return org_key_pressed(widget, text); } return gnt_bindable_perform_action_key(GNT_BINDABLE(widget), text); } static void gnt_menu_destroy(GntWidget *widget) { GntMenu *menu = GNT_MENU(widget); g_list_free_full(menu->list, g_object_unref); org_destroy(widget); } static void gnt_menu_toggled(GntTree *tree, gpointer key) { GntMenuItem *item = GNT_MENU_ITEM(key); GntMenu *menu = GNT_MENU(tree); gboolean check = gnt_menuitem_check_get_checked(GNT_MENU_ITEM_CHECK(item)); gnt_menuitem_check_set_checked(GNT_MENU_ITEM_CHECK(item), !check); gnt_menuitem_activate(item); while (menu) { gnt_widget_hide(GNT_WIDGET(menu)); menu = menu->parentmenu; } } static void gnt_menu_activate(GntWidget *widget) { GntMenu *menu = GNT_MENU(widget); GntMenuItem *item; if (menu->type == GNT_MENU_TOPLEVEL) { item = g_list_nth_data(menu->list, menu->selected); } else { item = gnt_tree_get_selection_data(GNT_TREE(menu)); } if (item) { if (GNT_IS_MENU_ITEM_CHECK(item)) gnt_menu_toggled(GNT_TREE(widget), item); else menuitem_activate(menu, item); } } static void gnt_menu_hide(GntWidget *widget) { GntMenu *sub, *menu = GNT_MENU(widget); while ((sub = menu->submenu)) gnt_widget_hide(GNT_WIDGET(sub)); if (menu->parentmenu) menu->parentmenu->submenu = NULL; } static gboolean gnt_menu_clicked(GntWidget *widget, GntMouseEvent event, int x, int y) { if (GNT_MENU(widget)->type != GNT_MENU_POPUP) return FALSE; if (org_clicked && org_clicked(widget, event, x, y)) return TRUE; gnt_widget_activate(widget); return TRUE; } static void gnt_menu_class_init(GntMenuClass *klass) { GntWidgetClass *widget_class = GNT_WIDGET_CLASS(klass); GntTreeClass *tree_class = GNT_TREE_CLASS(klass); org_destroy = widget_class->destroy; org_map = widget_class->map; org_draw = widget_class->draw; org_key_pressed = widget_class->key_pressed; org_size_request = widget_class->size_request; org_clicked = widget_class->clicked; widget_class->destroy = gnt_menu_destroy; widget_class->draw = gnt_menu_draw; widget_class->map = gnt_menu_map; widget_class->size_request = gnt_menu_size_request; widget_class->key_pressed = gnt_menu_key_pressed; widget_class->activate = gnt_menu_activate; widget_class->hide = gnt_menu_hide; widget_class->clicked = gnt_menu_clicked; tree_class->toggled = gnt_menu_toggled; } static void gnt_menu_init(GntMenu *menu) { GntWidget *widget = GNT_WIDGET(menu); gnt_widget_set_has_shadow(widget, FALSE); gnt_widget_set_has_border(widget, FALSE); gnt_widget_set_take_focus(widget, TRUE); gnt_widget_set_transient(widget, TRUE); gnt_widget_set_disable_actions(widget, TRUE); } /****************************************************************************** * GntMenu API *****************************************************************************/ GntWidget *gnt_menu_new(GntMenuType type) { GntWidget *widget = g_object_new(GNT_TYPE_MENU, NULL); GntMenu *menu = GNT_MENU(widget); menu->list = NULL; menu->selected = 0; menu->type = type; if (type == GNT_MENU_TOPLEVEL) { gnt_widget_set_position(widget, 0, 0); } else { gnt_tree_set_show_separator(GNT_TREE(widget), FALSE); g_object_set(G_OBJECT(widget), "columns", NUM_COLUMNS, NULL); gnt_tree_set_col_width(GNT_TREE(widget), ITEM_TRIGGER, 3); gnt_tree_set_column_resizable(GNT_TREE(widget), ITEM_TRIGGER, FALSE); gnt_tree_set_col_width(GNT_TREE(widget), ITEM_SUBMENU, 1); gnt_tree_set_column_resizable(GNT_TREE(widget), ITEM_SUBMENU, FALSE); gnt_widget_set_has_border(widget, TRUE); } return widget; } void gnt_menu_add_item(GntMenu *menu, GntMenuItem *item) { menu->list = g_list_append(menu->list, item); } GntMenuItem *gnt_menu_get_item(GntMenu *menu, const char *id) { GntMenuItem *item = NULL; GList *iter = menu->list; if (!id || !*id) return NULL; for (; iter; iter = iter->next) { GntMenu *sub; item = iter->data; sub = gnt_menuitem_get_submenu(item); if (sub) { item = gnt_menu_get_item(sub, id); if (item) break; } else { const char *itid = gnt_menuitem_get_id(item); if (itid && strcmp(itid, id) == 0) break; /* XXX: Perhaps look at the menu-label as well? */ } item = NULL; } return item; } /* Internal. */ GntMenuType gnt_menu_get_menutype(GntMenu *menu) { g_return_val_if_fail(GNT_IS_MENU(menu), 0); return menu->type; } /* Internal. */ GntMenuItem * gnt_menu_get_selected_item(GntMenu *menu) { g_return_val_if_fail(GNT_IS_MENU(menu), NULL); return g_list_nth_data(menu->list, menu->selected); } /* Internal. */ GntMenu * gnt_menu_get_parent_menu(GntMenu *menu) { g_return_val_if_fail(GNT_IS_MENU(menu), NULL); return menu->parentmenu; } /* Internal. */ GntMenu * gnt_menu_get_submenu(GntMenu *menu) { g_return_val_if_fail(GNT_IS_MENU(menu), NULL); return menu->submenu; }