Use commands instead of callbacks to execute menu items.
[midnight-commander.git] / src / menu.c
blobff2b587137faf34f84faefd4efaa34564403ca6d
1 /* Copyright (C) 1994, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005,
2 2007, 2009 Free Software Foundation, Inc.
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
18 /** \file menu.c
19 * \brief Source: pulldown menu code
22 #include <config.h>
24 #include <ctype.h>
25 #include <stdarg.h>
26 #include <string.h>
27 #include <sys/types.h>
29 #include "global.h"
31 #include "../src/tty/tty.h"
32 #include "../src/skin/skin.h"
33 #include "../src/tty/mouse.h"
34 #include "../src/tty/key.h" /* key macros */
36 #include "menu.h"
37 #include "help.h"
38 #include "dialog.h"
39 #include "widget.h"
40 #include "main.h" /* is_right */
41 #include "strutil.h"
43 int menubar_visible = 1; /* This is the new default */
45 menu_entry_t *
46 menu_entry_create (const char *name, int command)
48 menu_entry_t *entry;
50 entry = g_new (menu_entry_t, 1);
51 entry->first_letter = ' ';
52 entry->text = parse_hotkey (name);
53 entry->command = command;
55 return entry;
58 void
59 menu_entry_free (menu_entry_t *entry)
61 if (entry != NULL) {
62 release_hotkey (entry->text);
63 g_free (entry);
67 static void
68 menu_arrange (Menu *menu)
70 if (menu != NULL) {
71 GList *i;
73 for (i = menu->entries; i != NULL; i = g_list_next (i)) {
74 menu_entry_t *entry = i->data;
76 if (entry != NULL) {
77 const size_t len = (size_t) hotkey_width (entry->text);
78 menu->max_entry_len = max (menu->max_entry_len, len);
84 Menu *
85 create_menu (const char *name, GList *entries, const char *help_node)
87 Menu *menu;
89 menu = g_new (Menu, 1);
90 menu->start_x = 0;
91 menu->text = parse_hotkey (name);
92 menu->entries = entries;
93 menu->max_entry_len = 1;
94 menu->selected = 0;
95 menu->help_node = g_strdup (help_node);
96 menu_arrange (menu);
98 return menu;
101 void
102 destroy_menu (Menu *menu)
104 release_hotkey (menu->text);
105 g_list_foreach (menu->entries, (GFunc) menu_entry_free, NULL);
106 g_list_free (menu->entries);
107 g_free (menu->help_node);
108 g_free (menu);
111 static void
112 menubar_paint_idx (WMenuBar *menubar, unsigned int idx, int color)
114 const Menu *menu = g_list_nth_data (menubar->menu, menubar->selected);
115 const menu_entry_t *entry = g_list_nth_data (menu->entries, idx);
116 const int y = 2 + idx;
117 int x = menu->start_x;
119 if (x + menu->max_entry_len + 3 > menubar->widget.cols)
120 x = menubar->widget.cols - menu->max_entry_len - 3;
122 if (entry == NULL) {
123 /* menu separator */
124 tty_setcolor (MENU_ENTRY_COLOR);
126 widget_move (&menubar->widget, y, x - 1);
127 tty_print_alt_char (ACS_LTEE);
129 tty_draw_hline (menubar->widget.y + y, menubar->widget.x + x,
130 ACS_HLINE, menu->max_entry_len + 2);
132 widget_move (&menubar->widget, y, x + menu->max_entry_len + 2);
133 tty_print_alt_char (ACS_RTEE);
134 } else {
135 /* menu text */
136 tty_setcolor (color);
137 widget_move (&menubar->widget, y, x);
138 tty_print_char ((unsigned char) entry->first_letter);
139 tty_draw_hline (-1, -1, ' ', menu->max_entry_len + 1); /* clear line */
140 tty_print_string (entry->text.start);
142 if (entry->text.hotkey != NULL) {
143 tty_setcolor (color == MENU_SELECTED_COLOR ?
144 MENU_HOTSEL_COLOR : MENU_HOT_COLOR);
145 tty_print_string (entry->text.hotkey);
146 tty_setcolor (color);
149 if (entry->text.end != NULL)
150 tty_print_string (entry->text.end);
152 /* move cursor to the start of entry text */
153 widget_move (&menubar->widget, y, x + 1);
157 static void
158 menubar_draw_drop (WMenuBar *menubar)
160 const Menu *menu = g_list_nth_data (menubar->menu, menubar->selected);
161 const unsigned int count = g_list_length (menu->entries);
162 int column = menu->start_x - 1;
163 unsigned int i;
165 if (column + menu->max_entry_len + 4 > menubar->widget.cols)
166 column = menubar->widget.cols - menu->max_entry_len - 4;
168 tty_setcolor (MENU_ENTRY_COLOR);
169 draw_box (menubar->widget.parent,
170 menubar->widget.y + 1, menubar->widget.x + column,
171 count + 2, menu->max_entry_len + 4);
173 /* draw items except selected */
174 for (i = 0; i < count; i++)
175 if (i != menu->selected)
176 menubar_paint_idx (menubar, i, MENU_ENTRY_COLOR);
178 /* draw selected item at last to move cursor to the nice location */
179 menubar_paint_idx (menubar, menu->selected, MENU_SELECTED_COLOR);
182 static void
183 menubar_set_color (WMenuBar *menubar, gboolean current, gboolean hotkey)
185 if (!menubar->is_active)
186 tty_setcolor (hotkey ? COLOR_HOT_FOCUS : SELECTED_COLOR);
187 else if (current)
188 tty_setcolor (hotkey ? MENU_HOTSEL_COLOR : MENU_SELECTED_COLOR);
189 else
190 tty_setcolor (hotkey ? MENU_HOT_COLOR : MENU_ENTRY_COLOR);
193 static void
194 menubar_draw (WMenuBar *menubar)
196 GList *i;
198 /* First draw the complete menubar */
199 tty_setcolor (menubar->is_active ? MENU_ENTRY_COLOR : SELECTED_COLOR);
200 tty_draw_hline (menubar->widget.y, menubar->widget.x, ' ', menubar->widget.cols);
202 /* Now each one of the entries */
203 for (i = menubar->menu; i != NULL; i = g_list_next (i)) {
204 Menu *menu = i->data;
205 gboolean is_selected = (menubar->selected == g_list_position (menubar->menu, i));
207 menubar_set_color (menubar, is_selected, FALSE);
208 widget_move (&menubar->widget, 0, menu->start_x);
210 tty_print_string (menu->text.start);
212 if (menu->text.hotkey != NULL) {
213 menubar_set_color (menubar, is_selected, TRUE);
214 tty_print_string (menu->text.hotkey);
215 menubar_set_color (menubar, is_selected, FALSE);
218 if (menu->text.end != NULL)
219 tty_print_string (menu->text.end);
222 if (menubar->is_dropped)
223 menubar_draw_drop (menubar);
224 else
225 widget_move (&menubar->widget, 0,
226 ((Menu *) g_list_nth_data (menubar->menu,
227 menubar->selected))->start_x);
230 static void
231 menubar_remove (WMenuBar *menubar)
233 if (menubar->is_dropped) {
234 menubar->is_dropped = FALSE;
235 do_refresh ();
236 menubar->is_dropped = TRUE;
240 static void
241 menubar_left (WMenuBar *menubar)
243 menubar_remove (menubar);
244 if (menubar->selected == 0)
245 menubar->selected = g_list_length (menubar->menu) - 1;
246 else
247 menubar->selected--;
248 menubar_draw (menubar);
251 static void
252 menubar_right (WMenuBar *menubar)
254 menubar_remove (menubar);
255 menubar->selected = (menubar->selected + 1) % g_list_length (menubar->menu);
256 menubar_draw (menubar);
259 static void
260 menubar_finish (WMenuBar *menubar)
262 menubar->is_dropped = FALSE;
263 menubar->is_active = FALSE;
264 menubar->widget.lines = 1;
265 widget_want_hotkey (menubar->widget, 0);
267 dlg_select_by_id (menubar->widget.parent, menubar->previous_widget);
268 do_refresh ();
271 static void
272 menubar_drop (WMenuBar *menubar, unsigned int selected)
274 menubar->is_dropped = TRUE;
275 menubar->selected = selected;
276 menubar_draw (menubar);
279 static void
280 menubar_execute (WMenuBar *menubar)
282 const Menu *menu = g_list_nth_data (menubar->menu, menubar->selected);
283 const menu_entry_t *entry = g_list_nth_data (menu->entries, menu->selected);
285 if ((entry != NULL) && (entry->command != 0)) {
286 is_right = (menubar->selected != 0);
287 menubar_finish (menubar);
288 menubar->widget.parent->menu_executor (entry->command);
289 do_refresh ();
293 static void
294 menubar_down (WMenuBar *menubar)
296 Menu *menu = g_list_nth_data (menubar->menu, menubar->selected);
297 const unsigned int len = g_list_length (menu->entries);
298 menu_entry_t *entry;
300 menubar_paint_idx (menubar, menu->selected, MENU_ENTRY_COLOR);
302 do {
303 menu->selected = (menu->selected + 1) % len;
304 entry = (menu_entry_t *) g_list_nth_data (menu->entries, menu->selected);
305 } while ((entry == NULL) || (entry->command == 0));
307 menubar_paint_idx (menubar, menu->selected, MENU_SELECTED_COLOR);
310 static void
311 menubar_up (WMenuBar *menubar)
313 Menu *menu = g_list_nth_data (menubar->menu, menubar->selected);
314 const unsigned int len = g_list_length (menu->entries);
315 menu_entry_t *entry;
317 menubar_paint_idx (menubar, menu->selected, MENU_ENTRY_COLOR);
319 do {
320 if (menu->selected == 0)
321 menu->selected = len - 1;
322 else
323 menu->selected--;
324 entry = (menu_entry_t *) g_list_nth_data (menu->entries, menu->selected);
325 } while ((entry == NULL) || (entry->command == 0));
327 menubar_paint_idx (menubar, menu->selected, MENU_SELECTED_COLOR);
330 static void
331 menubar_first (WMenuBar *menubar)
333 Menu *menu = g_list_nth_data (menubar->menu, menubar->selected);
334 menu_entry_t *entry;
336 if (menu->selected == 0)
337 return;
339 menubar_paint_idx (menubar, menu->selected, MENU_ENTRY_COLOR);
341 menu->selected = 0;
343 while (TRUE) {
344 entry = (menu_entry_t *) g_list_nth_data (menu->entries, menu->selected);
346 if ((entry == NULL) || (entry->command == 0))
347 menu->selected++;
348 else
349 break;
352 menubar_paint_idx (menubar, menu->selected, MENU_SELECTED_COLOR);
355 static void
356 menubar_last (WMenuBar *menubar)
358 Menu *menu = g_list_nth_data (menubar->menu, menubar->selected);
359 const unsigned int len = g_list_length (menu->entries);
360 menu_entry_t *entry;
362 if (menu->selected == len - 1)
363 return;
365 menubar_paint_idx (menubar, menu->selected, MENU_ENTRY_COLOR);
367 menu->selected = len;
369 do {
370 menu->selected--;
371 entry = (menu_entry_t *) g_list_nth_data (menu->entries, menu->selected);
372 } while ((entry == NULL) || (entry->command == 0));
374 menubar_paint_idx (menubar, menu->selected, MENU_SELECTED_COLOR);
377 static int
378 menubar_handle_key (WMenuBar *menubar, int key)
380 /* Lowercase */
381 if (isascii (key))
382 key = g_ascii_tolower (key);
384 if (is_abort_char (key)) {
385 menubar_finish (menubar);
386 return 1;
389 /* menubar help or menubar navigation */
390 switch (key) {
391 case KEY_F(1):
392 if (menubar->is_dropped)
393 interactive_display (NULL,
394 ((Menu *) g_list_nth_data (menubar->menu,
395 menubar->selected))->help_node);
396 else
397 interactive_display (NULL, "[Menu Bar]");
398 menubar_draw (menubar);
399 return 1;
401 case KEY_LEFT:
402 case XCTRL('b'):
403 menubar_left (menubar);
404 return 1;
406 case KEY_RIGHT:
407 case XCTRL ('f'):
408 menubar_right (menubar);
409 return 1;
412 if (!menubar->is_dropped) {
413 GList *i;
415 /* drop menu by hotkey */
416 for (i = menubar->menu; i != NULL; i = g_list_next (i)) {
417 Menu *menu = i->data;
419 if ((menu->text.hotkey != NULL)
420 && (key == g_ascii_tolower (menu->text.hotkey[0]))) {
421 menubar_drop (menubar, g_list_position (menubar->menu, i));
422 return 1;
426 /* drop menu by Enter or Dowwn key */
427 if (key == KEY_ENTER || key == XCTRL ('n')
428 || key == KEY_DOWN || key == '\n')
429 menubar_drop (menubar, menubar->selected);
431 return 1;
435 Menu *menu = g_list_nth_data (menubar->menu, menubar->selected);
436 GList *i;
438 /* execute menu command by hotkey */
439 for (i = menu->entries; i != NULL; i = g_list_next (i)) {
440 const menu_entry_t *entry = i->data;
442 if ((entry != NULL) && (entry->command != 0)
443 && (entry->text.hotkey != NULL)
444 && (key == g_ascii_tolower (entry->text.hotkey[0]))) {
445 menu->selected = g_list_position (menu->entries, i);
446 menubar_execute (menubar);
447 return 1;
451 /* menu execute by Enter or menu navigation */
452 switch (key) {
453 case KEY_ENTER:
454 case '\n':
455 menubar_execute (menubar);
456 return 1;
458 case KEY_HOME:
459 case ALT ('<'):
460 menubar_first (menubar);
461 break;
463 case KEY_END:
464 case ALT ('>'):
465 menubar_last (menubar);
466 break;
468 case KEY_DOWN:
469 case XCTRL ('n'):
470 menubar_down (menubar);
471 break;
473 case KEY_UP:
474 case XCTRL ('p'):
475 menubar_up (menubar);
476 break;
480 return 0;
483 static cb_ret_t
484 menubar_callback (Widget *w, widget_msg_t msg, int parm)
486 WMenuBar *menubar = (WMenuBar *) w;
488 switch (msg) {
489 /* We do not want the focus unless we have been activated */
490 case WIDGET_FOCUS:
491 if (!menubar->is_active)
492 return MSG_NOT_HANDLED;
494 widget_want_cursor (menubar->widget, 1);
496 /* Trick to get all the mouse events */
497 menubar->widget.lines = LINES;
499 /* Trick to get all of the hotkeys */
500 widget_want_hotkey (menubar->widget, 1);
501 menubar_draw (menubar);
502 return MSG_HANDLED;
504 /* We don't want the buttonbar to activate while using the menubar */
505 case WIDGET_HOTKEY:
506 case WIDGET_KEY:
507 if (menubar->is_active) {
508 menubar_handle_key (menubar, parm);
509 return MSG_HANDLED;
511 return MSG_NOT_HANDLED;
513 case WIDGET_CURSOR:
514 /* Put the cursor in a suitable place */
515 return MSG_NOT_HANDLED;
517 case WIDGET_UNFOCUS:
518 if (menubar->is_active)
519 return MSG_NOT_HANDLED;
521 widget_want_cursor (menubar->widget, 0);
522 return MSG_HANDLED;
524 case WIDGET_DRAW:
525 if (menubar_visible) {
526 menubar_draw (menubar);
527 return MSG_HANDLED;
529 /* fall through */
531 case WIDGET_RESIZED:
532 /* try show menu after screen resize */
533 send_message (w, WIDGET_FOCUS, 0);
534 return MSG_HANDLED;
537 case WIDGET_DESTROY:
538 menubar_set_menu (menubar, NULL);
539 return MSG_HANDLED;
541 default:
542 return default_proc (msg, parm);
546 static int
547 menubar_event (Gpm_Event *event, void *data)
549 WMenuBar *menubar = data;
550 gboolean was_active = TRUE;
551 int left_x, right_x, bottom_y;
552 Menu *menu;
554 /* ignore unsupported events */
555 if ((event->type & (GPM_UP | GPM_DOWN | GPM_DRAG)) == 0)
556 return MOU_NORMAL;
558 /* ignore wheel events if menu is inactive */
559 if (!menubar->is_active
560 && ((event->buttons & (GPM_B_MIDDLE | GPM_B_UP | GPM_B_DOWN)) != 0))
561 return MOU_NORMAL;
563 if (!menubar->is_dropped) {
564 menubar->previous_widget = menubar->widget.parent->current->dlg_id;
565 menubar->is_active = TRUE;
566 menubar->is_dropped = TRUE;
567 was_active = FALSE;
570 /* Mouse operations on the menubar */
571 if (event->y == 1 || !was_active) {
572 if ((event->type & GPM_UP) != 0)
573 return MOU_NORMAL;
575 /* wheel events on menubar */
576 if (event->buttons & GPM_B_UP)
577 menubar_left (menubar);
578 else if (event->buttons & GPM_B_DOWN)
579 menubar_right (menubar);
580 else {
581 const unsigned int len = g_list_length (menubar->menu);
582 int new_selection = 0;
584 while ((new_selection < len)
585 && (event->x > ((Menu *) g_list_nth_data (menubar->menu,
586 new_selection))->start_x))
587 new_selection++;
589 if (new_selection != 0) /* Don't set the invalid value -1 */
590 new_selection--;
592 if (!was_active) {
593 menubar->selected = new_selection;
594 dlg_select_widget (menubar);
595 } else {
596 menubar_remove (menubar);
597 menubar->selected = new_selection;
599 menubar_draw (menubar);
601 return MOU_NORMAL;
604 if (!menubar->is_dropped || (event->y < 2))
605 return MOU_NORMAL;
607 /* middle click -- everywhere */
608 if (((event->buttons & GPM_B_MIDDLE) != 0)
609 && ((event->type & GPM_DOWN) != 0)) {
610 menubar_execute (menubar);
611 return MOU_NORMAL;
614 /* the mouse operation is on the menus or it is not */
615 menu = (Menu *) g_list_nth_data (menubar->menu, menubar->selected);
616 left_x = menu->start_x;
617 right_x = left_x + menu->max_entry_len + 3;
618 if (right_x > menubar->widget.cols) {
619 left_x = menubar->widget.cols - menu->max_entry_len - 3;
620 right_x = menubar->widget.cols;
623 bottom_y = g_list_length (menu->entries) + 3;
625 if ((event->x >= left_x) && (event->x <= right_x) && (event->y <= bottom_y)){
626 int pos = event->y - 3;
627 const menu_entry_t *entry = g_list_nth_data (menu->entries, pos);
629 /* mouse wheel */
630 if ((event->buttons & GPM_B_UP) && (event->type & GPM_DOWN)) {
631 menubar_up (menubar);
632 return MOU_NORMAL;
634 if ((event->buttons & GPM_B_DOWN) && (event->type & GPM_DOWN)) {
635 menubar_down (menubar);
636 return MOU_NORMAL;
639 /* ignore events above and below dropped down menu */
640 if ((pos < 0) || (pos >= bottom_y - 3))
641 return MOU_NORMAL;
643 if ((entry != NULL) && (entry->command != 0)) {
644 menubar_paint_idx (menubar, menu->selected, MENU_ENTRY_COLOR);
645 menu->selected = pos;
646 menubar_paint_idx (menubar, menu->selected, MENU_SELECTED_COLOR);
648 if ((event->type & GPM_UP) != 0)
649 menubar_execute (menubar);
651 } else
652 /* use click not wheel to close menu */
653 if (((event->type & GPM_DOWN) != 0)
654 && ((event->buttons & (GPM_B_UP | GPM_B_DOWN)) == 0))
655 menubar_finish (menubar);
657 return MOU_NORMAL;
660 WMenuBar *
661 menubar_new (int y, int x, int cols, GList *menu)
663 WMenuBar *menubar = g_new0 (WMenuBar, 1);
665 init_widget (&menubar->widget, y, x, 1, cols,
666 menubar_callback, menubar_event);
667 widget_want_cursor (menubar->widget, 0);
668 menubar_set_menu (menubar, menu);
669 return menubar;
672 void
673 menubar_set_menu (WMenuBar *menubar, GList *menu)
675 /* delete previous menu */
676 if (menubar->menu != NULL) {
677 g_list_foreach (menubar->menu, (GFunc) destroy_menu, NULL);
678 g_list_free (menubar->menu);
680 /* add new menu */
681 menubar->is_active = FALSE;
682 menubar->is_dropped = FALSE;
683 menubar->menu = menu;
684 menubar->selected = 0;
685 menubar_arrange (menubar);
688 void
689 menubar_add_menu (WMenuBar *menubar, Menu *menu)
691 if (menu != NULL)
692 menubar->menu = g_list_append (menubar->menu, menu);
694 menubar_arrange (menubar);
698 * Properly space menubar items. Should be called when menubar is created
699 * and also when widget width is changed (i.e. upon xterm resize).
701 void
702 menubar_arrange (WMenuBar* menubar)
704 int start_x = 1;
705 GList *i;
706 int gap;
708 if (menubar->menu == NULL)
709 return;
711 #ifndef RESIZABLE_MENUBAR
712 gap = 3;
714 for (i = menubar->menu; i != NULL; i = g_list_next (i)) {
715 Menu *menu = i->data;
716 int len = hotkey_width (menu->text);
718 menu->start_x = start_x;
719 start_x += len + gap;
721 #else /* RESIZABLE_MENUBAR */
722 gap = menubar->widget.cols - 2;
724 /* First, calculate gap between items... */
725 for (i = menubar->menu; i != NULL; i = g_list_nwxt (i)) {
726 Menu *menu = i->data;
727 /* preserve length here, to be used below */
728 menu->start_x = hotkey_width (menu->text);
729 gap -= menu->start_x;
732 gap /= (menubar->menu->len - 1);
734 if (gap <= 0) {
735 /* We are out of luck - window is too narrow... */
736 gap = 1;
739 /* ...and now fix start positions of menubar items */
740 for (i = menubar->menu; i != NULL; i = g_list_nwxt (i)) {
741 Menu *menu = i->data;
742 int len = menu->start_x;
744 menu->start_x = start_x;
745 start_x += len + gap;
747 #endif /* RESIZABLE_MENUBAR */