Initial commit.
[gnt.git] / gntmenu.c
blob2ca20666fcc520ec8705a959496a6ea3c3814c9f
1 /**
2 * GNT - The GLib Ncurses Toolkit
4 * GNT is the legal property of its developers, whose names are too numerous
5 * to list here. Please refer to the COPYRIGHT file distributed with this
6 * source distribution.
8 * This library is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
23 #include "gntmenu.h"
24 #include "gntmenuitemcheck.h"
26 #include <ctype.h>
27 #include <string.h>
29 enum
31 SIGS = 1,
34 enum
36 ITEM_TEXT = 0,
37 ITEM_TRIGGER,
38 ITEM_SUBMENU,
39 NUM_COLUMNS
42 static GntTreeClass *parent_class = NULL;
44 static void (*org_draw)(GntWidget *wid);
45 static void (*org_destroy)(GntWidget *wid);
46 static void (*org_map)(GntWidget *wid);
47 static void (*org_size_request)(GntWidget *wid);
48 static gboolean (*org_key_pressed)(GntWidget *w, const char *t);
50 static void
51 menu_hide_all(GntMenu *menu)
53 while (menu->parentmenu)
54 menu = menu->parentmenu;
55 gnt_widget_hide(GNT_WIDGET(menu));
58 static void
59 gnt_menu_draw(GntWidget *widget)
61 GntMenu *menu = GNT_MENU(widget);
62 GList *iter;
63 chtype type;
64 int i;
66 if (menu->type == GNT_MENU_TOPLEVEL) {
67 wbkgdset(widget->window, '\0' | gnt_color_pair(GNT_COLOR_HIGHLIGHT));
68 werase(widget->window);
70 for (i = 0, iter = menu->list; iter; iter = iter->next, i++) {
71 GntMenuItem *item = GNT_MENU_ITEM(iter->data);
72 type = ' ' | gnt_color_pair(GNT_COLOR_HIGHLIGHT);
73 if (i == menu->selected)
74 type |= A_REVERSE;
75 item->priv.x = getcurx(widget->window) + widget->priv.x;
76 item->priv.y = getcury(widget->window) + widget->priv.y + 1;
77 wbkgdset(widget->window, type);
78 wprintw(widget->window, " %s ", item->text);
80 } else {
81 org_draw(widget);
84 GNTDEBUG;
87 static void
88 gnt_menu_size_request(GntWidget *widget)
90 GntMenu *menu = GNT_MENU(widget);
92 if (menu->type == GNT_MENU_TOPLEVEL) {
93 widget->priv.height = 1;
94 widget->priv.width = getmaxx(stdscr);
95 } else {
96 org_size_request(widget);
97 widget->priv.height = g_list_length(menu->list) + 2;
101 static void
102 menu_tree_add(GntMenu *menu, GntMenuItem *item, GntMenuItem *parent)
104 char trigger[4] = "\0 )\0";
106 if ((trigger[1] = gnt_menuitem_get_trigger(item)) && trigger[1] != ' ')
107 trigger[0] = '(';
109 if (GNT_IS_MENU_ITEM_CHECK(item)) {
110 gnt_tree_add_choice(GNT_TREE(menu), item,
111 gnt_tree_create_row(GNT_TREE(menu), item->text, trigger, " "), parent, NULL);
112 gnt_tree_set_choice(GNT_TREE(menu), item, gnt_menuitem_check_get_checked(GNT_MENU_ITEM_CHECK(item)));
113 } else
114 gnt_tree_add_row_last(GNT_TREE(menu), item,
115 gnt_tree_create_row(GNT_TREE(menu), item->text, trigger, item->submenu ? ">" : " "), parent);
117 if (0 && item->submenu) {
118 GntMenu *sub = GNT_MENU(item->submenu);
119 GList *iter;
120 for (iter = sub->list; iter; iter = iter->next) {
121 GntMenuItem *it = GNT_MENU_ITEM(iter->data);
122 menu_tree_add(menu, it, item);
127 #define GET_VAL(ch) ((ch >= '0' && ch <= '9') ? (ch - '0') : (ch >= 'a' && ch <= 'z') ? (10 + ch - 'a') : 36)
129 static void
130 assign_triggers(GntMenu *menu)
132 GList *iter;
133 gboolean bools[37];
135 memset(bools, 0, sizeof(bools));
136 bools[36] = 1;
138 for (iter = menu->list; iter; iter = iter->next) {
139 GntMenuItem *item = iter->data;
140 char trigger = tolower(gnt_menuitem_get_trigger(item));
141 if (trigger == '\0' || trigger == ' ')
142 continue;
143 bools[(int)GET_VAL(trigger)] = 1;
146 for (iter = menu->list; iter; iter = iter->next) {
147 GntMenuItem *item = iter->data;
148 char trigger = gnt_menuitem_get_trigger(item);
149 const char *text = item->text;
150 if (trigger != '\0')
151 continue;
152 while (*text) {
153 char ch = tolower(*text++);
154 if (ch == ' ' || bools[(int)GET_VAL(ch)])
155 continue;
156 trigger = ch;
157 break;
159 if (trigger == 0)
160 trigger = item->text[0];
161 gnt_menuitem_set_trigger(item, trigger);
162 bools[(int)GET_VAL(trigger)] = 1;
166 static void
167 gnt_menu_map(GntWidget *widget)
169 GntMenu *menu = GNT_MENU(widget);
171 if (menu->type == GNT_MENU_TOPLEVEL) {
172 gnt_widget_size_request(widget);
173 } else {
174 /* Populate the tree */
175 GList *iter;
176 gnt_tree_remove_all(GNT_TREE(widget));
177 /* Try to assign some trigger for the items */
178 assign_triggers(menu);
179 for (iter = menu->list; iter; iter = iter->next) {
180 GntMenuItem *item = GNT_MENU_ITEM(iter->data);
181 menu_tree_add(menu, item, NULL);
183 org_map(widget);
184 gnt_tree_adjust_columns(GNT_TREE(widget));
186 GNTDEBUG;
189 static void
190 menuitem_activate(GntMenu *menu, GntMenuItem *item)
192 if (!item)
193 return;
195 if (gnt_menuitem_activate(item)) {
196 menu_hide_all(menu);
197 } else {
198 if (item->submenu) {
199 GntMenu *sub = GNT_MENU(item->submenu);
200 menu->submenu = sub;
201 sub->type = GNT_MENU_POPUP; /* Submenus are *never* toplevel */
202 sub->parentmenu = menu;
203 if (menu->type != GNT_MENU_TOPLEVEL) {
204 GntWidget *widget = GNT_WIDGET(menu);
205 item->priv.x = widget->priv.x + widget->priv.width - 1;
206 item->priv.y = widget->priv.y + gnt_tree_get_selection_visible_line(GNT_TREE(menu));
208 gnt_widget_set_position(GNT_WIDGET(sub), item->priv.x, item->priv.y);
209 GNT_WIDGET_UNSET_FLAGS(GNT_WIDGET(sub), GNT_WIDGET_INVISIBLE);
210 gnt_widget_draw(GNT_WIDGET(sub));
211 } else {
212 menu_hide_all(menu);
217 static GList*
218 find_item_with_trigger(GList *start, GList *end, char trigger)
220 GList *iter;
221 for (iter = start; iter != (end ? end : NULL); iter = iter->next) {
222 if (gnt_menuitem_get_trigger(iter->data) == trigger)
223 return iter;
225 return NULL;
228 static gboolean
229 check_for_trigger(GntMenu *menu, char trigger)
231 /* check for a trigger key */
232 GList *iter;
233 GList *find;
234 GList *nth = g_list_find(menu->list, gnt_tree_get_selection_data(GNT_TREE(menu)));
236 if (nth == NULL)
237 return FALSE;
239 find = find_item_with_trigger(nth->next, NULL, trigger);
240 if (!find)
241 find = find_item_with_trigger(menu->list, nth->next, trigger);
242 if (!find)
243 return FALSE;
244 if (find != nth) {
245 gnt_tree_set_selected(GNT_TREE(menu), find->data);
246 iter = find_item_with_trigger(find->next, NULL, trigger);
247 if (iter != NULL && iter != find)
248 return TRUE;
249 iter = find_item_with_trigger(menu->list, nth, trigger);
250 if (iter != NULL && iter != find)
251 return TRUE;
253 gnt_widget_activate(GNT_WIDGET(menu));
254 return TRUE;
257 static gboolean
258 gnt_menu_key_pressed(GntWidget *widget, const char *text)
260 GntMenu *menu = GNT_MENU(widget);
261 int current = menu->selected;
263 if (menu->submenu) {
264 GntMenu *sub = menu;
265 do sub = sub->submenu; while (sub->submenu);
266 if (gnt_widget_key_pressed(GNT_WIDGET(sub), text))
267 return TRUE;
270 if ((text[0] == 27 && text[1] == 0) ||
271 (menu->type != GNT_MENU_TOPLEVEL && strcmp(text, GNT_KEY_LEFT) == 0)) {
272 /* Escape closes menu */
273 GntMenu *par = menu->parentmenu;
274 if (par != NULL) {
275 par->submenu = NULL;
276 gnt_widget_hide(widget);
277 } else
278 gnt_widget_hide(widget);
279 return TRUE;
282 if (menu->type == GNT_MENU_TOPLEVEL) {
283 if (strcmp(text, GNT_KEY_LEFT) == 0) {
284 menu->selected--;
285 if (menu->selected < 0)
286 menu->selected = g_list_length(menu->list) - 1;
287 } else if (strcmp(text, GNT_KEY_RIGHT) == 0) {
288 menu->selected++;
289 if (menu->selected >= g_list_length(menu->list))
290 menu->selected = 0;
291 } else if (strcmp(text, GNT_KEY_ENTER) == 0 ||
292 strcmp(text, GNT_KEY_DOWN) == 0) {
293 gnt_widget_activate(widget);
296 if (current != menu->selected) {
297 GntMenu *sub = menu->submenu;
298 if (sub)
299 gnt_widget_hide(GNT_WIDGET(sub));
300 gnt_widget_draw(widget);
301 return TRUE;
303 } else {
304 if (text[1] == '\0') {
305 if (check_for_trigger(menu, text[0]))
306 return TRUE;
307 } else if (strcmp(text, GNT_KEY_RIGHT) == 0) {
308 GntMenuItem *item = gnt_tree_get_selection_data(GNT_TREE(menu));
309 if (item && item->submenu) {
310 menuitem_activate(menu, item);
311 return TRUE;
314 return org_key_pressed(widget, text);
317 return FALSE;
320 static void
321 gnt_menu_destroy(GntWidget *widget)
323 GntMenu *menu = GNT_MENU(widget);
324 g_list_foreach(menu->list, (GFunc)g_object_unref, NULL);
325 g_list_free(menu->list);
326 org_destroy(widget);
329 static void
330 gnt_menu_toggled(GntTree *tree, gpointer key)
332 GntMenuItem *item = GNT_MENU_ITEM(key);
333 GntMenu *menu = GNT_MENU(tree);
334 gboolean check = gnt_menuitem_check_get_checked(GNT_MENU_ITEM_CHECK(item));
335 gnt_menuitem_check_set_checked(GNT_MENU_ITEM_CHECK(item), !check);
336 gnt_menuitem_activate(item);
337 while (menu) {
338 gnt_widget_hide(GNT_WIDGET(menu));
339 menu = menu->parentmenu;
343 static void
344 gnt_menu_activate(GntWidget *widget)
346 GntMenu *menu = GNT_MENU(widget);
347 GntMenuItem *item;
349 if (menu->type == GNT_MENU_TOPLEVEL) {
350 item = g_list_nth_data(menu->list, menu->selected);
351 } else {
352 item = gnt_tree_get_selection_data(GNT_TREE(menu));
355 if (item) {
356 if (GNT_IS_MENU_ITEM_CHECK(item))
357 gnt_menu_toggled(GNT_TREE(widget), item);
358 else
359 menuitem_activate(menu, item);
363 static void
364 gnt_menu_hide(GntWidget *widget)
366 GntMenu *sub, *menu = GNT_MENU(widget);
368 while ((sub = menu->submenu))
369 gnt_widget_hide(GNT_WIDGET(sub));
370 if (menu->parentmenu)
371 menu->parentmenu->submenu = NULL;
374 static void
375 gnt_menu_class_init(GntMenuClass *klass)
377 GntWidgetClass *wid_class = GNT_WIDGET_CLASS(klass);
378 parent_class = GNT_TREE_CLASS(klass);
380 org_destroy = wid_class->destroy;
381 org_map = wid_class->map;
382 org_draw = wid_class->draw;
383 org_key_pressed = wid_class->key_pressed;
384 org_size_request = wid_class->size_request;
386 wid_class->destroy = gnt_menu_destroy;
387 wid_class->draw = gnt_menu_draw;
388 wid_class->map = gnt_menu_map;
389 wid_class->size_request = gnt_menu_size_request;
390 wid_class->key_pressed = gnt_menu_key_pressed;
391 wid_class->activate = gnt_menu_activate;
392 wid_class->hide = gnt_menu_hide;
394 parent_class->toggled = gnt_menu_toggled;
396 GNTDEBUG;
399 static void
400 gnt_menu_init(GTypeInstance *instance, gpointer class)
402 GntWidget *widget = GNT_WIDGET(instance);
403 GNT_WIDGET_SET_FLAGS(widget, GNT_WIDGET_NO_SHADOW | GNT_WIDGET_NO_BORDER |
404 GNT_WIDGET_CAN_TAKE_FOCUS | GNT_WIDGET_TRANSIENT);
405 GNTDEBUG;
408 /******************************************************************************
409 * GntMenu API
410 *****************************************************************************/
411 GType
412 gnt_menu_get_gtype(void)
414 static GType type = 0;
416 if(type == 0)
418 static const GTypeInfo info = {
419 sizeof(GntMenuClass),
420 NULL, /* base_init */
421 NULL, /* base_finalize */
422 (GClassInitFunc)gnt_menu_class_init,
423 NULL, /* class_finalize */
424 NULL, /* class_data */
425 sizeof(GntMenu),
426 0, /* n_preallocs */
427 gnt_menu_init, /* instance_init */
428 NULL /* value_table */
431 type = g_type_register_static(GNT_TYPE_TREE,
432 "GntMenu",
433 &info, 0);
436 return type;
439 GntWidget *gnt_menu_new(GntMenuType type)
441 GntWidget *widget = g_object_new(GNT_TYPE_MENU, NULL);
442 GntMenu *menu = GNT_MENU(widget);
443 menu->list = NULL;
444 menu->selected = 0;
445 menu->type = type;
447 if (type == GNT_MENU_TOPLEVEL) {
448 widget->priv.x = 0;
449 widget->priv.y = 0;
450 } else {
451 GNT_TREE(widget)->show_separator = FALSE;
452 g_object_set(G_OBJECT(widget), "columns", NUM_COLUMNS, NULL);
453 gnt_tree_set_col_width(GNT_TREE(widget), ITEM_TRIGGER, 3);
454 gnt_tree_set_column_resizable(GNT_TREE(widget), ITEM_TRIGGER, FALSE);
455 gnt_tree_set_col_width(GNT_TREE(widget), ITEM_SUBMENU, 1);
456 gnt_tree_set_column_resizable(GNT_TREE(widget), ITEM_SUBMENU, FALSE);
457 GNT_WIDGET_UNSET_FLAGS(widget, GNT_WIDGET_NO_BORDER);
460 return widget;
463 void gnt_menu_add_item(GntMenu *menu, GntMenuItem *item)
465 menu->list = g_list_append(menu->list, item);
466 g_object_ref(item);
469 GntMenuItem *gnt_menu_get_item(GntMenu *menu, const char *id)
471 GntMenuItem *item = NULL;
472 GList *iter = menu->list;
474 if (!id || !*id)
475 return NULL;
477 for (; iter; iter = iter->next) {
478 GntMenu *sub;
479 item = iter->data;
480 sub = gnt_menuitem_get_submenu(item);
481 if (sub) {
482 item = gnt_menu_get_item(sub, id);
483 if (item)
484 break;
485 } else {
486 const char *itid = gnt_menuitem_get_id(item);
487 if (itid && strcmp(itid, id) == 0)
488 break;
489 /* XXX: Perhaps look at the menu-label as well? */
491 item = NULL;
494 if (item)
495 menuitem_activate(menu, item);
497 return item;