De-inline a few functions which are large
[midnight-commander.git] / src / menu.c
blob1edd68d481561186e538759305f1b3078d57ab03
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/tty/color.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 if (!tty_is_slow ()) {
94 widget_move (&menubar->widget, y, x - 1);
95 tty_print_alt_char (ACS_LTEE);
98 tty_draw_hline (menubar->widget.y + y, menubar->widget.x + x,
99 tty_is_slow () ? ' ' : ACS_HLINE, menubar->max_entry_len + 2);
101 if (!tty_is_slow ()) {
102 widget_move (&menubar->widget, y, x + menubar->max_entry_len + 2);
103 tty_print_alt_char (ACS_RTEE);
105 } else {
106 /* menu text */
107 tty_setcolor (color);
108 widget_move (&menubar->widget, y, x);
109 tty_print_char ((unsigned char) entry->first_letter);
110 tty_draw_hline (-1, -1, ' ', menubar->max_entry_len + 1); /* clear line */
111 tty_print_string (entry->text.start);
113 if (entry->text.hotkey != NULL) {
114 tty_setcolor (color == MENU_SELECTED_COLOR ?
115 MENU_HOTSEL_COLOR : MENU_HOT_COLOR);
116 tty_print_string (entry->text.hotkey);
117 tty_setcolor(color);
120 if (entry->text.end != NULL)
121 tty_print_string (entry->text.end);
123 /* move cursor to the start of entry text */
124 widget_move (&menubar->widget, y, x + 1);
128 static void menubar_draw_drop (WMenu *menubar)
130 const int count = menubar->menu [menubar->selected]->count;
131 int column = menubar->menu [menubar->selected]->start_x - 1;
132 int i;
134 if (column + menubar->max_entry_len + 4 > menubar->widget.cols)
135 column = menubar->widget.cols - menubar->max_entry_len - 4;
137 tty_setcolor (MENU_ENTRY_COLOR);
138 draw_box (menubar->widget.parent,
139 menubar->widget.y + 1, menubar->widget.x + column,
140 count + 2, menubar->max_entry_len + 4);
142 /* draw items except selected */
143 for (i = 0; i < count; i++)
144 if (i != menubar->subsel)
145 menubar_paint_idx (menubar, i, MENU_ENTRY_COLOR);
147 /* draw selected item at last to move cursot to the nice location */
148 menubar_paint_idx (menubar, menubar->subsel, MENU_SELECTED_COLOR);
151 static void menubar_set_color (WMenu *menubar, int current, gboolean hotkey)
153 if (!menubar->active)
154 tty_setcolor (hotkey ? COLOR_HOT_FOCUS : SELECTED_COLOR);
155 else if (current == menubar->selected)
156 tty_setcolor (hotkey ? MENU_HOTSEL_COLOR : MENU_SELECTED_COLOR);
157 else
158 tty_setcolor (hotkey ? MENU_HOT_COLOR : MENU_ENTRY_COLOR);
161 static void menubar_draw (WMenu *menubar)
163 const int items = menubar->items;
164 int i;
166 /* First draw the complete menubar */
167 tty_setcolor (menubar->active ? MENU_ENTRY_COLOR : SELECTED_COLOR);
168 tty_draw_hline (menubar->widget.y, menubar->widget.x, ' ', menubar->widget.cols);
170 /* Now each one of the entries */
171 for (i = 0; i < items; i++){
172 menubar_set_color (menubar, i, FALSE);
173 widget_move (&menubar->widget, 0, menubar->menu [i]->start_x);
175 tty_print_string (menubar->menu[i]->text.start);
177 if (menubar->menu[i]->text.hotkey != NULL) {
178 menubar_set_color (menubar, i, TRUE);
179 tty_print_string (menubar->menu[i]->text.hotkey);
180 menubar_set_color (menubar, i, FALSE);
182 if (menubar->menu[i]->text.end != NULL)
183 tty_print_string (menubar->menu[i]->text.end);
186 if (menubar->dropped)
187 menubar_draw_drop (menubar);
188 else
189 widget_move (&menubar->widget, 0,
190 menubar-> menu[menubar->selected]->start_x);
193 static void menubar_remove (WMenu *menubar)
195 menubar->subsel = 0;
196 if (menubar->dropped) {
197 menubar->dropped = 0;
198 do_refresh ();
199 menubar->dropped = 1;
203 static void menubar_left (WMenu *menu)
205 menubar_remove (menu);
206 menu->selected = (menu->selected - 1) % menu->items;
207 if (menu->selected < 0)
208 menu->selected = menu->items -1;
209 menubar_drop_compute (menu);
210 menubar_draw (menu);
213 static void menubar_right (WMenu *menu)
215 menubar_remove (menu);
216 menu->selected = (menu->selected + 1) % menu->items;
217 menubar_drop_compute (menu);
218 menubar_draw (menu);
221 static void
222 menubar_finish (WMenu *menubar)
224 menubar->dropped = 0;
225 menubar->active = 0;
226 menubar->widget.lines = 1;
227 widget_want_hotkey (menubar->widget, 0);
229 dlg_select_by_id (menubar->widget.parent, menubar->previous_widget);
230 do_refresh ();
233 static void menubar_drop (WMenu *menubar, int selected)
235 menubar->dropped = 1;
236 menubar->selected = selected;
237 menubar->subsel = 0;
238 menubar_drop_compute (menubar);
239 menubar_draw (menubar);
242 static void menubar_execute (WMenu *menubar, int entry)
244 const Menu *menu = menubar->menu [menubar->selected];
245 const callfn call_back = menu->entries [entry].call_back;
247 is_right = menubar->selected != 0;
249 /* This used to be the other way round, i.e. first callback and
250 then menubar_finish. The new order (hack?) is needed to make
251 change_panel () work which is used in quick_view_cmd () -- Norbert
253 menubar_finish (menubar);
254 (*call_back) ();
255 do_refresh ();
258 static void menubar_move (WMenu *menubar, int step)
260 const Menu *menu = menubar->menu [menubar->selected];
262 menubar_paint_idx (menubar, menubar->subsel, MENU_ENTRY_COLOR);
263 do {
264 menubar->subsel += step;
265 if (menubar->subsel < 0)
266 menubar->subsel = menu->count - 1;
268 menubar->subsel %= menu->count;
269 } while (!menu->entries [menubar->subsel].call_back);
270 menubar_paint_idx (menubar, menubar->subsel, MENU_SELECTED_COLOR);
273 static int menubar_handle_key (WMenu *menubar, int key)
275 int i;
277 /* Lowercase */
278 if (isascii (key)) key = g_ascii_tolower (key);
280 if (is_abort_char (key)){
281 menubar_finish (menubar);
282 return 1;
285 if (key == KEY_F(1)) {
286 if (menubar->dropped) {
287 interactive_display (NULL,
288 (menubar->menu [menubar->selected])->help_node);
289 } else {
290 interactive_display (NULL, "[Menu Bar]");
292 menubar_draw (menubar);
293 return 1;
296 if (key == KEY_LEFT || key == XCTRL('b')){
297 menubar_left (menubar);
298 return 1;
299 } else if (key == KEY_RIGHT || key == XCTRL ('f')){
300 menubar_right (menubar);
301 return 1;
304 if (!menubar->dropped){
305 const int items = menubar->items;
306 for (i = 0; i < items; i++) {
307 const Menu *menu = menubar->menu [i];
309 if (menu->text.hotkey != NULL) {
310 if (g_ascii_tolower(menu->text.hotkey[0]) == key) {
311 menubar_drop (menubar, i);
312 return 1;
316 if (key == KEY_ENTER || key == XCTRL ('n')
317 || key == KEY_DOWN || key == '\n') {
319 menubar_drop (menubar, menubar->selected);
320 return 1;
322 return 1;
323 } else {
324 const int selected = menubar->selected;
325 const Menu *menu = menubar->menu [selected];
326 const int items = menu->count;
328 for (i = 0; i < items; i++) {
329 if (!menu->entries [i].call_back)
330 continue;
332 if (menu->entries[i].text.hotkey != NULL) {
333 if (key != g_ascii_tolower (menu->entries[i].text.hotkey[0]))
334 continue;
336 menubar_execute (menubar, i);
337 return 1;
341 if (key == KEY_ENTER || key == '\n') {
342 menubar_execute (menubar, menubar->subsel);
343 return 1;
347 if (key == KEY_DOWN || key == XCTRL ('n'))
348 menubar_move (menubar, 1);
350 if (key == KEY_UP || key == XCTRL ('p'))
351 menubar_move (menubar, -1);
353 return 0;
356 static cb_ret_t
357 menubar_callback (Widget *w, widget_msg_t msg, int parm)
359 WMenu *menubar = (WMenu *) w;
361 switch (msg) {
362 /* We do not want the focus unless we have been activated */
363 case WIDGET_FOCUS:
364 if (!menubar->active)
365 return MSG_NOT_HANDLED;
367 widget_want_cursor (menubar->widget, 1);
369 /* Trick to get all the mouse events */
370 menubar->widget.lines = LINES;
372 /* Trick to get all of the hotkeys */
373 widget_want_hotkey (menubar->widget, 1);
374 menubar->subsel = 0;
375 menubar_drop_compute (menubar);
376 menubar_draw (menubar);
377 return MSG_HANDLED;
379 /* We don't want the buttonbar to activate while using the menubar */
380 case WIDGET_HOTKEY:
381 case WIDGET_KEY:
382 if (menubar->active) {
383 menubar_handle_key (menubar, parm);
384 return MSG_HANDLED;
385 } else
386 return MSG_NOT_HANDLED;
388 case WIDGET_CURSOR:
389 /* Put the cursor in a suitable place */
390 return MSG_NOT_HANDLED;
392 case WIDGET_UNFOCUS:
393 if (menubar->active)
394 return MSG_NOT_HANDLED;
395 else {
396 widget_want_cursor (menubar->widget, 0);
397 return MSG_HANDLED;
400 case WIDGET_DRAW:
401 if (menubar_visible) {
402 menubar_draw (menubar);
403 return MSG_HANDLED;
405 /* fall through */
407 case WIDGET_RESIZED:
408 /* try show menu after screen resize */
409 send_message (w, WIDGET_FOCUS, 0);
410 return MSG_HANDLED;
412 default:
413 return default_proc (msg, parm);
417 static int
418 menubar_event (Gpm_Event *event, void *data)
420 WMenu *menubar = data;
421 int was_active;
422 int new_selection;
423 int left_x, right_x, bottom_y;
425 if (!(event->type & (GPM_UP|GPM_DOWN|GPM_DRAG)))
426 return MOU_NORMAL;
428 if (!menubar->dropped){
429 menubar->previous_widget = menubar->widget.parent->current->dlg_id;
430 menubar->active = 1;
431 menubar->dropped = 1;
432 was_active = 0;
433 } else
434 was_active = 1;
436 /* Mouse operations on the menubar */
437 if (event->y == 1 || !was_active){
438 if (event->type & GPM_UP)
439 return MOU_NORMAL;
441 new_selection = 0;
442 while (new_selection < menubar->items
443 && event->x > menubar->menu[new_selection]->start_x
445 new_selection++;
447 if (new_selection) /* Don't set the invalid value -1 */
448 --new_selection;
450 if (!was_active){
451 menubar->selected = new_selection;
452 dlg_select_widget (menubar);
453 menubar_drop_compute (menubar);
454 menubar_draw (menubar);
455 return MOU_NORMAL;
458 menubar_remove (menubar);
460 menubar->selected = new_selection;
462 menubar_drop_compute (menubar);
463 menubar_draw (menubar);
464 return MOU_NORMAL;
467 if (!menubar->dropped)
468 return MOU_NORMAL;
470 /* Ignore the events on anything below the third line */
471 if (event->y <= 2)
472 return MOU_NORMAL;
474 /* Else, the mouse operation is on the menus or it is not */
475 left_x = menubar->menu[menubar->selected]->start_x;
476 right_x = left_x + menubar->max_entry_len + 4;
477 if (right_x > menubar->widget.cols)
479 left_x = menubar->widget.cols - menubar->max_entry_len - 3;
480 right_x = menubar->widget.cols - 1;
483 bottom_y = (menubar->menu [menubar->selected])->count + 3;
485 if ((event->x > left_x) && (event->x < right_x) && (event->y < bottom_y)){
486 int pos = event->y - 3;
488 if (!menubar->menu [menubar->selected]->entries [pos].call_back)
489 return MOU_NORMAL;
491 menubar_paint_idx (menubar, menubar->subsel, MENU_ENTRY_COLOR);
492 menubar->subsel = pos;
493 menubar_paint_idx (menubar, menubar->subsel, MENU_SELECTED_COLOR);
495 if (event->type & GPM_UP)
496 menubar_execute (menubar, pos);
497 } else
498 if (event->type & GPM_DOWN)
499 menubar_finish (menubar);
501 return MOU_NORMAL;
505 * Properly space menubar items. Should be called when menubar is created
506 * and also when widget width is changed (i.e. upon xterm resize).
508 void
509 menubar_arrange(WMenu* menubar)
511 register int i, start_x = 1;
512 int items = menubar->items;
514 #ifndef RESIZABLE_MENUBAR
515 int gap = 3;
517 for (i = 0; i < items; i++)
519 int len = hotkey_width (menubar->menu[i]->text);
520 menubar->menu[i]->start_x = start_x;
521 start_x += len + gap;
524 #else /* RESIZABLE_MENUBAR */
526 int gap = menubar->widget.cols - 2;
528 /* First, calculate gap between items... */
529 for (i = 0; i < items; i++)
531 /* preserve length here, to be used below */
532 gap -= (menubar->menu[i]->start_x = hotkey_width (menubar->menu[i]->text));
535 gap /= (items - 1);
537 if (gap <= 0)
539 /* We are out of luck - window is too narrow... */
540 gap = 1;
543 /* ...and now fix start positions of menubar items */
544 for (i = 0; i < items; i++)
546 int len = menubar->menu[i]->start_x;
547 menubar->menu[i]->start_x = start_x;
548 start_x += len + gap;
550 #endif /* RESIZABLE_MENUBAR */
553 void
554 destroy_menu (Menu *menu)
556 release_hotkey (menu->text);
557 if (menu->entries != NULL) {
558 int me;
559 for (me = 0; me < menu->count; me++) {
560 if (menu->entries[me].label[0] != '\0') {
561 release_hotkey (menu->entries[me].text);
566 g_free (menu->help_node);
567 g_free (menu);
570 WMenu *
571 menubar_new (int y, int x, int cols, Menu *menu[], int items)
573 WMenu *menubar = g_new0 (WMenu, 1);
575 init_widget (&menubar->widget, y, x, 1, cols,
576 menubar_callback, menubar_event);
577 menubar->menu = menu;
578 menubar->active = 0;
579 menubar->dropped = 0;
580 menubar->items = items;
581 menubar->selected = 0;
582 menubar->subsel = 0;
583 widget_want_cursor (menubar->widget, 0);
584 menubar_arrange (menubar);
586 return menubar;