Merge branch '1858_segfault_in_search'
[midnight-commander.git] / src / menu.c
bloba64b60bf51f2442ab2d7e18f3963ebcb8db48a6e
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 "cmddef.h" /* CK_Ignore_Key */
37 #include "help.h"
38 #include "dialog.h"
39 #include "widget.h"
40 #include "main.h" /* is_right */
41 #include "strutil.h"
42 #include "menu.h"
44 int menubar_visible = 1; /* This is the new default */
46 static cb_ret_t menubar_callback (Widget *w, widget_msg_t msg, int parm);
48 menu_entry_t *
49 menu_entry_create (const char *name, unsigned long command)
51 menu_entry_t *entry;
53 entry = g_new (menu_entry_t, 1);
54 entry->first_letter = ' ';
55 entry->text = parse_hotkey (name);
56 entry->command = command;
57 entry->shortcut = NULL;
59 return entry;
62 void
63 menu_entry_free (menu_entry_t *entry)
65 if (entry != NULL) {
66 release_hotkey (entry->text);
67 g_free (entry->shortcut);
68 g_free (entry);
72 static void
73 menu_arrange (Menu *menu, dlg_shortcut_str get_shortcut)
75 if (menu != NULL) {
76 GList *i;
77 size_t max_shortcut_len = 0;
79 menu->max_entry_len = 1;
80 menu->max_hotkey_len = 1;
82 for (i = menu->entries; i != NULL; i = g_list_next (i)) {
83 menu_entry_t *entry = i->data;
85 if (entry != NULL) {
86 size_t len;
88 len = (size_t) hotkey_width (entry->text);
89 menu->max_hotkey_len = max (menu->max_hotkey_len, len);
91 if (get_shortcut != NULL)
92 entry->shortcut = get_shortcut (entry->command);
94 if (entry->shortcut != NULL) {
95 len = (size_t) str_term_width1 (entry->shortcut);
96 max_shortcut_len = max (max_shortcut_len, len);
101 menu->max_entry_len = menu->max_hotkey_len + max_shortcut_len;
105 Menu *
106 create_menu (const char *name, GList *entries, const char *help_node)
108 Menu *menu;
110 menu = g_new (Menu, 1);
111 menu->start_x = 0;
112 menu->text = parse_hotkey (name);
113 menu->entries = entries;
114 menu->max_entry_len = 1;
115 menu->max_hotkey_len = 0;
116 menu->selected = 0;
117 menu->help_node = g_strdup (help_node);
119 return menu;
122 void
123 destroy_menu (Menu *menu)
125 release_hotkey (menu->text);
126 g_list_foreach (menu->entries, (GFunc) menu_entry_free, NULL);
127 g_list_free (menu->entries);
128 g_free (menu->help_node);
129 g_free (menu);
132 static void
133 menubar_paint_idx (WMenuBar *menubar, unsigned int idx, int color)
135 const Menu *menu = g_list_nth_data (menubar->menu, menubar->selected);
136 const menu_entry_t *entry = g_list_nth_data (menu->entries, idx);
137 const int y = 2 + idx;
138 int x = menu->start_x;
140 if (x + menu->max_entry_len + 4 > (gsize) menubar->widget.cols)
141 x = menubar->widget.cols - menu->max_entry_len - 4;
143 if (entry == NULL) {
144 /* menu separator */
145 tty_setcolor (MENU_ENTRY_COLOR);
147 widget_move (&menubar->widget, y, x - 1);
148 tty_print_alt_char (ACS_LTEE);
150 tty_draw_hline (menubar->widget.y + y, menubar->widget.x + x,
151 ACS_HLINE, menu->max_entry_len + 3);
153 widget_move (&menubar->widget, y, x + menu->max_entry_len + 3);
154 tty_print_alt_char (ACS_RTEE);
155 } else {
156 /* menu text */
157 tty_setcolor (color);
158 widget_move (&menubar->widget, y, x);
159 tty_print_char ((unsigned char) entry->first_letter);
160 tty_draw_hline (-1, -1, ' ', menu->max_entry_len + 2); /* clear line */
161 tty_print_string (entry->text.start);
163 if (entry->text.hotkey != NULL) {
164 tty_setcolor (color == MENU_SELECTED_COLOR ?
165 MENU_HOTSEL_COLOR : MENU_HOT_COLOR);
166 tty_print_string (entry->text.hotkey);
167 tty_setcolor (color);
170 if (entry->text.end != NULL)
171 tty_print_string (entry->text.end);
173 if (entry->shortcut != NULL) {
174 widget_move (&menubar->widget, y, x + menu->max_hotkey_len + 3);
175 tty_print_string (entry->shortcut);
178 /* move cursor to the start of entry text */
179 widget_move (&menubar->widget, y, x + 1);
183 static void
184 menubar_draw_drop (WMenuBar *menubar)
186 const Menu *menu = g_list_nth_data (menubar->menu, menubar->selected);
187 const unsigned int count = g_list_length (menu->entries);
188 int column = menu->start_x - 1;
189 unsigned int i;
191 if (column + menu->max_entry_len + 5 > (gsize) menubar->widget.cols)
192 column = menubar->widget.cols - menu->max_entry_len - 5;
194 tty_setcolor (MENU_ENTRY_COLOR);
195 draw_box (menubar->widget.parent,
196 menubar->widget.y + 1, menubar->widget.x + column,
197 count + 2, menu->max_entry_len + 5);
199 /* draw items except selected */
200 for (i = 0; i < count; i++)
201 if (i != menu->selected)
202 menubar_paint_idx (menubar, i, MENU_ENTRY_COLOR);
204 /* draw selected item at last to move cursor to the nice location */
205 menubar_paint_idx (menubar, menu->selected, MENU_SELECTED_COLOR);
208 static void
209 menubar_set_color (WMenuBar *menubar, gboolean current, gboolean hotkey)
211 if (!menubar->is_active)
212 tty_setcolor (hotkey ? COLOR_HOT_FOCUS : SELECTED_COLOR);
213 else if (current)
214 tty_setcolor (hotkey ? MENU_HOTSEL_COLOR : MENU_SELECTED_COLOR);
215 else
216 tty_setcolor (hotkey ? MENU_HOT_COLOR : MENU_ENTRY_COLOR);
219 static void
220 menubar_draw (WMenuBar *menubar)
222 GList *i;
224 /* First draw the complete menubar */
225 tty_setcolor (menubar->is_active ? MENU_ENTRY_COLOR : SELECTED_COLOR);
226 tty_draw_hline (menubar->widget.y, menubar->widget.x, ' ', menubar->widget.cols);
228 /* Now each one of the entries */
229 for (i = menubar->menu; i != NULL; i = g_list_next (i)) {
230 Menu *menu = i->data;
231 gboolean is_selected = (menubar->selected == (gsize) g_list_position (menubar->menu, i));
233 menubar_set_color (menubar, is_selected, FALSE);
234 widget_move (&menubar->widget, 0, menu->start_x);
236 tty_print_char (' ');
237 tty_print_string (menu->text.start);
239 if (menu->text.hotkey != NULL) {
240 menubar_set_color (menubar, is_selected, TRUE);
241 tty_print_string (menu->text.hotkey);
242 menubar_set_color (menubar, is_selected, FALSE);
245 if (menu->text.end != NULL)
246 tty_print_string (menu->text.end);
248 tty_print_char (' ');
251 if (menubar->is_dropped)
252 menubar_draw_drop (menubar);
253 else
254 widget_move (&menubar->widget, 0,
255 ((Menu *) g_list_nth_data (menubar->menu,
256 menubar->selected))->start_x);
259 static void
260 menubar_remove (WMenuBar *menubar)
262 if (menubar->is_dropped) {
263 menubar->is_dropped = FALSE;
264 do_refresh ();
265 menubar->is_dropped = TRUE;
269 static void
270 menubar_left (WMenuBar *menubar)
272 menubar_remove (menubar);
273 if (menubar->selected == 0)
274 menubar->selected = g_list_length (menubar->menu) - 1;
275 else
276 menubar->selected--;
277 menubar_draw (menubar);
280 static void
281 menubar_right (WMenuBar *menubar)
283 menubar_remove (menubar);
284 menubar->selected = (menubar->selected + 1) % g_list_length (menubar->menu);
285 menubar_draw (menubar);
288 static void
289 menubar_finish (WMenuBar *menubar)
291 menubar->is_dropped = FALSE;
292 menubar->is_active = FALSE;
293 menubar->widget.lines = 1;
294 widget_want_hotkey (menubar->widget, 0);
296 dlg_select_by_id (menubar->widget.parent, menubar->previous_widget);
297 do_refresh ();
300 static void
301 menubar_drop (WMenuBar *menubar, unsigned int selected)
303 menubar->is_dropped = TRUE;
304 menubar->selected = selected;
305 menubar_draw (menubar);
308 static void
309 menubar_execute (WMenuBar *menubar)
311 const Menu *menu = g_list_nth_data (menubar->menu, menubar->selected);
312 const menu_entry_t *entry = g_list_nth_data (menu->entries, menu->selected);
314 if ((entry != NULL) && (entry->command != CK_Ignore_Key)) {
315 is_right = (menubar->selected != 0);
316 menubar_finish (menubar);
317 menubar->widget.parent->callback (menubar->widget.parent, &menubar->widget,
318 DLG_ACTION, entry->command, NULL);
319 do_refresh ();
323 static void
324 menubar_down (WMenuBar *menubar)
326 Menu *menu = g_list_nth_data (menubar->menu, menubar->selected);
327 const unsigned int len = g_list_length (menu->entries);
328 menu_entry_t *entry;
330 menubar_paint_idx (menubar, menu->selected, MENU_ENTRY_COLOR);
332 do {
333 menu->selected = (menu->selected + 1) % len;
334 entry = (menu_entry_t *) g_list_nth_data (menu->entries, menu->selected);
335 } while ((entry == NULL) || (entry->command == CK_Ignore_Key));
337 menubar_paint_idx (menubar, menu->selected, MENU_SELECTED_COLOR);
340 static void
341 menubar_up (WMenuBar *menubar)
343 Menu *menu = g_list_nth_data (menubar->menu, menubar->selected);
344 const unsigned int len = g_list_length (menu->entries);
345 menu_entry_t *entry;
347 menubar_paint_idx (menubar, menu->selected, MENU_ENTRY_COLOR);
349 do {
350 if (menu->selected == 0)
351 menu->selected = len - 1;
352 else
353 menu->selected--;
354 entry = (menu_entry_t *) g_list_nth_data (menu->entries, menu->selected);
355 } while ((entry == NULL) || (entry->command == CK_Ignore_Key));
357 menubar_paint_idx (menubar, menu->selected, MENU_SELECTED_COLOR);
360 static void
361 menubar_first (WMenuBar *menubar)
363 Menu *menu = g_list_nth_data (menubar->menu, menubar->selected);
364 menu_entry_t *entry;
366 if (menu->selected == 0)
367 return;
369 menubar_paint_idx (menubar, menu->selected, MENU_ENTRY_COLOR);
371 menu->selected = 0;
373 while (TRUE) {
374 entry = (menu_entry_t *) g_list_nth_data (menu->entries, menu->selected);
376 if ((entry == NULL) || (entry->command == CK_Ignore_Key))
377 menu->selected++;
378 else
379 break;
382 menubar_paint_idx (menubar, menu->selected, MENU_SELECTED_COLOR);
385 static void
386 menubar_last (WMenuBar *menubar)
388 Menu *menu = g_list_nth_data (menubar->menu, menubar->selected);
389 const unsigned int len = g_list_length (menu->entries);
390 menu_entry_t *entry;
392 if (menu->selected == len - 1)
393 return;
395 menubar_paint_idx (menubar, menu->selected, MENU_ENTRY_COLOR);
397 menu->selected = len;
399 do {
400 menu->selected--;
401 entry = (menu_entry_t *) g_list_nth_data (menu->entries, menu->selected);
402 } while ((entry == NULL) || (entry->command == CK_Ignore_Key));
404 menubar_paint_idx (menubar, menu->selected, MENU_SELECTED_COLOR);
407 static int
408 menubar_handle_key (WMenuBar *menubar, int key)
410 /* Lowercase */
411 if (isascii (key))
412 key = g_ascii_tolower (key);
414 if (is_abort_char (key)) {
415 menubar_finish (menubar);
416 return 1;
419 /* menubar help or menubar navigation */
420 switch (key) {
421 case KEY_F(1):
422 if (menubar->is_dropped)
423 interactive_display (NULL,
424 ((Menu *) g_list_nth_data (menubar->menu,
425 menubar->selected))->help_node);
426 else
427 interactive_display (NULL, "[Menu Bar]");
428 menubar_draw (menubar);
429 return 1;
431 case KEY_LEFT:
432 case XCTRL('b'):
433 menubar_left (menubar);
434 return 1;
436 case KEY_RIGHT:
437 case XCTRL ('f'):
438 menubar_right (menubar);
439 return 1;
442 if (!menubar->is_dropped) {
443 GList *i;
445 /* drop menu by hotkey */
446 for (i = menubar->menu; i != NULL; i = g_list_next (i)) {
447 Menu *menu = i->data;
449 if ((menu->text.hotkey != NULL)
450 && (key == g_ascii_tolower (menu->text.hotkey[0]))) {
451 menubar_drop (menubar, g_list_position (menubar->menu, i));
452 return 1;
456 /* drop menu by Enter or Dowwn key */
457 if (key == KEY_ENTER || key == XCTRL ('n')
458 || key == KEY_DOWN || key == '\n')
459 menubar_drop (menubar, menubar->selected);
461 return 1;
465 Menu *menu = g_list_nth_data (menubar->menu, menubar->selected);
466 GList *i;
468 /* execute menu command by hotkey */
469 for (i = menu->entries; i != NULL; i = g_list_next (i)) {
470 const menu_entry_t *entry = i->data;
472 if ((entry != NULL) && (entry->command != CK_Ignore_Key)
473 && (entry->text.hotkey != NULL)
474 && (key == g_ascii_tolower (entry->text.hotkey[0]))) {
475 menu->selected = g_list_position (menu->entries, i);
476 menubar_execute (menubar);
477 return 1;
481 /* menu execute by Enter or menu navigation */
482 switch (key) {
483 case KEY_ENTER:
484 case '\n':
485 menubar_execute (menubar);
486 return 1;
488 case KEY_HOME:
489 case ALT ('<'):
490 menubar_first (menubar);
491 break;
493 case KEY_END:
494 case ALT ('>'):
495 menubar_last (menubar);
496 break;
498 case KEY_DOWN:
499 case XCTRL ('n'):
500 menubar_down (menubar);
501 break;
503 case KEY_UP:
504 case XCTRL ('p'):
505 menubar_up (menubar);
506 break;
510 return 0;
513 static cb_ret_t
514 menubar_callback (Widget *w, widget_msg_t msg, int parm)
516 WMenuBar *menubar = (WMenuBar *) w;
518 switch (msg) {
519 /* We do not want the focus unless we have been activated */
520 case WIDGET_FOCUS:
521 if (!menubar->is_active)
522 return MSG_NOT_HANDLED;
524 widget_want_cursor (menubar->widget, 1);
526 /* Trick to get all the mouse events */
527 menubar->widget.lines = LINES;
529 /* Trick to get all of the hotkeys */
530 widget_want_hotkey (menubar->widget, 1);
531 menubar_draw (menubar);
532 return MSG_HANDLED;
534 /* We don't want the buttonbar to activate while using the menubar */
535 case WIDGET_HOTKEY:
536 case WIDGET_KEY:
537 if (menubar->is_active) {
538 menubar_handle_key (menubar, parm);
539 return MSG_HANDLED;
541 return MSG_NOT_HANDLED;
543 case WIDGET_CURSOR:
544 /* Put the cursor in a suitable place */
545 return MSG_NOT_HANDLED;
547 case WIDGET_UNFOCUS:
548 if (menubar->is_active)
549 return MSG_NOT_HANDLED;
551 widget_want_cursor (menubar->widget, 0);
552 return MSG_HANDLED;
554 case WIDGET_DRAW:
555 if (menubar_visible) {
556 menubar_draw (menubar);
557 return MSG_HANDLED;
559 /* fall through */
561 case WIDGET_RESIZED:
562 /* try show menu after screen resize */
563 send_message (w, WIDGET_FOCUS, 0);
564 return MSG_HANDLED;
567 case WIDGET_DESTROY:
568 menubar_set_menu (menubar, NULL);
569 return MSG_HANDLED;
571 default:
572 return default_proc (msg, parm);
576 static int
577 menubar_event (Gpm_Event *event, void *data)
579 WMenuBar *menubar = data;
580 gboolean was_active = TRUE;
581 int left_x, right_x, bottom_y;
582 Menu *menu;
584 /* ignore unsupported events */
585 if ((event->type & (GPM_UP | GPM_DOWN | GPM_DRAG)) == 0)
586 return MOU_NORMAL;
588 /* ignore wheel events if menu is inactive */
589 if (!menubar->is_active
590 && ((event->buttons & (GPM_B_MIDDLE | GPM_B_UP | GPM_B_DOWN)) != 0))
591 return MOU_NORMAL;
593 if (!menubar->is_dropped) {
594 menubar->previous_widget = menubar->widget.parent->current->dlg_id;
595 menubar->is_active = TRUE;
596 menubar->is_dropped = TRUE;
597 was_active = FALSE;
600 /* Mouse operations on the menubar */
601 if (event->y == 1 || !was_active) {
602 if ((event->type & GPM_UP) != 0)
603 return MOU_NORMAL;
605 /* wheel events on menubar */
606 if (event->buttons & GPM_B_UP)
607 menubar_left (menubar);
608 else if (event->buttons & GPM_B_DOWN)
609 menubar_right (menubar);
610 else {
611 const unsigned int len = g_list_length (menubar->menu);
612 unsigned int new_selection = 0;
614 while ((new_selection < len)
615 && (event->x > ((Menu *) g_list_nth_data (menubar->menu,
616 new_selection))->start_x))
617 new_selection++;
619 if (new_selection != 0) /* Don't set the invalid value -1 */
620 new_selection--;
622 if (!was_active) {
623 menubar->selected = new_selection;
624 dlg_select_widget (menubar);
625 } else {
626 menubar_remove (menubar);
627 menubar->selected = new_selection;
629 menubar_draw (menubar);
631 return MOU_NORMAL;
634 if (!menubar->is_dropped || (event->y < 2))
635 return MOU_NORMAL;
637 /* middle click -- everywhere */
638 if (((event->buttons & GPM_B_MIDDLE) != 0)
639 && ((event->type & GPM_DOWN) != 0)) {
640 menubar_execute (menubar);
641 return MOU_NORMAL;
644 /* the mouse operation is on the menus or it is not */
645 menu = (Menu *) g_list_nth_data (menubar->menu, menubar->selected);
646 left_x = menu->start_x;
647 right_x = left_x + menu->max_entry_len + 3;
648 if (right_x > menubar->widget.cols) {
649 left_x = menubar->widget.cols - menu->max_entry_len - 3;
650 right_x = menubar->widget.cols;
653 bottom_y = g_list_length (menu->entries) + 3;
655 if ((event->x >= left_x) && (event->x <= right_x) && (event->y <= bottom_y)){
656 int pos = event->y - 3;
657 const menu_entry_t *entry = g_list_nth_data (menu->entries, pos);
659 /* mouse wheel */
660 if ((event->buttons & GPM_B_UP) && (event->type & GPM_DOWN)) {
661 menubar_up (menubar);
662 return MOU_NORMAL;
664 if ((event->buttons & GPM_B_DOWN) && (event->type & GPM_DOWN)) {
665 menubar_down (menubar);
666 return MOU_NORMAL;
669 /* ignore events above and below dropped down menu */
670 if ((pos < 0) || (pos >= bottom_y - 3))
671 return MOU_NORMAL;
673 if ((entry != NULL) && (entry->command != CK_Ignore_Key)) {
674 menubar_paint_idx (menubar, menu->selected, MENU_ENTRY_COLOR);
675 menu->selected = pos;
676 menubar_paint_idx (menubar, menu->selected, MENU_SELECTED_COLOR);
678 if ((event->type & GPM_UP) != 0)
679 menubar_execute (menubar);
681 } else
682 /* use click not wheel to close menu */
683 if (((event->type & GPM_DOWN) != 0)
684 && ((event->buttons & (GPM_B_UP | GPM_B_DOWN)) == 0))
685 menubar_finish (menubar);
687 return MOU_NORMAL;
690 WMenuBar *
691 menubar_new (int y, int x, int cols, GList *menu)
693 WMenuBar *menubar = g_new0 (WMenuBar, 1);
695 init_widget (&menubar->widget, y, x, 1, cols,
696 menubar_callback, menubar_event);
697 widget_want_cursor (menubar->widget, 0);
698 menubar_set_menu (menubar, menu);
699 return menubar;
702 void
703 menubar_set_menu (WMenuBar *menubar, GList *menu)
705 /* delete previous menu */
706 if (menubar->menu != NULL) {
707 g_list_foreach (menubar->menu, (GFunc) destroy_menu, NULL);
708 g_list_free (menubar->menu);
710 /* add new menu */
711 menubar->is_active = FALSE;
712 menubar->is_dropped = FALSE;
713 menubar->menu = menu;
714 menubar->selected = 0;
715 menubar_arrange (menubar);
718 void
719 menubar_add_menu (WMenuBar *menubar, Menu *menu)
721 if (menu != NULL) {
722 menu_arrange (menu, menubar->widget.parent->get_shortcut);
723 menubar->menu = g_list_append (menubar->menu, menu);
726 menubar_arrange (menubar);
730 * Properly space menubar items. Should be called when menubar is created
731 * and also when widget width is changed (i.e. upon xterm resize).
733 void
734 menubar_arrange (WMenuBar* menubar)
736 int start_x = 1;
737 GList *i;
738 int gap;
740 if (menubar->menu == NULL)
741 return;
743 #ifndef RESIZABLE_MENUBAR
744 gap = 3;
746 for (i = menubar->menu; i != NULL; i = g_list_next (i)) {
747 Menu *menu = i->data;
748 int len = hotkey_width (menu->text) + 2;
750 menu->start_x = start_x;
751 start_x += len + gap;
753 #else /* RESIZABLE_MENUBAR */
754 gap = menubar->widget.cols - 2;
756 /* First, calculate gap between items... */
757 for (i = menubar->menu; i != NULL; i = g_list_nwxt (i)) {
758 Menu *menu = i->data;
759 /* preserve length here, to be used below */
760 menu->start_x = hotkey_width (menu->text) + 2;
761 gap -= menu->start_x;
764 gap /= (menubar->menu->len - 1);
766 if (gap <= 0) {
767 /* We are out of luck - window is too narrow... */
768 gap = 1;
771 /* ...and now fix start positions of menubar items */
772 for (i = menubar->menu; i != NULL; i = g_list_nwxt (i)) {
773 Menu *menu = i->data;
774 int len = menu->start_x;
776 menu->start_x = start_x;
777 start_x += len + gap;
779 #endif /* RESIZABLE_MENUBAR */
782 /* Find MenuBar widget in the dialog */
783 WMenuBar *
784 find_menubar (const Dlg_head *h)
786 return (WMenuBar *) find_widget_type (h, menubar_callback);