change hotkey from char* to int
[midnight-commander.git] / src / menu.c
blobafbfd5b85a8f6b9e2f1469988d1c02040de99805
1 /* Pulldown menu code.
2 Copyright (C) 1994, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005,
3 2007 Free Software Foundation, Inc.
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
19 /** \file menu.c
20 * \brief Source: pulldown menu code
23 #include <config.h>
25 #include <ctype.h>
26 #include <stdarg.h>
27 #include <string.h>
28 #include <sys/types.h>
30 #include "global.h"
32 #include "../src/tty/tty.h"
33 #include "../src/skin/skin.h"
34 #include "../src/tty/mouse.h"
35 #include "../src/tty/key.h" /* key macros */
37 #include "menu.h"
38 #include "help.h"
39 #include "dialog.h"
40 #include "widget.h"
41 #include "main.h" /* is_right */
42 #include "strutil.h"
44 int menubar_visible = 1; /* This is the new default */
46 Menu *
47 create_menu (const char *name, menu_entry *entries, int count, const char *help_node)
49 Menu *menu;
51 menu = g_new (Menu, 1);
52 menu->count = count;
53 menu->max_entry_len = 20;
54 menu->entries = entries;
55 menu->text = parse_hotkey (name);
57 if (entries != (menu_entry*) NULL) {
58 int len;
59 register menu_entry* mp;
60 for (mp = entries; count--; mp++) {
61 if (mp->label[0] != '\0') {
62 mp->text = parse_hotkey (_(mp->label));
63 len = hotkey_width (mp->text);
64 menu->max_entry_len = max (len, menu->max_entry_len);
69 menu->start_x = 0;
70 menu->help_node = g_strdup (help_node);
71 return menu;
74 static void menubar_drop_compute (WMenu *menubar)
76 menubar->max_entry_len = menubar->menu [menubar->selected]->max_entry_len;
79 static void menubar_paint_idx (WMenu *menubar, int idx, int color)
81 const Menu *menu = menubar->menu[menubar->selected];
82 const int y = 2 + idx;
83 int x = menu->start_x;
84 const menu_entry *entry = &menu->entries[idx];
86 if (x + menubar->max_entry_len + 3 > menubar->widget.cols)
87 x = menubar->widget.cols - menubar->max_entry_len - 3;
89 if (entry->text.start == NULL) {
90 /* menu separator */
91 tty_setcolor (MENU_ENTRY_COLOR);
93 widget_move (&menubar->widget, y, x - 1);
94 tty_print_alt_char (ACS_LTEE);
96 tty_draw_hline (menubar->widget.y + y, menubar->widget.x + x,
97 ACS_HLINE, menubar->max_entry_len + 2);
99 widget_move (&menubar->widget, y, x + menubar->max_entry_len + 2);
100 tty_print_alt_char (ACS_RTEE);
101 } else {
102 /* menu text */
103 tty_setcolor (color);
104 widget_move (&menubar->widget, y, x);
105 tty_print_char ((unsigned char) entry->first_letter);
106 tty_draw_hline (-1, -1, ' ', menubar->max_entry_len + 1); /* clear line */
107 tty_print_string (entry->text.start);
109 if (entry->text.hotkey != 0) {
110 tty_setcolor (color == MENU_SELECTED_COLOR ?
111 MENU_HOTSEL_COLOR : MENU_HOT_COLOR);
112 tty_print_anychar (entry->text.hotkey);
113 tty_setcolor(color);
116 if (entry->text.end != NULL)
117 tty_print_string (entry->text.end);
119 /* move cursor to the start of entry text */
120 widget_move (&menubar->widget, y, x + 1);
124 static void menubar_draw_drop (WMenu *menubar)
126 const int count = menubar->menu [menubar->selected]->count;
127 int column = menubar->menu [menubar->selected]->start_x - 1;
128 int i;
130 if (column + menubar->max_entry_len + 4 > menubar->widget.cols)
131 column = menubar->widget.cols - menubar->max_entry_len - 4;
133 tty_setcolor (MENU_ENTRY_COLOR);
134 draw_box (menubar->widget.parent,
135 menubar->widget.y + 1, menubar->widget.x + column,
136 count + 2, menubar->max_entry_len + 4);
138 /* draw items except selected */
139 for (i = 0; i < count; i++)
140 if (i != menubar->subsel)
141 menubar_paint_idx (menubar, i, MENU_ENTRY_COLOR);
143 /* draw selected item at last to move cursot to the nice location */
144 menubar_paint_idx (menubar, menubar->subsel, MENU_SELECTED_COLOR);
147 static void menubar_set_color (WMenu *menubar, int current, gboolean hotkey)
149 if (!menubar->active)
150 tty_setcolor (hotkey ? COLOR_HOT_FOCUS : SELECTED_COLOR);
151 else if (current == menubar->selected)
152 tty_setcolor (hotkey ? MENU_HOTSEL_COLOR : MENU_SELECTED_COLOR);
153 else
154 tty_setcolor (hotkey ? MENU_HOT_COLOR : MENU_ENTRY_COLOR);
157 static void menubar_draw (WMenu *menubar)
159 const int items = menubar->items;
160 int i;
162 /* First draw the complete menubar */
163 tty_setcolor (menubar->active ? MENU_ENTRY_COLOR : SELECTED_COLOR);
164 tty_draw_hline (menubar->widget.y, menubar->widget.x, ' ', menubar->widget.cols);
166 /* Now each one of the entries */
167 for (i = 0; i < items; i++){
168 menubar_set_color (menubar, i, FALSE);
169 widget_move (&menubar->widget, 0, menubar->menu [i]->start_x);
171 tty_print_string (menubar->menu[i]->text.start);
173 if (menubar->menu[i]->text.hotkey != 0) {
174 menubar_set_color (menubar, i, TRUE);
175 tty_print_anychar (menubar->menu[i]->text.hotkey);
176 menubar_set_color (menubar, i, FALSE);
178 if (menubar->menu[i]->text.end != NULL)
179 tty_print_string (menubar->menu[i]->text.end);
182 if (menubar->dropped)
183 menubar_draw_drop (menubar);
184 else
185 widget_move (&menubar->widget, 0,
186 menubar-> menu[menubar->selected]->start_x);
189 static void menubar_remove (WMenu *menubar)
191 menubar->subsel = 0;
192 if (menubar->dropped) {
193 menubar->dropped = 0;
194 do_refresh ();
195 menubar->dropped = 1;
199 static void menubar_left (WMenu *menu)
201 menubar_remove (menu);
202 menu->selected = (menu->selected - 1) % menu->items;
203 if (menu->selected < 0)
204 menu->selected = menu->items -1;
205 menubar_drop_compute (menu);
206 menubar_draw (menu);
209 static void menubar_right (WMenu *menu)
211 menubar_remove (menu);
212 menu->selected = (menu->selected + 1) % menu->items;
213 menubar_drop_compute (menu);
214 menubar_draw (menu);
217 static void
218 menubar_finish (WMenu *menubar)
220 menubar->dropped = 0;
221 menubar->active = 0;
222 menubar->widget.lines = 1;
223 widget_want_hotkey (menubar->widget, 0);
225 dlg_select_by_id (menubar->widget.parent, menubar->previous_widget);
226 do_refresh ();
229 static void menubar_drop (WMenu *menubar, int selected)
231 menubar->dropped = 1;
232 menubar->selected = selected;
233 menubar->subsel = 0;
234 menubar_drop_compute (menubar);
235 menubar_draw (menubar);
238 static void menubar_execute (WMenu *menubar, int entry)
240 const Menu *menu = menubar->menu [menubar->selected];
241 const callfn call_back = menu->entries [entry].call_back;
243 is_right = menubar->selected != 0;
245 /* This used to be the other way round, i.e. first callback and
246 then menubar_finish. The new order (hack?) is needed to make
247 change_panel () work which is used in quick_view_cmd () -- Norbert
249 menubar_finish (menubar);
250 (*call_back) ();
251 do_refresh ();
254 static void menubar_move (WMenu *menubar, int step)
256 const Menu *menu = menubar->menu [menubar->selected];
258 menubar_paint_idx (menubar, menubar->subsel, MENU_ENTRY_COLOR);
259 do {
260 menubar->subsel += step;
261 if (menubar->subsel < 0)
262 menubar->subsel = menu->count - 1;
264 menubar->subsel %= menu->count;
265 } while (!menu->entries [menubar->subsel].call_back);
266 menubar_paint_idx (menubar, menubar->subsel, MENU_SELECTED_COLOR);
269 static int menubar_handle_key (WMenu *menubar, int key)
271 int i;
273 /* Lowercase */
274 if (isascii (key)) key = g_ascii_tolower (key);
276 if (is_abort_char (key)){
277 menubar_finish (menubar);
278 return 1;
281 if (key == KEY_F(1)) {
282 if (menubar->dropped) {
283 interactive_display (NULL,
284 (menubar->menu [menubar->selected])->help_node);
285 } else {
286 interactive_display (NULL, "[Menu Bar]");
288 menubar_draw (menubar);
289 return 1;
292 if (key == KEY_LEFT || key == XCTRL('b')){
293 menubar_left (menubar);
294 return 1;
295 } else if (key == KEY_RIGHT || key == XCTRL ('f')){
296 menubar_right (menubar);
297 return 1;
300 if (!menubar->dropped){
301 const int items = menubar->items;
302 for (i = 0; i < items; i++) {
303 const Menu *menu = menubar->menu [i];
305 if (menu->text.hotkey != 0) {
306 if (menu->text.hotkey == key) {
307 menubar_drop (menubar, i);
308 return 1;
312 if (key == KEY_ENTER || key == XCTRL ('n')
313 || key == KEY_DOWN || key == '\n') {
315 menubar_drop (menubar, menubar->selected);
316 return 1;
318 return 1;
319 } else {
320 const int selected = menubar->selected;
321 const Menu *menu = menubar->menu [selected];
322 const int items = menu->count;
324 for (i = 0; i < items; i++) {
325 if (!menu->entries [i].call_back)
326 continue;
328 if (menu->entries[i].text.hotkey != 0) {
329 if (key != menu->entries[i].text.hotkey)
330 continue;
332 menubar_execute (menubar, i);
333 return 1;
337 if (key == KEY_ENTER || key == '\n') {
338 menubar_execute (menubar, menubar->subsel);
339 return 1;
343 if (key == KEY_DOWN || key == XCTRL ('n'))
344 menubar_move (menubar, 1);
346 if (key == KEY_UP || key == XCTRL ('p'))
347 menubar_move (menubar, -1);
349 return 0;
352 static cb_ret_t
353 menubar_callback (Widget *w, widget_msg_t msg, int parm)
355 WMenu *menubar = (WMenu *) w;
357 switch (msg) {
358 /* We do not want the focus unless we have been activated */
359 case WIDGET_FOCUS:
360 if (!menubar->active)
361 return MSG_NOT_HANDLED;
363 widget_want_cursor (menubar->widget, 1);
365 /* Trick to get all the mouse events */
366 menubar->widget.lines = LINES;
368 /* Trick to get all of the hotkeys */
369 widget_want_hotkey (menubar->widget, 1);
370 menubar->subsel = 0;
371 menubar_drop_compute (menubar);
372 menubar_draw (menubar);
373 return MSG_HANDLED;
375 /* We don't want the buttonbar to activate while using the menubar */
376 case WIDGET_HOTKEY:
377 case WIDGET_KEY:
378 if (menubar->active) {
379 menubar_handle_key (menubar, parm);
380 return MSG_HANDLED;
381 } else
382 return MSG_NOT_HANDLED;
384 case WIDGET_CURSOR:
385 /* Put the cursor in a suitable place */
386 return MSG_NOT_HANDLED;
388 case WIDGET_UNFOCUS:
389 if (menubar->active)
390 return MSG_NOT_HANDLED;
391 else {
392 widget_want_cursor (menubar->widget, 0);
393 return MSG_HANDLED;
396 case WIDGET_DRAW:
397 if (menubar_visible) {
398 menubar_draw (menubar);
399 return MSG_HANDLED;
401 /* fall through */
403 case WIDGET_RESIZED:
404 /* try show menu after screen resize */
405 send_message (w, WIDGET_FOCUS, 0);
406 return MSG_HANDLED;
408 default:
409 return default_proc (msg, parm);
413 static int
414 menubar_event (Gpm_Event *event, void *data)
416 WMenu *menubar = data;
417 Menu *menu = menubar->menu [menubar->selected];
418 int was_active = 1;
419 int left_x, right_x, bottom_y;
421 /* ignore unsupported events */
422 if ((event->type & (GPM_UP | GPM_DOWN | GPM_DRAG)) == 0)
423 return MOU_NORMAL;
425 /* ignore wheel events if menu is inactive */
426 if (!menubar->active && (event->buttons & (GPM_B_MIDDLE | GPM_B_UP | GPM_B_DOWN)))
427 return MOU_NORMAL;
429 /* detect the menu state */
430 if (!menubar->dropped) {
431 menubar->previous_widget = menubar->widget.parent->current->dlg_id;
432 menubar->active = 1;
433 menubar->dropped = 1;
434 was_active = 0;
437 /* Mouse operations on the menubar */
438 if (event->y == 1 || !was_active){
439 if (event->type & GPM_UP)
440 return MOU_NORMAL;
442 /* wheel events on menubar */
443 if (event->buttons & GPM_B_UP)
444 menubar_left (menubar);
445 else if (event->buttons & GPM_B_DOWN)
446 menubar_right (menubar);
447 else {
448 int new_selection = 0;
450 while (new_selection < menubar->items
451 && event->x > menubar->menu[new_selection]->start_x)
452 new_selection++;
454 if (new_selection) /* Don't set the invalid value -1 */
455 --new_selection;
457 if (!was_active) {
458 menubar->selected = new_selection;
459 dlg_select_widget (menubar);
460 } else {
461 menubar_remove (menubar);
462 menubar->selected = new_selection;
464 menubar_drop_compute (menubar);
465 menubar_draw (menubar);
467 return MOU_NORMAL;
470 if (!menubar->dropped || (event->y < 2))
471 return MOU_NORMAL;
473 /* middle click -- everywhere */
474 if ((event->buttons & GPM_B_MIDDLE) && (event->type & GPM_DOWN)) {
475 menubar_execute (menubar, menubar->subsel);
476 return MOU_NORMAL;
479 /* the mouse operation is on the menus or it is not */
480 left_x = menu->start_x;
481 right_x = left_x + menubar->max_entry_len + 3;
482 if (right_x > menubar->widget.cols) {
483 left_x = menubar->widget.cols - menubar->max_entry_len - 3;
484 right_x = menubar->widget.cols;
487 bottom_y = menu->count + 3;
489 if ((event->x >= left_x) && (event->x <= right_x) && (event->y <= bottom_y)){
490 int pos = event->y - 3;
492 /* mouse wheel */
493 if ((event->buttons & GPM_B_UP) && (event->type & GPM_DOWN)) {
494 menubar_move (menubar, -1);
495 return MOU_NORMAL;
497 if ((event->buttons & GPM_B_DOWN) && (event->type & GPM_DOWN)) {
498 menubar_move (menubar, 1);
499 return MOU_NORMAL;
502 /* ignore events above and below dropped down menu */
503 if ((pos < 0) || (pos >= menu->count))
504 return MOU_NORMAL;
506 if (menubar->menu [menubar->selected]->entries [pos].call_back) {
507 menubar_paint_idx (menubar, menubar->subsel, MENU_ENTRY_COLOR);
508 menubar->subsel = pos;
509 menubar_paint_idx (menubar, menubar->subsel, MENU_SELECTED_COLOR);
511 if (event->type & GPM_UP)
512 menubar_execute (menubar, menubar->subsel);
514 } else
515 /* use click not wheel to close menu */
516 if ((event->type & GPM_DOWN) && !(event->buttons & (GPM_B_UP | GPM_B_DOWN)))
517 menubar_finish (menubar);
519 return MOU_NORMAL;
523 * Properly space menubar items. Should be called when menubar is created
524 * and also when widget width is changed (i.e. upon xterm resize).
526 void
527 menubar_arrange(WMenu* menubar)
529 register int i, start_x = 1;
530 int items = menubar->items;
532 #ifndef RESIZABLE_MENUBAR
533 int gap = 3;
535 for (i = 0; i < items; i++)
537 int len = hotkey_width (menubar->menu[i]->text);
538 menubar->menu[i]->start_x = start_x;
539 start_x += len + gap;
542 #else /* RESIZABLE_MENUBAR */
544 int gap = menubar->widget.cols - 2;
546 /* First, calculate gap between items... */
547 for (i = 0; i < items; i++)
549 /* preserve length here, to be used below */
550 gap -= (menubar->menu[i]->start_x = hotkey_width (menubar->menu[i]->text));
553 gap /= (items - 1);
555 if (gap <= 0)
557 /* We are out of luck - window is too narrow... */
558 gap = 1;
561 /* ...and now fix start positions of menubar items */
562 for (i = 0; i < items; i++)
564 int len = menubar->menu[i]->start_x;
565 menubar->menu[i]->start_x = start_x;
566 start_x += len + gap;
568 #endif /* RESIZABLE_MENUBAR */
571 void
572 destroy_menu (Menu *menu)
574 release_hotkey (menu->text);
575 if (menu->entries != NULL) {
576 int me;
577 for (me = 0; me < menu->count; me++) {
578 if (menu->entries[me].label[0] != '\0') {
579 release_hotkey (menu->entries[me].text);
584 g_free (menu->help_node);
585 g_free (menu);
588 WMenu *
589 menubar_new (int y, int x, int cols, Menu *menu[], int items)
591 WMenu *menubar = g_new0 (WMenu, 1);
593 init_widget (&menubar->widget, y, x, 1, cols,
594 menubar_callback, menubar_event);
595 menubar->menu = menu;
596 menubar->active = 0;
597 menubar->dropped = 0;
598 menubar->items = items;
599 menubar->selected = 0;
600 menubar->subsel = 0;
601 widget_want_cursor (menubar->widget, 0);
602 menubar_arrange (menubar);
604 return menubar;