Ticket #2021 (MarkFileDown/MarkFileUp)
[free-mc.git] / src / menu.c
blob800854ef8b58f0be626ae5ffe2ec7d661d731564
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)
67 release_hotkey (entry->text);
68 g_free (entry->shortcut);
69 g_free (entry);
73 static void
74 menu_arrange (Menu * menu, dlg_shortcut_str get_shortcut)
76 if (menu != NULL)
78 GList *i;
79 size_t max_shortcut_len = 0;
81 menu->max_entry_len = 1;
82 menu->max_hotkey_len = 1;
84 for (i = menu->entries; i != NULL; i = g_list_next (i))
86 menu_entry_t *entry = i->data;
88 if (entry != NULL)
90 size_t len;
92 len = (size_t) hotkey_width (entry->text);
93 menu->max_hotkey_len = max (menu->max_hotkey_len, len);
95 if (get_shortcut != NULL)
96 entry->shortcut = get_shortcut (entry->command);
98 if (entry->shortcut != NULL)
100 len = (size_t) str_term_width1 (entry->shortcut);
101 max_shortcut_len = max (max_shortcut_len, len);
106 menu->max_entry_len = menu->max_hotkey_len + max_shortcut_len;
110 Menu *
111 create_menu (const char *name, GList * entries, const char *help_node)
113 Menu *menu;
115 menu = g_new (Menu, 1);
116 menu->start_x = 0;
117 menu->text = parse_hotkey (name);
118 menu->entries = entries;
119 menu->max_entry_len = 1;
120 menu->max_hotkey_len = 0;
121 menu->selected = 0;
122 menu->help_node = g_strdup (help_node);
124 return menu;
127 void
128 destroy_menu (Menu * menu)
130 release_hotkey (menu->text);
131 g_list_foreach (menu->entries, (GFunc) menu_entry_free, NULL);
132 g_list_free (menu->entries);
133 g_free (menu->help_node);
134 g_free (menu);
137 static void
138 menubar_paint_idx (WMenuBar * menubar, unsigned int idx, int color)
140 const Menu *menu = g_list_nth_data (menubar->menu, menubar->selected);
141 const menu_entry_t *entry = g_list_nth_data (menu->entries, idx);
142 const int y = 2 + idx;
143 int x = menu->start_x;
145 if (x + menu->max_entry_len + 4 > (gsize) menubar->widget.cols)
146 x = menubar->widget.cols - menu->max_entry_len - 4;
148 if (entry == NULL)
150 /* menu separator */
151 tty_setcolor (MENU_ENTRY_COLOR);
153 widget_move (&menubar->widget, y, x - 1);
154 tty_print_alt_char (ACS_LTEE, FALSE);
156 tty_draw_hline (menubar->widget.y + y, menubar->widget.x + x,
157 ACS_HLINE, menu->max_entry_len + 3);
159 widget_move (&menubar->widget, y, x + menu->max_entry_len + 3);
160 tty_print_alt_char (ACS_RTEE, FALSE);
162 else
164 /* menu text */
165 tty_setcolor (color);
166 widget_move (&menubar->widget, y, x);
167 tty_print_char ((unsigned char) entry->first_letter);
168 tty_draw_hline (-1, -1, ' ', menu->max_entry_len + 2); /* clear line */
169 tty_print_string (entry->text.start);
171 if (entry->text.hotkey != NULL)
173 tty_setcolor (color == MENU_SELECTED_COLOR ? MENU_HOTSEL_COLOR : MENU_HOT_COLOR);
174 tty_print_string (entry->text.hotkey);
175 tty_setcolor (color);
178 if (entry->text.end != NULL)
179 tty_print_string (entry->text.end);
181 if (entry->shortcut != NULL)
183 widget_move (&menubar->widget, y, x + menu->max_hotkey_len + 3);
184 tty_print_string (entry->shortcut);
187 /* move cursor to the start of entry text */
188 widget_move (&menubar->widget, y, x + 1);
192 static void
193 menubar_draw_drop (WMenuBar * menubar)
195 const Menu *menu = g_list_nth_data (menubar->menu, menubar->selected);
196 const unsigned int count = g_list_length (menu->entries);
197 int column = menu->start_x - 1;
198 unsigned int i;
200 if (column + menu->max_entry_len + 5 > (gsize) menubar->widget.cols)
201 column = menubar->widget.cols - menu->max_entry_len - 5;
203 tty_setcolor (MENU_ENTRY_COLOR);
204 draw_box (menubar->widget.parent,
205 menubar->widget.y + 1, menubar->widget.x + column,
206 count + 2, menu->max_entry_len + 5, FALSE);
208 /* draw items except selected */
209 for (i = 0; i < count; i++)
210 if (i != menu->selected)
211 menubar_paint_idx (menubar, i, MENU_ENTRY_COLOR);
213 /* draw selected item at last to move cursor to the nice location */
214 menubar_paint_idx (menubar, menu->selected, MENU_SELECTED_COLOR);
217 static void
218 menubar_set_color (WMenuBar * menubar, gboolean current, gboolean hotkey)
220 if (!menubar->is_active)
221 tty_setcolor (MENU_INACTIVE_COLOR);
222 else if (current)
223 tty_setcolor (hotkey ? MENU_HOTSEL_COLOR : MENU_SELECTED_COLOR);
224 else
225 tty_setcolor (hotkey ? MENU_HOT_COLOR : MENU_ENTRY_COLOR);
228 static void
229 menubar_draw (WMenuBar * menubar)
231 GList *i;
233 /* First draw the complete menubar */
234 tty_setcolor (menubar->is_active ? MENU_ENTRY_COLOR : MENU_INACTIVE_COLOR);
235 tty_draw_hline (menubar->widget.y, menubar->widget.x, ' ', menubar->widget.cols);
237 /* Now each one of the entries */
238 for (i = menubar->menu; i != NULL; i = g_list_next (i))
240 Menu *menu = i->data;
241 gboolean is_selected = (menubar->selected == (gsize) g_list_position (menubar->menu, i));
243 menubar_set_color (menubar, is_selected, FALSE);
244 widget_move (&menubar->widget, 0, menu->start_x);
246 tty_print_char (' ');
247 tty_print_string (menu->text.start);
249 if (menu->text.hotkey != NULL)
251 menubar_set_color (menubar, is_selected, TRUE);
252 tty_print_string (menu->text.hotkey);
253 menubar_set_color (menubar, is_selected, FALSE);
256 if (menu->text.end != NULL)
257 tty_print_string (menu->text.end);
259 tty_print_char (' ');
262 if (menubar->is_dropped)
263 menubar_draw_drop (menubar);
264 else
265 widget_move (&menubar->widget, 0,
266 ((Menu *) g_list_nth_data (menubar->menu, menubar->selected))->start_x);
269 static void
270 menubar_remove (WMenuBar * menubar)
272 if (menubar->is_dropped)
274 menubar->is_dropped = FALSE;
275 do_refresh ();
276 menubar->is_dropped = TRUE;
280 static void
281 menubar_left (WMenuBar * menubar)
283 menubar_remove (menubar);
284 if (menubar->selected == 0)
285 menubar->selected = g_list_length (menubar->menu) - 1;
286 else
287 menubar->selected--;
288 menubar_draw (menubar);
291 static void
292 menubar_right (WMenuBar * menubar)
294 menubar_remove (menubar);
295 menubar->selected = (menubar->selected + 1) % g_list_length (menubar->menu);
296 menubar_draw (menubar);
299 static void
300 menubar_finish (WMenuBar * menubar)
302 menubar->is_dropped = FALSE;
303 menubar->is_active = FALSE;
304 menubar->widget.lines = 1;
305 widget_want_hotkey (menubar->widget, 0);
307 dlg_select_by_id (menubar->widget.parent, menubar->previous_widget);
308 do_refresh ();
311 static void
312 menubar_drop (WMenuBar * menubar, unsigned int selected)
314 menubar->is_dropped = TRUE;
315 menubar->selected = selected;
316 menubar_draw (menubar);
319 static void
320 menubar_execute (WMenuBar * menubar)
322 const Menu *menu = g_list_nth_data (menubar->menu, menubar->selected);
323 const menu_entry_t *entry = g_list_nth_data (menu->entries, menu->selected);
325 if ((entry != NULL) && (entry->command != CK_Ignore_Key))
327 is_right = (menubar->selected != 0);
328 menubar_finish (menubar);
329 menubar->widget.parent->callback (menubar->widget.parent, &menubar->widget,
330 DLG_ACTION, entry->command, NULL);
331 do_refresh ();
335 static void
336 menubar_down (WMenuBar * menubar)
338 Menu *menu = g_list_nth_data (menubar->menu, menubar->selected);
339 const unsigned int len = g_list_length (menu->entries);
340 menu_entry_t *entry;
342 menubar_paint_idx (menubar, menu->selected, MENU_ENTRY_COLOR);
346 menu->selected = (menu->selected + 1) % len;
347 entry = (menu_entry_t *) g_list_nth_data (menu->entries, menu->selected);
349 while ((entry == NULL) || (entry->command == CK_Ignore_Key));
351 menubar_paint_idx (menubar, menu->selected, MENU_SELECTED_COLOR);
354 static void
355 menubar_up (WMenuBar * menubar)
357 Menu *menu = g_list_nth_data (menubar->menu, menubar->selected);
358 const unsigned int len = g_list_length (menu->entries);
359 menu_entry_t *entry;
361 menubar_paint_idx (menubar, menu->selected, MENU_ENTRY_COLOR);
365 if (menu->selected == 0)
366 menu->selected = len - 1;
367 else
368 menu->selected--;
369 entry = (menu_entry_t *) g_list_nth_data (menu->entries, menu->selected);
371 while ((entry == NULL) || (entry->command == CK_Ignore_Key));
373 menubar_paint_idx (menubar, menu->selected, MENU_SELECTED_COLOR);
376 static void
377 menubar_first (WMenuBar * menubar)
379 Menu *menu = g_list_nth_data (menubar->menu, menubar->selected);
380 menu_entry_t *entry;
382 if (menu->selected == 0)
383 return;
385 menubar_paint_idx (menubar, menu->selected, MENU_ENTRY_COLOR);
387 menu->selected = 0;
389 while (TRUE)
391 entry = (menu_entry_t *) g_list_nth_data (menu->entries, menu->selected);
393 if ((entry == NULL) || (entry->command == CK_Ignore_Key))
394 menu->selected++;
395 else
396 break;
399 menubar_paint_idx (menubar, menu->selected, MENU_SELECTED_COLOR);
402 static void
403 menubar_last (WMenuBar * menubar)
405 Menu *menu = g_list_nth_data (menubar->menu, menubar->selected);
406 const unsigned int len = g_list_length (menu->entries);
407 menu_entry_t *entry;
409 if (menu->selected == len - 1)
410 return;
412 menubar_paint_idx (menubar, menu->selected, MENU_ENTRY_COLOR);
414 menu->selected = len;
418 menu->selected--;
419 entry = (menu_entry_t *) g_list_nth_data (menu->entries, menu->selected);
421 while ((entry == NULL) || (entry->command == CK_Ignore_Key));
423 menubar_paint_idx (menubar, menu->selected, MENU_SELECTED_COLOR);
426 static int
427 menubar_handle_key (WMenuBar * menubar, int key)
429 /* Lowercase */
430 if (isascii (key))
431 key = g_ascii_tolower (key);
433 if (is_abort_char (key))
435 menubar_finish (menubar);
436 return 1;
439 /* menubar help or menubar navigation */
440 switch (key)
442 case KEY_F (1):
443 if (menubar->is_dropped)
444 interactive_display (NULL,
445 ((Menu *) g_list_nth_data (menubar->menu,
446 menubar->selected))->help_node);
447 else
448 interactive_display (NULL, "[Menu Bar]");
449 menubar_draw (menubar);
450 return 1;
452 case KEY_LEFT:
453 case XCTRL ('b'):
454 menubar_left (menubar);
455 return 1;
457 case KEY_RIGHT:
458 case XCTRL ('f'):
459 menubar_right (menubar);
460 return 1;
463 if (!menubar->is_dropped)
465 GList *i;
467 /* drop menu by hotkey */
468 for (i = menubar->menu; i != NULL; i = g_list_next (i))
470 Menu *menu = i->data;
472 if ((menu->text.hotkey != NULL) && (key == g_ascii_tolower (menu->text.hotkey[0])))
474 menubar_drop (menubar, g_list_position (menubar->menu, i));
475 return 1;
479 /* drop menu by Enter or Dowwn key */
480 if (key == KEY_ENTER || key == XCTRL ('n') || key == KEY_DOWN || key == '\n')
481 menubar_drop (menubar, menubar->selected);
483 return 1;
487 Menu *menu = g_list_nth_data (menubar->menu, menubar->selected);
488 GList *i;
490 /* execute menu command by hotkey */
491 for (i = menu->entries; i != NULL; i = g_list_next (i))
493 const menu_entry_t *entry = i->data;
495 if ((entry != NULL) && (entry->command != CK_Ignore_Key)
496 && (entry->text.hotkey != NULL) && (key == g_ascii_tolower (entry->text.hotkey[0])))
498 menu->selected = g_list_position (menu->entries, i);
499 menubar_execute (menubar);
500 return 1;
504 /* menu execute by Enter or menu navigation */
505 switch (key)
507 case KEY_ENTER:
508 case '\n':
509 menubar_execute (menubar);
510 return 1;
512 case KEY_HOME:
513 case ALT ('<'):
514 menubar_first (menubar);
515 break;
517 case KEY_END:
518 case ALT ('>'):
519 menubar_last (menubar);
520 break;
522 case KEY_DOWN:
523 case XCTRL ('n'):
524 menubar_down (menubar);
525 break;
527 case KEY_UP:
528 case XCTRL ('p'):
529 menubar_up (menubar);
530 break;
534 return 0;
537 static cb_ret_t
538 menubar_callback (Widget * w, widget_msg_t msg, int parm)
540 WMenuBar *menubar = (WMenuBar *) w;
542 switch (msg)
544 /* We do not want the focus unless we have been activated */
545 case WIDGET_FOCUS:
546 if (!menubar->is_active)
547 return MSG_NOT_HANDLED;
549 widget_want_cursor (menubar->widget, 1);
551 /* Trick to get all the mouse events */
552 menubar->widget.lines = LINES;
554 /* Trick to get all of the hotkeys */
555 widget_want_hotkey (menubar->widget, 1);
556 menubar_draw (menubar);
557 return MSG_HANDLED;
559 /* We don't want the buttonbar to activate while using the menubar */
560 case WIDGET_HOTKEY:
561 case WIDGET_KEY:
562 if (menubar->is_active)
564 menubar_handle_key (menubar, parm);
565 return MSG_HANDLED;
567 return MSG_NOT_HANDLED;
569 case WIDGET_CURSOR:
570 /* Put the cursor in a suitable place */
571 return MSG_NOT_HANDLED;
573 case WIDGET_UNFOCUS:
574 if (menubar->is_active)
575 return MSG_NOT_HANDLED;
577 widget_want_cursor (menubar->widget, 0);
578 return MSG_HANDLED;
580 case WIDGET_DRAW:
581 if (menubar_visible)
583 menubar_draw (menubar);
584 return MSG_HANDLED;
586 /* fall through */
588 case WIDGET_RESIZED:
589 /* try show menu after screen resize */
590 send_message (w, WIDGET_FOCUS, 0);
591 return MSG_HANDLED;
594 case WIDGET_DESTROY:
595 menubar_set_menu (menubar, NULL);
596 return MSG_HANDLED;
598 default:
599 return default_proc (msg, parm);
603 static int
604 menubar_event (Gpm_Event * event, void *data)
606 WMenuBar *menubar = data;
607 gboolean was_active = TRUE;
608 int left_x, right_x, bottom_y;
609 Menu *menu;
611 /* ignore unsupported events */
612 if ((event->type & (GPM_UP | GPM_DOWN | GPM_DRAG)) == 0)
613 return MOU_NORMAL;
615 /* ignore wheel events if menu is inactive */
616 if (!menubar->is_active && ((event->buttons & (GPM_B_MIDDLE | GPM_B_UP | GPM_B_DOWN)) != 0))
617 return MOU_NORMAL;
619 if (!menubar->is_dropped)
621 menubar->previous_widget = menubar->widget.parent->current->dlg_id;
622 menubar->is_active = TRUE;
623 menubar->is_dropped = TRUE;
624 was_active = FALSE;
627 /* Mouse operations on the menubar */
628 if (event->y == 1 || !was_active)
630 if ((event->type & GPM_UP) != 0)
631 return MOU_NORMAL;
633 /* wheel events on menubar */
634 if (event->buttons & GPM_B_UP)
635 menubar_left (menubar);
636 else if (event->buttons & GPM_B_DOWN)
637 menubar_right (menubar);
638 else
640 const unsigned int len = g_list_length (menubar->menu);
641 unsigned int new_selection = 0;
643 while ((new_selection < len)
644 && (event->x > ((Menu *) g_list_nth_data (menubar->menu,
645 new_selection))->start_x))
646 new_selection++;
648 if (new_selection != 0) /* Don't set the invalid value -1 */
649 new_selection--;
651 if (!was_active)
653 menubar->selected = new_selection;
654 dlg_select_widget (menubar);
656 else
658 menubar_remove (menubar);
659 menubar->selected = new_selection;
661 menubar_draw (menubar);
663 return MOU_NORMAL;
666 if (!menubar->is_dropped || (event->y < 2))
667 return MOU_NORMAL;
669 /* middle click -- everywhere */
670 if (((event->buttons & GPM_B_MIDDLE) != 0) && ((event->type & GPM_DOWN) != 0))
672 menubar_execute (menubar);
673 return MOU_NORMAL;
676 /* the mouse operation is on the menus or it is not */
677 menu = (Menu *) g_list_nth_data (menubar->menu, menubar->selected);
678 left_x = menu->start_x;
679 right_x = left_x + menu->max_entry_len + 3;
680 if (right_x > menubar->widget.cols)
682 left_x = menubar->widget.cols - menu->max_entry_len - 3;
683 right_x = menubar->widget.cols;
686 bottom_y = g_list_length (menu->entries) + 3;
688 if ((event->x >= left_x) && (event->x <= right_x) && (event->y <= bottom_y))
690 int pos = event->y - 3;
691 const menu_entry_t *entry = g_list_nth_data (menu->entries, pos);
693 /* mouse wheel */
694 if ((event->buttons & GPM_B_UP) && (event->type & GPM_DOWN))
696 menubar_up (menubar);
697 return MOU_NORMAL;
699 if ((event->buttons & GPM_B_DOWN) && (event->type & GPM_DOWN))
701 menubar_down (menubar);
702 return MOU_NORMAL;
705 /* ignore events above and below dropped down menu */
706 if ((pos < 0) || (pos >= bottom_y - 3))
707 return MOU_NORMAL;
709 if ((entry != NULL) && (entry->command != CK_Ignore_Key))
711 menubar_paint_idx (menubar, menu->selected, MENU_ENTRY_COLOR);
712 menu->selected = pos;
713 menubar_paint_idx (menubar, menu->selected, MENU_SELECTED_COLOR);
715 if ((event->type & GPM_UP) != 0)
716 menubar_execute (menubar);
719 else
720 /* use click not wheel to close menu */
721 if (((event->type & GPM_DOWN) != 0) && ((event->buttons & (GPM_B_UP | GPM_B_DOWN)) == 0))
722 menubar_finish (menubar);
724 return MOU_NORMAL;
727 WMenuBar *
728 menubar_new (int y, int x, int cols, GList * menu)
730 WMenuBar *menubar = g_new0 (WMenuBar, 1);
732 init_widget (&menubar->widget, y, x, 1, cols, menubar_callback, menubar_event);
733 widget_want_cursor (menubar->widget, 0);
734 menubar_set_menu (menubar, menu);
735 return menubar;
738 void
739 menubar_set_menu (WMenuBar * menubar, GList * menu)
741 /* delete previous menu */
742 if (menubar->menu != NULL)
744 g_list_foreach (menubar->menu, (GFunc) destroy_menu, NULL);
745 g_list_free (menubar->menu);
747 /* add new menu */
748 menubar->is_active = FALSE;
749 menubar->is_dropped = FALSE;
750 menubar->menu = menu;
751 menubar->selected = 0;
752 menubar_arrange (menubar);
755 void
756 menubar_add_menu (WMenuBar * menubar, Menu * menu)
758 if (menu != NULL)
760 menu_arrange (menu, menubar->widget.parent->get_shortcut);
761 menubar->menu = g_list_append (menubar->menu, menu);
764 menubar_arrange (menubar);
768 * Properly space menubar items. Should be called when menubar is created
769 * and also when widget width is changed (i.e. upon xterm resize).
771 void
772 menubar_arrange (WMenuBar * menubar)
774 int start_x = 1;
775 GList *i;
776 int gap;
778 if (menubar->menu == NULL)
779 return;
781 #ifndef RESIZABLE_MENUBAR
782 gap = 3;
784 for (i = menubar->menu; i != NULL; i = g_list_next (i))
786 Menu *menu = i->data;
787 int len = hotkey_width (menu->text) + 2;
789 menu->start_x = start_x;
790 start_x += len + gap;
792 #else /* RESIZABLE_MENUBAR */
793 gap = menubar->widget.cols - 2;
795 /* First, calculate gap between items... */
796 for (i = menubar->menu; i != NULL; i = g_list_nwxt (i))
798 Menu *menu = i->data;
799 /* preserve length here, to be used below */
800 menu->start_x = hotkey_width (menu->text) + 2;
801 gap -= menu->start_x;
804 gap /= (menubar->menu->len - 1);
806 if (gap <= 0)
808 /* We are out of luck - window is too narrow... */
809 gap = 1;
812 /* ...and now fix start positions of menubar items */
813 for (i = menubar->menu; i != NULL; i = g_list_nwxt (i))
815 Menu *menu = i->data;
816 int len = menu->start_x;
818 menu->start_x = start_x;
819 start_x += len + gap;
821 #endif /* RESIZABLE_MENUBAR */
824 /* Find MenuBar widget in the dialog */
825 WMenuBar *
826 find_menubar (const Dlg_head * h)
828 return (WMenuBar *) find_widget_type (h, menubar_callback);