Editor: sync with new global config location (user menu and syntax files).
[midnight-commander.git] / src / menu.c
blob59031572265053f14fd9ecf5c218627a51889ca4
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"
31 #include "tty.h"
32 #include "menu.h"
33 #include "help.h"
34 #include "dialog.h"
35 #include "color.h"
36 #include "main.h"
37 #include "mouse.h"
38 #include "win.h"
39 #include "key.h" /* For mi_getch() */
40 #include "strutil.h"
42 int menubar_visible = 1; /* This is the new default */
44 Menu *
45 create_menu (const char *name, menu_entry *entries, int count, const char *help_node)
47 Menu *menu;
49 menu = g_new (Menu, 1);
50 menu->count = count;
51 menu->max_entry_len = 20;
52 menu->entries = entries;
53 menu->text = parse_hotkey (name);
55 if (entries != (menu_entry*) NULL) {
56 int len;
57 register menu_entry* mp;
58 for (mp = entries; count--; mp++) {
59 if (mp->label[0] != '\0') {
60 mp->text = parse_hotkey (_(mp->label));
61 len = hotkey_width (mp->text);
62 menu->max_entry_len = max (len, menu->max_entry_len);
67 menu->start_x = 0;
68 menu->help_node = g_strdup (help_node);
69 return menu;
72 static void menubar_drop_compute (WMenu *menubar)
74 menubar->max_entry_len = menubar->menu [menubar->selected]->max_entry_len;
77 static void menubar_paint_idx (WMenu *menubar, int idx, int color)
79 const Menu *menu = menubar->menu[menubar->selected];
80 const int y = 2 + idx;
81 int x = menu->start_x;
82 const menu_entry *entry = &menu->entries[idx];
84 if (x + menubar->max_entry_len + 3 > menubar->widget.cols)
85 x = menubar->widget.cols - menubar->max_entry_len - 3;
87 if (entry->text.start == NULL) {
88 /* menu separator */
89 attrset (SELECTED_COLOR);
91 if (!slow_terminal) {
92 widget_move (&menubar->widget, y, x - 1);
93 tty_print_alt_char (ACS_LTEE);
96 tty_print_hline (menubar->widget.y + y, menubar->widget.x + x,
97 menubar->max_entry_len + 2);
99 if (!slow_terminal)
100 tty_print_alt_char (ACS_RTEE);
101 } else {
102 /* menu text */
103 attrset (color);
104 widget_move (&menubar->widget, y, x);
105 addch ((unsigned char) entry->first_letter);
106 hline (' ', menubar->max_entry_len + 1); /* clear line */
107 addstr (str_term_form (entry->text.start));
109 if (entry->text.hotkey != NULL) {
110 attrset (color == MENU_SELECTED_COLOR ?
111 MENU_HOTSEL_COLOR : MENU_HOT_COLOR);
112 addstr (str_term_form (entry->text.hotkey));
113 attrset(color);
116 if (entry->text.end != NULL)
117 addstr (str_term_form (entry->text.end));
119 /* move cursor to the start of entry text */
120 widget_move (&menubar->widget, y, x + 1);
124 static inline 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 attrset (SELECTED_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_draw (WMenu *menubar)
149 const int items = menubar->items;
150 int i;
152 /* First draw the complete menubar */
153 attrset (SELECTED_COLOR);
154 widget_move (&menubar->widget, 0, 0);
156 hline (' ', menubar->widget.cols);
158 attrset (SELECTED_COLOR);
159 /* Now each one of the entries */
160 for (i = 0; i < items; i++){
161 attrset ((menubar->active && i == menubar->selected) ?
162 MENU_SELECTED_COLOR : SELECTED_COLOR);
163 widget_move (&menubar->widget, 0, menubar->menu [i]->start_x);
165 addstr (str_term_form (menubar->menu[i]->text.start));
167 if (menubar->menu[i]->text.hotkey != NULL) {
168 attrset ((menubar->active && i == menubar->selected) ?
169 MENU_HOTSEL_COLOR : COLOR_HOT_FOCUS);
170 addstr (str_term_form (menubar->menu[i]->text.hotkey));
171 attrset ((menubar->active && i == menubar->selected) ?
172 MENU_SELECTED_COLOR : SELECTED_COLOR);
174 if (menubar->menu[i]->text.end != NULL) {
175 addstr (str_term_form (menubar->menu[i]->text.end));
179 if (menubar->dropped)
180 menubar_draw_drop (menubar);
181 else
182 widget_move (&menubar->widget, 0,
183 menubar-> menu[menubar->selected]->start_x);
186 static inline void menubar_remove (WMenu *menubar)
188 menubar->subsel = 0;
189 if (menubar->dropped){
190 menubar->dropped = 0;
191 do_refresh ();
192 menubar->dropped = 1;
196 static void menubar_left (WMenu *menu)
198 menubar_remove (menu);
199 menu->selected = (menu->selected - 1) % menu->items;
200 if (menu->selected < 0)
201 menu->selected = menu->items -1;
202 menubar_drop_compute (menu);
203 menubar_draw (menu);
206 static void menubar_right (WMenu *menu)
208 menubar_remove (menu);
209 menu->selected = (menu->selected + 1) % menu->items;
210 menubar_drop_compute (menu);
211 menubar_draw (menu);
214 static void
215 menubar_finish (WMenu *menubar)
217 menubar->dropped = 0;
218 menubar->active = 0;
219 menubar->widget.lines = 1;
220 widget_want_hotkey (menubar->widget, 0);
222 dlg_select_by_id (menubar->widget.parent, menubar->previous_widget);
223 do_refresh ();
226 static void menubar_drop (WMenu *menubar, int selected)
228 menubar->dropped = 1;
229 menubar->selected = selected;
230 menubar->subsel = 0;
231 menubar_drop_compute (menubar);
232 menubar_draw (menubar);
235 static void menubar_execute (WMenu *menubar, int entry)
237 const Menu *menu = menubar->menu [menubar->selected];
238 const callfn call_back = menu->entries [entry].call_back;
240 is_right = menubar->selected != 0;
242 /* This used to be the other way round, i.e. first callback and
243 then menubar_finish. The new order (hack?) is needed to make
244 change_panel () work which is used in quick_view_cmd () -- Norbert
246 menubar_finish (menubar);
247 (*call_back) ();
248 do_refresh ();
251 static void menubar_move (WMenu *menubar, int step)
253 const Menu *menu = menubar->menu [menubar->selected];
255 menubar_paint_idx (menubar, menubar->subsel, MENU_ENTRY_COLOR);
256 do {
257 menubar->subsel += step;
258 if (menubar->subsel < 0)
259 menubar->subsel = menu->count - 1;
261 menubar->subsel %= menu->count;
262 } while (!menu->entries [menubar->subsel].call_back);
263 menubar_paint_idx (menubar, menubar->subsel, MENU_SELECTED_COLOR);
266 static int menubar_handle_key (WMenu *menubar, int key)
268 int i;
270 /* Lowercase */
271 if (isascii (key)) key = g_ascii_tolower (key);
273 if (is_abort_char (key)){
274 menubar_finish (menubar);
275 return 1;
278 if (key == KEY_F(1)) {
279 if (menubar->dropped) {
280 interactive_display (NULL,
281 (menubar->menu [menubar->selected])->help_node);
282 } else {
283 interactive_display (NULL, "[Menu Bar]");
285 menubar_draw (menubar);
286 return 1;
289 if (key == KEY_LEFT || key == XCTRL('b')){
290 menubar_left (menubar);
291 return 1;
292 } else if (key == KEY_RIGHT || key == XCTRL ('f')){
293 menubar_right (menubar);
294 return 1;
297 if (!menubar->dropped){
298 const int items = menubar->items;
299 for (i = 0; i < items; i++) {
300 const Menu *menu = menubar->menu [i];
302 if (menu->text.hotkey != NULL) {
303 if (g_ascii_tolower(menu->text.hotkey[0]) == key) {
304 menubar_drop (menubar, i);
305 return 1;
309 if (key == KEY_ENTER || key == XCTRL ('n')
310 || key == KEY_DOWN || key == '\n') {
312 menubar_drop (menubar, menubar->selected);
313 return 1;
315 return 1;
316 } else {
317 const int selected = menubar->selected;
318 const Menu *menu = menubar->menu [selected];
319 const int items = menu->count;
321 for (i = 0; i < items; i++) {
322 if (!menu->entries [i].call_back)
323 continue;
325 if (menu->entries[i].text.hotkey != NULL) {
326 if (key != g_ascii_tolower (menu->entries[i].text.hotkey[0]))
327 continue;
329 menubar_execute (menubar, i);
330 return 1;
334 if (key == KEY_ENTER || key == '\n') {
335 menubar_execute (menubar, menubar->subsel);
336 return 1;
340 if (key == KEY_DOWN || key == XCTRL ('n'))
341 menubar_move (menubar, 1);
343 if (key == KEY_UP || key == XCTRL ('p'))
344 menubar_move (menubar, -1);
346 return 0;
349 static cb_ret_t
350 menubar_callback (Widget *w, widget_msg_t msg, int parm)
352 WMenu *menubar = (WMenu *) w;
354 switch (msg) {
355 /* We do not want the focus unless we have been activated */
356 case WIDGET_FOCUS:
357 if (!menubar->active)
358 return MSG_NOT_HANDLED;
360 widget_want_cursor (menubar->widget, 1);
362 /* Trick to get all the mouse events */
363 menubar->widget.lines = LINES;
365 /* Trick to get all of the hotkeys */
366 widget_want_hotkey (menubar->widget, 1);
367 menubar->subsel = 0;
368 menubar_drop_compute (menubar);
369 menubar_draw (menubar);
370 return MSG_HANDLED;
372 /* We don't want the buttonbar to activate while using the menubar */
373 case WIDGET_HOTKEY:
374 case WIDGET_KEY:
375 if (menubar->active) {
376 menubar_handle_key (menubar, parm);
377 return MSG_HANDLED;
378 } else
379 return MSG_NOT_HANDLED;
381 case WIDGET_CURSOR:
382 /* Put the cursor in a suitable place */
383 return MSG_NOT_HANDLED;
385 case WIDGET_UNFOCUS:
386 if (menubar->active)
387 return MSG_NOT_HANDLED;
388 else {
389 widget_want_cursor (menubar->widget, 0);
390 return MSG_HANDLED;
393 case WIDGET_DRAW:
394 if (menubar_visible)
395 menubar_draw (menubar);
396 else
397 /* try show menu after screen resize */
398 send_message (w, WIDGET_FOCUS, 0);
399 return MSG_HANDLED;
401 default:
402 return default_proc (msg, parm);
406 static int
407 menubar_event (Gpm_Event *event, void *data)
409 WMenu *menubar = data;
410 int was_active;
411 int new_selection;
412 int left_x, right_x, bottom_y;
414 if (!(event->type & (GPM_UP|GPM_DOWN|GPM_DRAG)))
415 return MOU_NORMAL;
417 if (!menubar->dropped){
418 menubar->previous_widget = menubar->widget.parent->current->dlg_id;
419 menubar->active = 1;
420 menubar->dropped = 1;
421 was_active = 0;
422 } else
423 was_active = 1;
425 /* Mouse operations on the menubar */
426 if (event->y == 1 || !was_active){
427 if (event->type & GPM_UP)
428 return MOU_NORMAL;
430 new_selection = 0;
431 while (new_selection < menubar->items
432 && event->x > menubar->menu[new_selection]->start_x
434 new_selection++;
436 if (new_selection) /* Don't set the invalid value -1 */
437 --new_selection;
439 if (!was_active){
440 menubar->selected = new_selection;
441 dlg_select_widget (menubar);
442 menubar_drop_compute (menubar);
443 menubar_draw (menubar);
444 return MOU_NORMAL;
447 menubar_remove (menubar);
449 menubar->selected = new_selection;
451 menubar_drop_compute (menubar);
452 menubar_draw (menubar);
453 return MOU_NORMAL;
456 if (!menubar->dropped)
457 return MOU_NORMAL;
459 /* Ignore the events on anything below the third line */
460 if (event->y <= 2)
461 return MOU_NORMAL;
463 /* Else, the mouse operation is on the menus or it is not */
464 left_x = menubar->menu[menubar->selected]->start_x;
465 right_x = left_x + menubar->max_entry_len + 4;
466 if (right_x > menubar->widget.cols)
468 left_x = menubar->widget.cols - menubar->max_entry_len - 3;
469 right_x = menubar->widget.cols - 1;
472 bottom_y = (menubar->menu [menubar->selected])->count + 3;
474 if ((event->x > left_x) && (event->x < right_x) && (event->y < bottom_y)){
475 int pos = event->y - 3;
477 if (!menubar->menu [menubar->selected]->entries [pos].call_back)
478 return MOU_NORMAL;
480 menubar_paint_idx (menubar, menubar->subsel, MENU_ENTRY_COLOR);
481 menubar->subsel = pos;
482 menubar_paint_idx (menubar, menubar->subsel, MENU_SELECTED_COLOR);
484 if (event->type & GPM_UP)
485 menubar_execute (menubar, pos);
486 } else
487 if (event->type & GPM_DOWN)
488 menubar_finish (menubar);
490 return MOU_NORMAL;
494 * Properly space menubar items. Should be called when menubar is created
495 * and also when widget width is changed (i.e. upon xterm resize).
497 void
498 menubar_arrange(WMenu* menubar)
500 register int i, start_x = 1;
501 int items = menubar->items;
503 #ifndef RESIZABLE_MENUBAR
504 int gap = 3;
506 for (i = 0; i < items; i++)
508 int len = hotkey_width (menubar->menu[i]->text);
509 menubar->menu[i]->start_x = start_x;
510 start_x += len + gap;
513 #else /* RESIZABLE_MENUBAR */
515 int gap = menubar->widget.cols - 2;
517 /* First, calculate gap between items... */
518 for (i = 0; i < items; i++)
520 /* preserve length here, to be used below */
521 gap -= (menubar->menu[i]->start_x = hotkey_width (menubar->menu[i]->text));
524 gap /= (items - 1);
526 if (gap <= 0)
528 /* We are out of luck - window is too narrow... */
529 gap = 1;
532 /* ...and now fix start positions of menubar items */
533 for (i = 0; i < items; i++)
535 int len = menubar->menu[i]->start_x;
536 menubar->menu[i]->start_x = start_x;
537 start_x += len + gap;
539 #endif /* RESIZABLE_MENUBAR */
542 void
543 destroy_menu (Menu *menu)
545 release_hotkey (menu->text);
546 if (menu->entries != NULL) {
547 int me;
548 for (me = 0; me < menu->count; me++) {
549 if (menu->entries[me].label[0] != '\0') {
550 release_hotkey (menu->entries[me].text);
555 g_free (menu->help_node);
556 g_free (menu);
559 WMenu *
560 menubar_new (int y, int x, int cols, Menu *menu[], int items)
562 WMenu *menubar = g_new0 (WMenu, 1);
564 init_widget (&menubar->widget, y, x, 1, cols,
565 menubar_callback, menubar_event);
566 menubar->menu = menu;
567 menubar->active = 0;
568 menubar->dropped = 0;
569 menubar->items = items;
570 menubar->selected = 0;
571 menubar->subsel = 0;
572 widget_want_cursor (menubar->widget, 0);
573 menubar_arrange (menubar);
575 return menubar;