Forgot to remove some more .s strings and do a rename in order to prevent compiler...
[midnight-commander.git] / src / menu.c
blobba25bf7c6f9148d67f78f4ee7e670cf3feeb2418
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 #include <config.h>
21 #include <ctype.h>
22 #include <stdarg.h>
23 #include <string.h>
24 #include <sys/types.h>
26 #include <mhl/memory.h>
27 #include <mhl/string.h>
29 #include "global.h"
30 #include "tty.h"
31 #include "menu.h"
32 #include "help.h"
33 #include "dialog.h"
34 #include "color.h"
35 #include "main.h"
36 #include "mouse.h"
37 #include "win.h"
38 #include "key.h" /* For mi_getch() */
40 int menubar_visible = 1; /* This is the new default */
42 static void
43 menu_scan_hotkey (Menu *menu)
45 char *cp = strchr (menu->name, '&');
47 if (cp != NULL && cp[1] != '\0') {
48 g_strlcpy (cp, cp + 1, strlen (cp));
49 menu->hotkey = tolower ((unsigned char) *cp);
50 } else
51 menu->hotkey = 0;
54 Menu *
55 create_menu (const char *name, menu_entry *entries, int count, const char *help_node)
57 Menu *menu;
58 const char *cp;
60 menu = (Menu *) g_malloc (sizeof (*menu));
61 menu->count = count;
62 menu->max_entry_len = 20;
63 menu->entries = entries;
65 if (entries != (menu_entry*) NULL) {
66 register menu_entry* mp;
67 for (mp = entries; count--; mp++) {
68 if (mp->text[0] != '\0') {
69 #ifdef ENABLE_NLS
70 mp->text = _(mp->text);
71 #endif /* ENABLE_NLS */
72 cp = strchr (mp->text,'&');
74 if (cp != NULL && *(cp+1) != '\0') {
75 mp->hot_key = tolower ((unsigned char) *(cp+1));
76 menu->max_entry_len = max ((int) (strlen (mp->text) - 1),
77 menu->max_entry_len);
78 } else {
79 menu->max_entry_len = max ((int) strlen (mp->text),
80 menu->max_entry_len);
86 menu->name = g_strdup (name);
87 menu_scan_hotkey(menu);
88 menu->start_x = 0;
89 menu->help_node = g_strdup (help_node);
90 return menu;
93 static void menubar_drop_compute (WMenu *menubar)
95 menubar->max_entry_len = menubar->menu [menubar->selected]->max_entry_len;
98 static void menubar_paint_idx (WMenu *menubar, int idx, int color)
100 const Menu *menu = menubar->menu [menubar->selected];
101 const int y = 2 + idx;
102 int x = menubar-> menu[menubar->selected]->start_x;
104 if (x + menubar->max_entry_len + 3 > menubar->widget.cols)
105 x = menubar->widget.cols - menubar->max_entry_len - 3;
107 widget_move (&menubar->widget, y, x);
108 attrset (color);
109 hline (' ', menubar->max_entry_len+2);
110 if (!*menu->entries [idx].text) {
111 attrset (SELECTED_COLOR);
112 widget_move (&menubar->widget, y, x + 1);
113 hline (slow_terminal ? ' ' : ACS_HLINE, menubar->max_entry_len);
114 } else {
115 const char *text;
117 addch((unsigned char)menu->entries [idx].first_letter);
118 for (text = menu->entries [idx].text; *text; text++)
120 if (*text != '&')
121 addch(*text);
122 else {
123 attrset (color == MENU_SELECTED_COLOR ?
124 MENU_HOTSEL_COLOR : MENU_HOT_COLOR);
125 addch(*(++text));
126 attrset(color);
130 widget_move (&menubar->widget, y, x + 1);
133 static inline void menubar_draw_drop (WMenu *menubar)
135 const int count = (menubar->menu [menubar->selected])->count;
136 int i;
137 int sel = menubar->subsel;
138 int column = menubar-> menu[menubar->selected]->start_x - 1;
140 if (column + menubar->max_entry_len + 4 > menubar->widget.cols)
141 column = menubar->widget.cols - menubar->max_entry_len - 4;
143 attrset (SELECTED_COLOR);
144 draw_box (menubar->widget.parent,
145 menubar->widget.y+1, menubar->widget.x + column,
146 count+2, menubar->max_entry_len + 4);
148 column++;
149 for (i = 0; i < count; i++){
150 if (i == sel)
151 continue;
152 menubar_paint_idx (menubar, i, MENU_ENTRY_COLOR);
154 menubar_paint_idx (menubar, sel, MENU_SELECTED_COLOR);
157 static void menubar_draw (WMenu *menubar)
159 const int items = menubar->items;
160 int i;
162 /* First draw the complete menubar */
163 attrset (SELECTED_COLOR);
164 widget_move (&menubar->widget, 0, 0);
166 hline (' ', menubar->widget.cols);
168 attrset (SELECTED_COLOR);
169 /* Now each one of the entries */
170 for (i = 0; i < items; i++){
171 if (menubar->active)
172 attrset(i == menubar->selected?MENU_SELECTED_COLOR:SELECTED_COLOR);
173 widget_move (&menubar->widget, 0, menubar->menu [i]->start_x);
174 tty_printf ("%s", menubar->menu [i]->name);
177 if (menubar->dropped)
178 menubar_draw_drop (menubar);
179 else
180 widget_move (&menubar->widget, 0,
181 menubar-> menu[menubar->selected]->start_x);
184 static inline void menubar_remove (WMenu *menubar)
186 menubar->subsel = 0;
187 if (menubar->dropped){
188 menubar->dropped = 0;
189 do_refresh ();
190 menubar->dropped = 1;
194 static void menubar_left (WMenu *menu)
196 menubar_remove (menu);
197 menu->selected = (menu->selected - 1) % menu->items;
198 if (menu->selected < 0)
199 menu->selected = menu->items -1;
200 menubar_drop_compute (menu);
201 menubar_draw (menu);
204 static void menubar_right (WMenu *menu)
206 menubar_remove (menu);
207 menu->selected = (menu->selected + 1) % menu->items;
208 menubar_drop_compute (menu);
209 menubar_draw (menu);
212 static void
213 menubar_finish (WMenu *menubar)
215 menubar->dropped = 0;
216 menubar->active = 0;
217 menubar->widget.lines = 1;
218 widget_want_hotkey (menubar->widget, 0);
220 dlg_select_by_id (menubar->widget.parent, menubar->previous_widget);
221 do_refresh ();
224 static void menubar_drop (WMenu *menubar, int selected)
226 menubar->dropped = 1;
227 menubar->selected = selected;
228 menubar->subsel = 0;
229 menubar_drop_compute (menubar);
230 menubar_draw (menubar);
233 static void menubar_execute (WMenu *menubar, int entry)
235 const Menu *menu = menubar->menu [menubar->selected];
236 const callfn call_back = menu->entries [entry].call_back;
238 is_right = menubar->selected != 0;
240 /* This used to be the other way round, i.e. first callback and
241 then menubar_finish. The new order (hack?) is needed to make
242 change_panel () work which is used in quick_view_cmd () -- Norbert
244 menubar_finish (menubar);
245 (*call_back) ();
246 do_refresh ();
249 static void menubar_move (WMenu *menubar, int step)
251 const Menu *menu = menubar->menu [menubar->selected];
253 menubar_paint_idx (menubar, menubar->subsel, MENU_ENTRY_COLOR);
254 do {
255 menubar->subsel += step;
256 if (menubar->subsel < 0)
257 menubar->subsel = menu->count - 1;
259 menubar->subsel %= menu->count;
260 } while (!menu->entries [menubar->subsel].call_back);
261 menubar_paint_idx (menubar, menubar->subsel, MENU_SELECTED_COLOR);
264 static int menubar_handle_key (WMenu *menubar, int key)
266 int i;
268 /* Lowercase */
269 if (key < 256 && isalpha (key)) /* Linux libc.so.5.x.x bug fix */
270 key = tolower (key);
272 if (is_abort_char (key)){
273 menubar_finish (menubar);
274 return 1;
277 if (key == KEY_F(1)) {
278 if (menubar->dropped) {
279 interactive_display (NULL,
280 (menubar->menu [menubar->selected])->help_node);
281 } else {
282 interactive_display (NULL, "[Menu Bar]");
284 menubar_draw (menubar);
285 return 1;
288 if (key == KEY_LEFT || key == XCTRL('b')){
289 menubar_left (menubar);
290 return 1;
291 } else if (key == KEY_RIGHT || key == XCTRL ('f')){
292 menubar_right (menubar);
293 return 1;
296 if (!menubar->dropped){
297 const int items = menubar->items;
298 for (i = 0; i < items; i++){
299 const Menu *menu = menubar->menu [i];
301 if (menu->hotkey == key){
302 menubar_drop (menubar, i);
303 return 1;
306 if (key == KEY_ENTER || key == XCTRL ('n') || key == KEY_DOWN
307 || key == '\n'){
308 menubar_drop (menubar, menubar->selected);
309 return 1;
311 return 1;
312 } else {
313 const int selected = menubar->selected;
314 const Menu *menu = menubar->menu [selected];
315 const int items = menu->count;
317 for (i = 0; i < items; i++){
318 if (!menu->entries [i].call_back)
319 continue;
321 if (key != menu->entries [i].hot_key)
322 continue;
324 menubar_execute (menubar, i);
325 return 1;
328 if (key == KEY_ENTER || key == '\n'){
329 menubar_execute (menubar, menubar->subsel);
330 return 1;
334 if (key == KEY_DOWN || key == XCTRL ('n'))
335 menubar_move (menubar, 1);
337 if (key == KEY_UP || key == XCTRL ('p'))
338 menubar_move (menubar, -1);
340 return 0;
343 static cb_ret_t
344 menubar_callback (Widget *w, widget_msg_t msg, int parm)
346 WMenu *menubar = (WMenu *) w;
348 switch (msg) {
349 /* We do not want the focus unless we have been activated */
350 case WIDGET_FOCUS:
351 if (!menubar->active)
352 return MSG_NOT_HANDLED;
354 widget_want_cursor (menubar->widget, 1);
356 /* Trick to get all the mouse events */
357 menubar->widget.lines = LINES;
359 /* Trick to get all of the hotkeys */
360 widget_want_hotkey (menubar->widget, 1);
361 menubar->subsel = 0;
362 menubar_drop_compute (menubar);
363 menubar_draw (menubar);
364 return MSG_HANDLED;
366 /* We don't want the buttonbar to activate while using the menubar */
367 case WIDGET_HOTKEY:
368 case WIDGET_KEY:
369 if (menubar->active) {
370 menubar_handle_key (menubar, parm);
371 return MSG_HANDLED;
372 } else
373 return MSG_NOT_HANDLED;
375 case WIDGET_CURSOR:
376 /* Put the cursor in a suitable place */
377 return MSG_NOT_HANDLED;
379 case WIDGET_UNFOCUS:
380 if (menubar->active)
381 return MSG_NOT_HANDLED;
382 else {
383 widget_want_cursor (menubar->widget, 0);
384 return MSG_HANDLED;
387 case WIDGET_DRAW:
388 if (menubar_visible)
389 menubar_draw (menubar);
390 return MSG_HANDLED;
392 default:
393 return default_proc (msg, parm);
397 static int
398 menubar_event (Gpm_Event *event, void *data)
400 WMenu *menubar = data;
401 int was_active;
402 int new_selection;
403 int left_x, right_x, bottom_y;
405 if (!(event->type & (GPM_UP|GPM_DOWN|GPM_DRAG)))
406 return MOU_NORMAL;
408 if (!menubar->dropped){
409 menubar->previous_widget = menubar->widget.parent->current->dlg_id;
410 menubar->active = 1;
411 menubar->dropped = 1;
412 was_active = 0;
413 } else
414 was_active = 1;
416 /* Mouse operations on the menubar */
417 if (event->y == 1 || !was_active){
418 if (event->type & GPM_UP)
419 return MOU_NORMAL;
421 new_selection = 0;
422 while (new_selection < menubar->items
423 && event->x > menubar->menu[new_selection]->start_x
425 new_selection++;
427 if (new_selection) /* Don't set the invalid value -1 */
428 --new_selection;
430 if (!was_active){
431 menubar->selected = new_selection;
432 dlg_select_widget (menubar);
433 menubar_drop_compute (menubar);
434 menubar_draw (menubar);
435 return MOU_NORMAL;
438 menubar_remove (menubar);
440 menubar->selected = new_selection;
442 menubar_drop_compute (menubar);
443 menubar_draw (menubar);
444 return MOU_NORMAL;
447 if (!menubar->dropped)
448 return MOU_NORMAL;
450 /* Ignore the events on anything below the third line */
451 if (event->y <= 2)
452 return MOU_NORMAL;
454 /* Else, the mouse operation is on the menus or it is not */
455 left_x = menubar->menu[menubar->selected]->start_x;
456 right_x = left_x + menubar->max_entry_len + 4;
457 if (right_x > menubar->widget.cols)
459 left_x = menubar->widget.cols - menubar->max_entry_len - 3;
460 right_x = menubar->widget.cols - 1;
463 bottom_y = (menubar->menu [menubar->selected])->count + 3;
465 if ((event->x > left_x) && (event->x < right_x) && (event->y < bottom_y)){
466 int pos = event->y - 3;
468 if (!menubar->menu [menubar->selected]->entries [pos].call_back)
469 return MOU_NORMAL;
471 menubar_paint_idx (menubar, menubar->subsel, MENU_ENTRY_COLOR);
472 menubar->subsel = pos;
473 menubar_paint_idx (menubar, menubar->subsel, MENU_SELECTED_COLOR);
475 if (event->type & GPM_UP)
476 menubar_execute (menubar, pos);
477 } else
478 if (event->type & GPM_DOWN)
479 menubar_finish (menubar);
481 return MOU_NORMAL;
485 * Properly space menubar items. Should be called when menubar is created
486 * and also when widget width is changed (i.e. upon xterm resize).
488 void
489 menubar_arrange(WMenu* menubar)
491 register int i, start_x = 1;
492 int items = menubar->items;
494 #ifndef RESIZABLE_MENUBAR
495 int gap = 3;
497 for (i = 0; i < items; i++)
499 int len = strlen(menubar->menu[i]->name);
500 menubar->menu[i]->start_x = start_x;
501 start_x += len + gap;
504 #else /* RESIZABLE_MENUBAR */
506 int gap = menubar->widget.cols - 2;
508 /* First, calculate gap between items... */
509 for (i = 0; i < items; i++)
511 /* preserve length here, to be used below */
512 gap -= (menubar->menu[i]->start_x = strlen(menubar->menu[i]->name));
515 gap /= (items - 1);
517 if (gap <= 0)
519 /* We are out of luck - window is too narrow... */
520 gap = 1;
523 /* ...and now fix start positions of menubar items */
524 for (i = 0; i < items; i++)
526 int len = menubar->menu[i]->start_x;
527 menubar->menu[i]->start_x = start_x;
528 start_x += len + gap;
530 #endif /* RESIZABLE_MENUBAR */
533 void
534 destroy_menu (Menu *menu)
536 g_free (menu->name);
537 g_free (menu->help_node);
538 g_free (menu);
541 WMenu *
542 menubar_new (int y, int x, int cols, Menu *menu[], int items)
544 WMenu *menubar = g_new0 (WMenu, 1); /* FIXME: subsel used w/o being set */
546 init_widget (&menubar->widget, y, x, 1, cols,
547 menubar_callback, menubar_event);
548 menubar->menu = menu;
549 menubar->active = 0;
550 menubar->dropped = 0;
551 menubar->items = items;
552 menubar->selected = 0;
553 widget_want_cursor (menubar->widget, 0);
554 menubar_arrange (menubar);
556 return menubar;