small highlighting correction...
[midnight-commander.git] / src / menu.c
blobfb41d4a9e4b919ee3f27ca6e3540bf8a935c9bb1
1 /* Pulldown menu code.
2 Copyright (C) 1994 Miguel de Icaza.
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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
18 #include <config.h>
19 #include <string.h>
20 #include <stdarg.h>
21 #include <sys/types.h>
22 #include <ctype.h>
23 #include "global.h"
24 #include "tty.h"
25 #include "menu.h"
26 #include "help.h"
27 #include "dialog.h"
28 #include "color.h"
29 #include "main.h"
30 #include "mouse.h"
31 #include "win.h"
32 #include "key.h" /* For mi_getch() */
34 /* "$Id$" */
36 extern int is_right;
37 int menubar_visible = 1; /* This is the new default */
39 static void menu_scan_hotkey(Menu menu)
41 char* cp = strchr (menu->name, '&');
43 if (cp != NULL && cp[1] != '\0'){
44 strcpy (cp, cp+1);
45 menu->hotkey = tolower(*cp);
47 else
48 menu->hotkey = 0;
51 Menu create_menu (char *name, menu_entry *entries, int count, char *help_node)
53 Menu menu;
54 char *cp;
56 menu = (Menu) g_malloc (sizeof (*menu));
57 menu->count = count;
58 menu->max_entry_len = 20;
59 menu->entries = entries;
61 if (entries != (menu_entry*) NULL) {
62 register menu_entry* mp;
63 for (mp = entries; count--; mp++) {
64 if (mp->text[0] != '\0') {
65 #ifdef ENABLE_NLS
66 mp->text = _(mp->text);
67 #endif /* ENABLE_NLS */
68 cp = strchr (mp->text,'&');
70 if (cp != NULL && *(cp+1) != '\0') {
71 mp->hot_key = tolower (*(cp+1));
72 menu->max_entry_len = max (strlen (mp->text) - 1,
73 menu->max_entry_len);
74 } else {
75 menu->max_entry_len = max (strlen (mp->text),
76 menu->max_entry_len);
82 menu->name = g_strdup (name);
83 menu_scan_hotkey(menu);
84 menu->start_x = 0;
85 menu->help_node = g_strdup (help_node);
86 return menu;
89 static void menubar_drop_compute (WMenu *menubar)
91 menubar->max_entry_len = menubar->menu [menubar->selected]->max_entry_len;
94 static void menubar_paint_idx (WMenu *menubar, int idx, int color)
96 const Menu menu = menubar->menu [menubar->selected];
97 const int y = 2 + idx;
98 int x = menubar-> menu[menubar->selected]->start_x;
100 if (x + menubar->max_entry_len + 3 > menubar->widget.cols)
101 x = menubar->widget.cols - menubar->max_entry_len - 3;
103 widget_move (&menubar->widget, y, x);
104 attrset (color);
105 hline (' ', menubar->max_entry_len+2);
106 if (!*menu->entries [idx].text) {
107 attrset (SELECTED_COLOR);
108 widget_move (&menubar->widget, y, x + 1);
109 hline (slow_terminal ? ' ' : ACS_HLINE, menubar->max_entry_len);
110 } else {
111 unsigned char *text;
113 addch((unsigned char)menu->entries [idx].first_letter);
114 for (text = menu->entries [idx].text; *text; text++)
116 if (*text != '&')
117 addch(*text);
118 else {
119 attrset (color == MENU_SELECTED_COLOR ?
120 MENU_HOTSEL_COLOR : MENU_HOT_COLOR);
121 addch(*(++text));
122 attrset(color);
126 widget_move (&menubar->widget, y, x + 1);
129 static inline void menubar_draw_drop (WMenu *menubar)
131 const int count = (menubar->menu [menubar->selected])->count;
132 int i;
133 int sel = menubar->subsel;
134 int column = menubar-> menu[menubar->selected]->start_x - 1;
136 if (column + menubar->max_entry_len + 4 > menubar->widget.cols)
137 column = menubar->widget.cols - menubar->max_entry_len - 4;
139 attrset (SELECTED_COLOR);
140 draw_box (menubar->widget.parent,
141 menubar->widget.y+1, menubar->widget.x + column,
142 count+2, menubar->max_entry_len + 4);
144 column++;
145 for (i = 0; i < count; i++){
146 if (i == sel)
147 continue;
148 menubar_paint_idx (menubar, i, MENU_ENTRY_COLOR);
150 menubar_paint_idx (menubar, sel, MENU_SELECTED_COLOR);
153 static void menubar_draw (WMenu *menubar)
155 const int items = menubar->items;
156 int i;
158 /* First draw the complete menubar */
159 attrset (SELECTED_COLOR);
160 widget_move (&menubar->widget, 0, 0);
162 /* ncurses bug: it should work with hline but it does not */
163 for (i = menubar->widget.cols; i; i--)
164 addch (' ');
166 attrset (SELECTED_COLOR);
167 /* Now each one of the entries */
168 for (i = 0; i < items; i++){
169 if (menubar->active)
170 attrset(i == menubar->selected?MENU_SELECTED_COLOR:SELECTED_COLOR);
171 widget_move (&menubar->widget, 0, menubar->menu [i]->start_x);
172 printw ("%s", menubar->menu [i]->name);
175 if (menubar->dropped)
176 menubar_draw_drop (menubar);
177 else
178 widget_move (&menubar->widget, 0,
179 menubar-> menu[menubar->selected]->start_x);
182 static inline void menubar_remove (WMenu *menubar)
184 menubar->subsel = 0;
185 if (menubar->dropped){
186 menubar->dropped = 0;
187 do_refresh ();
188 menubar->dropped = 1;
192 static void menubar_left (WMenu *menu)
194 menubar_remove (menu);
195 menu->selected = (menu->selected - 1) % menu->items;
196 if (menu->selected < 0)
197 menu->selected = menu->items -1;
198 menubar_drop_compute (menu);
199 menubar_draw (menu);
202 static void menubar_right (WMenu *menu)
204 menubar_remove (menu);
205 menu->selected = (menu->selected + 1) % menu->items;
206 menubar_drop_compute (menu);
207 menubar_draw (menu);
210 static void menubar_finish (WMenu *menubar)
212 menubar->dropped = 0;
213 menubar->active = 0;
214 menubar->widget.lines = 1;
215 widget_want_hotkey (menubar->widget, 0);
216 dlg_select_nth_widget (menubar->widget.parent,
217 menubar->previous_selection);
218 do_refresh ();
221 static void menubar_drop (WMenu *menubar, int selected)
223 menubar->dropped = 1;
224 menubar->selected = selected;
225 menubar->subsel = 0;
226 menubar_drop_compute (menubar);
227 menubar_draw (menubar);
230 static void menubar_execute (WMenu *menubar, int entry)
232 const Menu menu = menubar->menu [menubar->selected];
233 const callfn call_back = menu->entries [entry].call_back;
235 is_right = menubar->selected != 0;
237 /* This used to be the other way round, i.e. first callback and
238 then menubar_finish. The new order (hack?) is needed to make
239 change_panel () work which is used in quick_view_cmd () -- Norbert
241 menubar_finish (menubar);
242 (*call_back)(0);
243 do_refresh ();
246 static void menubar_move (WMenu *menubar, int step)
248 const Menu menu = menubar->menu [menubar->selected];
250 menubar_paint_idx (menubar, menubar->subsel, MENU_ENTRY_COLOR);
251 do {
252 menubar->subsel += step;
253 if (menubar->subsel < 0)
254 menubar->subsel = menu->count - 1;
256 menubar->subsel %= menu->count;
257 } while (!menu->entries [menubar->subsel].call_back);
258 menubar_paint_idx (menubar, menubar->subsel, MENU_SELECTED_COLOR);
261 static int menubar_handle_key (WMenu *menubar, int key)
263 int i;
265 /* Lowercase */
266 if (key < 256 && isalpha (key)) /* Linux libc.so.5.x.x bug fix */
267 key = tolower (key);
269 if (is_abort_char (key)){
270 menubar_finish (menubar);
271 return 1;
274 if (key == KEY_F(1)) {
275 if (menubar->dropped) {
276 interactive_display (NULL,
277 (menubar->menu [menubar->selected])->help_node);
278 } else {
279 interactive_display (NULL, "[Menu Bar]");
281 menubar_draw (menubar);
282 return 1;
285 if (key == KEY_LEFT || key == XCTRL('b')){
286 menubar_left (menubar);
287 return 1;
288 } else if (key == KEY_RIGHT || key == XCTRL ('f')){
289 menubar_right (menubar);
290 return 1;
293 /* .ado: NT Alpha can not allow CTRL in Menubar */
294 #if defined(NATIVE_WIN32)
295 if (!key)
296 return 0;
297 #endif
299 if (!menubar->dropped){
300 const int items = menubar->items;
301 for (i = 0; i < items; i++){
302 const Menu menu = menubar->menu [i];
304 if (menu->hotkey == key){
305 menubar_drop (menubar, i);
306 return 1;
309 if (key == KEY_ENTER || key == XCTRL ('n') || key == KEY_DOWN
310 || key == '\n'){
311 menubar_drop (menubar, menubar->selected);
312 return 1;
314 return 1;
315 } else {
316 const int selected = menubar->selected;
317 const Menu menu = menubar->menu [selected];
318 const int items = menu->count;
320 for (i = 0; i < items; i++){
321 if (!menu->entries [i].call_back)
322 continue;
324 if (key != menu->entries [i].hot_key)
325 continue;
327 menubar_execute (menubar, i);
328 return 1;
331 if (key == KEY_ENTER || key == '\n'){
332 menubar_execute (menubar, menubar->subsel);
333 return 1;
337 if (key == KEY_DOWN || key == XCTRL ('n'))
338 menubar_move (menubar, 1);
340 if (key == KEY_UP || key == XCTRL ('p'))
341 menubar_move (menubar, -1);
343 return 0;
346 static int menubar_callback (Dlg_head *h, WMenu *menubar, int msg, int par)
348 switch (msg){
349 /* We do not want the focus unless we have been activated */
350 case WIDGET_FOCUS:
351 if (menubar->active){
352 widget_want_cursor (menubar->widget, 1);
354 /* Trick to get all the mouse events */
355 menubar->widget.lines = LINES;
357 /* Trick to get all of the hotkeys */
358 widget_want_hotkey (menubar->widget, 1);
359 menubar->subsel = 0;
360 menubar_drop_compute (menubar);
361 menubar_draw (menubar);
362 return 1;
363 } else
364 return 0;
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, par);
371 return 1;
372 } else
373 return 0;
375 case WIDGET_CURSOR:
376 /* Put the cursor in a suitable place */
377 return 0;
379 case WIDGET_UNFOCUS:
380 if (menubar->active)
381 return 0;
382 else {
383 widget_want_cursor (menubar->widget, 0);
384 return 1;
387 case WIDGET_DRAW:
388 if (menubar_visible)
389 menubar_draw (menubar);
391 return default_proc (h, msg, par);
395 menubar_event (Gpm_Event *event, WMenu *menubar)
397 int was_active;
398 int new_selection;
399 int left_x, right_x, bottom_y;
401 if (!(event->type & (GPM_UP|GPM_DOWN|GPM_DRAG)))
402 return MOU_NORMAL;
404 if (!menubar->dropped){
405 menubar->previous_selection = dlg_item_number(menubar->widget.parent);
406 menubar->active = 1;
407 menubar->dropped = 1;
408 was_active = 0;
409 } else
410 was_active = 1;
412 /* Mouse operations on the menubar */
413 if (event->y == 1 || !was_active){
414 if (event->type & GPM_UP)
415 return MOU_NORMAL;
417 new_selection = 0;
418 while (new_selection < menubar->items
419 && event->x > menubar->menu[new_selection]->start_x
421 new_selection++;
423 if (new_selection) /* Don't set the invalid value -1 */
424 --new_selection;
426 if (!was_active){
427 menubar->selected = new_selection;
428 dlg_select_widget (menubar->widget.parent, menubar);
429 menubar_drop_compute (menubar);
430 menubar_draw (menubar);
431 return MOU_NORMAL;
434 menubar_remove (menubar);
436 menubar->selected = new_selection;
438 menubar_drop_compute (menubar);
439 menubar_draw (menubar);
440 return MOU_NORMAL;
443 if (!menubar->dropped)
444 return MOU_NORMAL;
446 /* Ignore the events on anything below the third line */
447 if (event->y <= 2)
448 return MOU_NORMAL;
450 /* Else, the mouse operation is on the menus or it is not */
451 left_x = menubar->menu[menubar->selected]->start_x;
452 right_x = left_x + menubar->max_entry_len + 4;
453 if (right_x > menubar->widget.cols)
455 left_x = menubar->widget.cols - menubar->max_entry_len - 3;
456 right_x = menubar->widget.cols - 1;
459 bottom_y = (menubar->menu [menubar->selected])->count + 3;
461 if ((event->x > left_x) && (event->x < right_x) && (event->y < bottom_y)){
462 int pos = event->y - 3;
464 if (!menubar->menu [menubar->selected]->entries [pos].call_back)
465 return MOU_NORMAL;
467 menubar_paint_idx (menubar, menubar->subsel, MENU_ENTRY_COLOR);
468 menubar->subsel = pos;
469 menubar_paint_idx (menubar, menubar->subsel, MENU_SELECTED_COLOR);
471 if (event->type & GPM_UP)
472 menubar_execute (menubar, pos);
473 } else
474 if (event->type & GPM_DOWN)
475 menubar_finish (menubar);
477 return MOU_NORMAL;
480 static void menubar_destroy (WMenu *menubar)
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 *menubar_new (int y, int x, int cols, Menu menu [], int items)
543 WMenu *menubar = g_new0 (WMenu, 1); /* FIXME: subsel used w/o being set */
545 init_widget (&menubar->widget, y, x, 1, cols,
546 (callback_fn) menubar_callback,
547 (destroy_fn) menubar_destroy,
548 (mouse_h) menubar_event, NULL);
549 menubar->menu = menu;
550 menubar->active = 0;
551 menubar->dropped = 0;
552 menubar->items = items;
553 menubar->selected = 0;
554 widget_want_cursor (menubar->widget, 0);
555 menubar_arrange(menubar);
557 return menubar;