lib/Makefile.am: added lib/skin.h into distribution
[midnight-commander.git] / src / menu.c
blobade349f465747a961b4b2770938394efa84cd53d
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 "lib/global.h"
31 #include "lib/tty/tty.h"
32 #include "lib/skin.h"
33 #include "lib/tty/mouse.h"
34 #include "lib/tty/key.h" /* key macros */
35 #include "lib/strutil.h"
37 #include "cmddef.h" /* CK_Ignore_Key */
38 #include "help.h"
39 #include "dialog.h"
40 #include "widget.h"
41 #include "main.h" /* is_right */
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 || !current)
212 tty_setcolor (hotkey ? MENU_HOT_COLOR : MENU_ENTRY_COLOR);
213 else
214 tty_setcolor (hotkey ? MENU_HOTSEL_COLOR : MENU_SELECTED_COLOR);
217 static void
218 menubar_draw (WMenuBar *menubar)
220 GList *i;
222 /* First draw the complete menubar */
223 tty_setcolor (MENU_ENTRY_COLOR);
224 tty_draw_hline (menubar->widget.y, menubar->widget.x, ' ', menubar->widget.cols);
226 /* Now each one of the entries */
227 for (i = menubar->menu; i != NULL; i = g_list_next (i)) {
228 Menu *menu = i->data;
229 gboolean is_selected = (menubar->selected == (gsize) g_list_position (menubar->menu, i));
231 menubar_set_color (menubar, is_selected, FALSE);
232 widget_move (&menubar->widget, 0, menu->start_x);
234 tty_print_char (' ');
235 tty_print_string (menu->text.start);
237 if (menu->text.hotkey != NULL) {
238 menubar_set_color (menubar, is_selected, TRUE);
239 tty_print_string (menu->text.hotkey);
240 menubar_set_color (menubar, is_selected, FALSE);
243 if (menu->text.end != NULL)
244 tty_print_string (menu->text.end);
246 tty_print_char (' ');
249 if (menubar->is_dropped)
250 menubar_draw_drop (menubar);
251 else
252 widget_move (&menubar->widget, 0,
253 ((Menu *) g_list_nth_data (menubar->menu,
254 menubar->selected))->start_x);
257 static void
258 menubar_remove (WMenuBar *menubar)
260 if (menubar->is_dropped) {
261 menubar->is_dropped = FALSE;
262 do_refresh ();
263 menubar->is_dropped = TRUE;
267 static void
268 menubar_left (WMenuBar *menubar)
270 menubar_remove (menubar);
271 if (menubar->selected == 0)
272 menubar->selected = g_list_length (menubar->menu) - 1;
273 else
274 menubar->selected--;
275 menubar_draw (menubar);
278 static void
279 menubar_right (WMenuBar *menubar)
281 menubar_remove (menubar);
282 menubar->selected = (menubar->selected + 1) % g_list_length (menubar->menu);
283 menubar_draw (menubar);
286 static void
287 menubar_finish (WMenuBar *menubar)
289 menubar->is_dropped = FALSE;
290 menubar->is_active = FALSE;
291 menubar->widget.lines = 1;
292 widget_want_hotkey (menubar->widget, 0);
294 dlg_select_by_id (menubar->widget.parent, menubar->previous_widget);
295 do_refresh ();
298 static void
299 menubar_drop (WMenuBar *menubar, unsigned int selected)
301 menubar->is_dropped = TRUE;
302 menubar->selected = selected;
303 menubar_draw (menubar);
306 static void
307 menubar_execute (WMenuBar *menubar)
309 const Menu *menu = g_list_nth_data (menubar->menu, menubar->selected);
310 const menu_entry_t *entry = g_list_nth_data (menu->entries, menu->selected);
312 if ((entry != NULL) && (entry->command != CK_Ignore_Key)) {
313 is_right = (menubar->selected != 0);
314 menubar_finish (menubar);
315 menubar->widget.parent->callback (menubar->widget.parent, &menubar->widget,
316 DLG_ACTION, entry->command, NULL);
317 do_refresh ();
321 static void
322 menubar_down (WMenuBar *menubar)
324 Menu *menu = g_list_nth_data (menubar->menu, menubar->selected);
325 const unsigned int len = g_list_length (menu->entries);
326 menu_entry_t *entry;
328 menubar_paint_idx (menubar, menu->selected, MENU_ENTRY_COLOR);
330 do {
331 menu->selected = (menu->selected + 1) % len;
332 entry = (menu_entry_t *) g_list_nth_data (menu->entries, menu->selected);
333 } while ((entry == NULL) || (entry->command == CK_Ignore_Key));
335 menubar_paint_idx (menubar, menu->selected, MENU_SELECTED_COLOR);
338 static void
339 menubar_up (WMenuBar *menubar)
341 Menu *menu = g_list_nth_data (menubar->menu, menubar->selected);
342 const unsigned int len = g_list_length (menu->entries);
343 menu_entry_t *entry;
345 menubar_paint_idx (menubar, menu->selected, MENU_ENTRY_COLOR);
347 do {
348 if (menu->selected == 0)
349 menu->selected = len - 1;
350 else
351 menu->selected--;
352 entry = (menu_entry_t *) g_list_nth_data (menu->entries, menu->selected);
353 } while ((entry == NULL) || (entry->command == CK_Ignore_Key));
355 menubar_paint_idx (menubar, menu->selected, MENU_SELECTED_COLOR);
358 static void
359 menubar_first (WMenuBar *menubar)
361 Menu *menu = g_list_nth_data (menubar->menu, menubar->selected);
362 menu_entry_t *entry;
364 if (menu->selected == 0)
365 return;
367 menubar_paint_idx (menubar, menu->selected, MENU_ENTRY_COLOR);
369 menu->selected = 0;
371 while (TRUE) {
372 entry = (menu_entry_t *) g_list_nth_data (menu->entries, menu->selected);
374 if ((entry == NULL) || (entry->command == CK_Ignore_Key))
375 menu->selected++;
376 else
377 break;
380 menubar_paint_idx (menubar, menu->selected, MENU_SELECTED_COLOR);
383 static void
384 menubar_last (WMenuBar *menubar)
386 Menu *menu = g_list_nth_data (menubar->menu, menubar->selected);
387 const unsigned int len = g_list_length (menu->entries);
388 menu_entry_t *entry;
390 if (menu->selected == len - 1)
391 return;
393 menubar_paint_idx (menubar, menu->selected, MENU_ENTRY_COLOR);
395 menu->selected = len;
397 do {
398 menu->selected--;
399 entry = (menu_entry_t *) g_list_nth_data (menu->entries, menu->selected);
400 } while ((entry == NULL) || (entry->command == CK_Ignore_Key));
402 menubar_paint_idx (menubar, menu->selected, MENU_SELECTED_COLOR);
405 static int
406 menubar_handle_key (WMenuBar *menubar, int key)
408 /* Lowercase */
409 if (isascii (key))
410 key = g_ascii_tolower (key);
412 if (is_abort_char (key)) {
413 menubar_finish (menubar);
414 return 1;
417 /* menubar help or menubar navigation */
418 switch (key) {
419 case KEY_F(1):
420 if (menubar->is_dropped)
421 interactive_display (NULL,
422 ((Menu *) g_list_nth_data (menubar->menu,
423 menubar->selected))->help_node);
424 else
425 interactive_display (NULL, "[Menu Bar]");
426 menubar_draw (menubar);
427 return 1;
429 case KEY_LEFT:
430 case XCTRL('b'):
431 menubar_left (menubar);
432 return 1;
434 case KEY_RIGHT:
435 case XCTRL ('f'):
436 menubar_right (menubar);
437 return 1;
440 if (!menubar->is_dropped) {
441 GList *i;
443 /* drop menu by hotkey */
444 for (i = menubar->menu; i != NULL; i = g_list_next (i)) {
445 Menu *menu = i->data;
447 if ((menu->text.hotkey != NULL)
448 && (key == g_ascii_tolower (menu->text.hotkey[0]))) {
449 menubar_drop (menubar, g_list_position (menubar->menu, i));
450 return 1;
454 /* drop menu by Enter or Dowwn key */
455 if (key == KEY_ENTER || key == XCTRL ('n')
456 || key == KEY_DOWN || key == '\n')
457 menubar_drop (menubar, menubar->selected);
459 return 1;
463 Menu *menu = g_list_nth_data (menubar->menu, menubar->selected);
464 GList *i;
466 /* execute menu command by hotkey */
467 for (i = menu->entries; i != NULL; i = g_list_next (i)) {
468 const menu_entry_t *entry = i->data;
470 if ((entry != NULL) && (entry->command != CK_Ignore_Key)
471 && (entry->text.hotkey != NULL)
472 && (key == g_ascii_tolower (entry->text.hotkey[0]))) {
473 menu->selected = g_list_position (menu->entries, i);
474 menubar_execute (menubar);
475 return 1;
479 /* menu execute by Enter or menu navigation */
480 switch (key) {
481 case KEY_ENTER:
482 case '\n':
483 menubar_execute (menubar);
484 return 1;
486 case KEY_HOME:
487 case ALT ('<'):
488 menubar_first (menubar);
489 break;
491 case KEY_END:
492 case ALT ('>'):
493 menubar_last (menubar);
494 break;
496 case KEY_DOWN:
497 case XCTRL ('n'):
498 menubar_down (menubar);
499 break;
501 case KEY_UP:
502 case XCTRL ('p'):
503 menubar_up (menubar);
504 break;
508 return 0;
511 static cb_ret_t
512 menubar_callback (Widget *w, widget_msg_t msg, int parm)
514 WMenuBar *menubar = (WMenuBar *) w;
516 switch (msg) {
517 /* We do not want the focus unless we have been activated */
518 case WIDGET_FOCUS:
519 if (!menubar->is_active)
520 return MSG_NOT_HANDLED;
522 widget_want_cursor (menubar->widget, 1);
524 /* Trick to get all the mouse events */
525 menubar->widget.lines = LINES;
527 /* Trick to get all of the hotkeys */
528 widget_want_hotkey (menubar->widget, 1);
529 menubar_draw (menubar);
530 return MSG_HANDLED;
532 /* We don't want the buttonbar to activate while using the menubar */
533 case WIDGET_HOTKEY:
534 case WIDGET_KEY:
535 if (menubar->is_active) {
536 menubar_handle_key (menubar, parm);
537 return MSG_HANDLED;
539 return MSG_NOT_HANDLED;
541 case WIDGET_CURSOR:
542 /* Put the cursor in a suitable place */
543 return MSG_NOT_HANDLED;
545 case WIDGET_UNFOCUS:
546 if (menubar->is_active)
547 return MSG_NOT_HANDLED;
549 widget_want_cursor (menubar->widget, 0);
550 return MSG_HANDLED;
552 case WIDGET_DRAW:
553 if (menubar_visible) {
554 menubar_draw (menubar);
555 return MSG_HANDLED;
557 /* fall through */
559 case WIDGET_RESIZED:
560 /* try show menu after screen resize */
561 send_message (w, WIDGET_FOCUS, 0);
562 return MSG_HANDLED;
565 case WIDGET_DESTROY:
566 menubar_set_menu (menubar, NULL);
567 return MSG_HANDLED;
569 default:
570 return default_proc (msg, parm);
574 static int
575 menubar_event (Gpm_Event *event, void *data)
577 WMenuBar *menubar = data;
578 gboolean was_active = TRUE;
579 int left_x, right_x, bottom_y;
580 Menu *menu;
582 /* ignore unsupported events */
583 if ((event->type & (GPM_UP | GPM_DOWN | GPM_DRAG)) == 0)
584 return MOU_NORMAL;
586 /* ignore wheel events if menu is inactive */
587 if (!menubar->is_active
588 && ((event->buttons & (GPM_B_MIDDLE | GPM_B_UP | GPM_B_DOWN)) != 0))
589 return MOU_NORMAL;
591 if (!menubar->is_dropped) {
592 menubar->previous_widget = menubar->widget.parent->current->dlg_id;
593 menubar->is_active = TRUE;
594 menubar->is_dropped = TRUE;
595 was_active = FALSE;
598 /* Mouse operations on the menubar */
599 if (event->y == 1 || !was_active) {
600 if ((event->type & GPM_UP) != 0)
601 return MOU_NORMAL;
603 /* wheel events on menubar */
604 if (event->buttons & GPM_B_UP)
605 menubar_left (menubar);
606 else if (event->buttons & GPM_B_DOWN)
607 menubar_right (menubar);
608 else {
609 const unsigned int len = g_list_length (menubar->menu);
610 unsigned int new_selection = 0;
612 while ((new_selection < len)
613 && (event->x > ((Menu *) g_list_nth_data (menubar->menu,
614 new_selection))->start_x))
615 new_selection++;
617 if (new_selection != 0) /* Don't set the invalid value -1 */
618 new_selection--;
620 if (!was_active) {
621 menubar->selected = new_selection;
622 dlg_select_widget (menubar);
623 } else {
624 menubar_remove (menubar);
625 menubar->selected = new_selection;
627 menubar_draw (menubar);
629 return MOU_NORMAL;
632 if (!menubar->is_dropped || (event->y < 2))
633 return MOU_NORMAL;
635 /* middle click -- everywhere */
636 if (((event->buttons & GPM_B_MIDDLE) != 0)
637 && ((event->type & GPM_DOWN) != 0)) {
638 menubar_execute (menubar);
639 return MOU_NORMAL;
642 /* the mouse operation is on the menus or it is not */
643 menu = (Menu *) g_list_nth_data (menubar->menu, menubar->selected);
644 left_x = menu->start_x;
645 right_x = left_x + menu->max_entry_len + 3;
646 if (right_x > menubar->widget.cols) {
647 left_x = menubar->widget.cols - menu->max_entry_len - 3;
648 right_x = menubar->widget.cols;
651 bottom_y = g_list_length (menu->entries) + 3;
653 if ((event->x >= left_x) && (event->x <= right_x) && (event->y <= bottom_y)){
654 int pos = event->y - 3;
655 const menu_entry_t *entry = g_list_nth_data (menu->entries, pos);
657 /* mouse wheel */
658 if ((event->buttons & GPM_B_UP) && (event->type & GPM_DOWN)) {
659 menubar_up (menubar);
660 return MOU_NORMAL;
662 if ((event->buttons & GPM_B_DOWN) && (event->type & GPM_DOWN)) {
663 menubar_down (menubar);
664 return MOU_NORMAL;
667 /* ignore events above and below dropped down menu */
668 if ((pos < 0) || (pos >= bottom_y - 3))
669 return MOU_NORMAL;
671 if ((entry != NULL) && (entry->command != CK_Ignore_Key)) {
672 menubar_paint_idx (menubar, menu->selected, MENU_ENTRY_COLOR);
673 menu->selected = pos;
674 menubar_paint_idx (menubar, menu->selected, MENU_SELECTED_COLOR);
676 if ((event->type & GPM_UP) != 0)
677 menubar_execute (menubar);
679 } else
680 /* use click not wheel to close menu */
681 if (((event->type & GPM_DOWN) != 0)
682 && ((event->buttons & (GPM_B_UP | GPM_B_DOWN)) == 0))
683 menubar_finish (menubar);
685 return MOU_NORMAL;
688 WMenuBar *
689 menubar_new (int y, int x, int cols, GList *menu)
691 WMenuBar *menubar = g_new0 (WMenuBar, 1);
693 init_widget (&menubar->widget, y, x, 1, cols,
694 menubar_callback, menubar_event);
695 widget_want_cursor (menubar->widget, 0);
696 menubar_set_menu (menubar, menu);
697 return menubar;
700 void
701 menubar_set_menu (WMenuBar *menubar, GList *menu)
703 /* delete previous menu */
704 if (menubar->menu != NULL) {
705 g_list_foreach (menubar->menu, (GFunc) destroy_menu, NULL);
706 g_list_free (menubar->menu);
708 /* add new menu */
709 menubar->is_active = FALSE;
710 menubar->is_dropped = FALSE;
711 menubar->menu = menu;
712 menubar->selected = 0;
713 menubar_arrange (menubar);
716 void
717 menubar_add_menu (WMenuBar *menubar, Menu *menu)
719 if (menu != NULL) {
720 menu_arrange (menu, menubar->widget.parent->get_shortcut);
721 menubar->menu = g_list_append (menubar->menu, menu);
724 menubar_arrange (menubar);
728 * Properly space menubar items. Should be called when menubar is created
729 * and also when widget width is changed (i.e. upon xterm resize).
731 void
732 menubar_arrange (WMenuBar* menubar)
734 int start_x = 1;
735 GList *i;
736 int gap;
738 if (menubar->menu == NULL)
739 return;
741 #ifndef RESIZABLE_MENUBAR
742 gap = 3;
744 for (i = menubar->menu; i != NULL; i = g_list_next (i)) {
745 Menu *menu = i->data;
746 int len = hotkey_width (menu->text) + 2;
748 menu->start_x = start_x;
749 start_x += len + gap;
751 #else /* RESIZABLE_MENUBAR */
752 gap = menubar->widget.cols - 2;
754 /* First, calculate gap between items... */
755 for (i = menubar->menu; i != NULL; i = g_list_nwxt (i)) {
756 Menu *menu = i->data;
757 /* preserve length here, to be used below */
758 menu->start_x = hotkey_width (menu->text) + 2;
759 gap -= menu->start_x;
762 gap /= (menubar->menu->len - 1);
764 if (gap <= 0) {
765 /* We are out of luck - window is too narrow... */
766 gap = 1;
769 /* ...and now fix start positions of menubar items */
770 for (i = menubar->menu; i != NULL; i = g_list_nwxt (i)) {
771 Menu *menu = i->data;
772 int len = menu->start_x;
774 menu->start_x = start_x;
775 start_x += len + gap;
777 #endif /* RESIZABLE_MENUBAR */
780 /* Find MenuBar widget in the dialog */
781 WMenuBar *
782 find_menubar (const Dlg_head *h)
784 return (WMenuBar *) find_widget_type (h, menubar_callback);