updated the .TP cleanup for coherency in the key description pages.
[midnight-commander.git] / src / menu.c
blob5c4f2b83644185dc15a3f17d00e821224ddec156
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 extern int is_right;
35 int menubar_visible = 1; /* This is the new default */
37 static void menu_scan_hotkey(Menu menu)
39 char* cp = strchr (menu->name, '&');
41 if (cp != NULL && cp[1] != '\0'){
42 strcpy (cp, cp+1);
43 menu->hotkey = tolower(*cp);
45 else
46 menu->hotkey = 0;
49 Menu create_menu (char *name, menu_entry *entries, int count, char *help_node)
51 Menu menu;
52 char *cp;
54 menu = (Menu) g_malloc (sizeof (*menu));
55 menu->count = count;
56 menu->max_entry_len = 20;
57 menu->entries = entries;
59 if (entries != (menu_entry*) NULL) {
60 register menu_entry* mp;
61 for (mp = entries; count--; mp++) {
62 if (mp->text[0] != '\0') {
63 #ifdef ENABLE_NLS
64 mp->text = _(mp->text);
65 #endif /* ENABLE_NLS */
66 cp = strchr (mp->text,'&');
68 if (cp != NULL && *(cp+1) != '\0') {
69 mp->hot_key = tolower (*(cp+1));
70 menu->max_entry_len = max (strlen (mp->text) - 1,
71 menu->max_entry_len);
72 } else {
73 menu->max_entry_len = max (strlen (mp->text),
74 menu->max_entry_len);
80 menu->name = g_strdup (name);
81 menu_scan_hotkey(menu);
82 menu->start_x = 0;
83 menu->help_node = g_strdup (help_node);
84 return menu;
87 static void menubar_drop_compute (WMenu *menubar)
89 menubar->max_entry_len = menubar->menu [menubar->selected]->max_entry_len;
92 static void menubar_paint_idx (WMenu *menubar, int idx, int color)
94 const Menu menu = menubar->menu [menubar->selected];
95 const int y = 2 + idx;
96 int x = menubar-> menu[menubar->selected]->start_x;
98 if (x + menubar->max_entry_len + 3 > menubar->widget.cols)
99 x = menubar->widget.cols - menubar->max_entry_len - 3;
101 widget_move (&menubar->widget, y, x);
102 attrset (color);
103 hline (' ', menubar->max_entry_len+2);
104 if (!*menu->entries [idx].text) {
105 attrset (SELECTED_COLOR);
106 widget_move (&menubar->widget, y, x + 1);
107 hline (slow_terminal ? ' ' : ACS_HLINE, menubar->max_entry_len);
108 } else {
109 unsigned char *text;
111 addch((unsigned char)menu->entries [idx].first_letter);
112 for (text = menu->entries [idx].text; *text; text++)
114 if (*text != '&')
115 addch(*text);
116 else {
117 attrset (color == MENU_SELECTED_COLOR ?
118 MENU_HOTSEL_COLOR : MENU_HOT_COLOR);
119 addch(*(++text));
120 attrset(color);
124 widget_move (&menubar->widget, y, x + 1);
127 static inline void menubar_draw_drop (WMenu *menubar)
129 const int count = (menubar->menu [menubar->selected])->count;
130 int i;
131 int sel = menubar->subsel;
132 int column = menubar-> menu[menubar->selected]->start_x - 1;
134 if (column + menubar->max_entry_len + 4 > menubar->widget.cols)
135 column = menubar->widget.cols - menubar->max_entry_len - 4;
137 attrset (SELECTED_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 column++;
143 for (i = 0; i < count; i++){
144 if (i == sel)
145 continue;
146 menubar_paint_idx (menubar, i, MENU_ENTRY_COLOR);
148 menubar_paint_idx (menubar, sel, MENU_SELECTED_COLOR);
151 static void menubar_draw (WMenu *menubar)
153 const int items = menubar->items;
154 int i;
156 /* First draw the complete menubar */
157 attrset (SELECTED_COLOR);
158 widget_move (&menubar->widget, 0, 0);
160 /* ncurses bug: it should work with hline but it does not */
161 for (i = menubar->widget.cols; i; i--)
162 addch (' ');
164 attrset (SELECTED_COLOR);
165 /* Now each one of the entries */
166 for (i = 0; i < items; i++){
167 if (menubar->active)
168 attrset(i == menubar->selected?MENU_SELECTED_COLOR:SELECTED_COLOR);
169 widget_move (&menubar->widget, 0, menubar->menu [i]->start_x);
170 printw ("%s", menubar->menu [i]->name);
173 if (menubar->dropped)
174 menubar_draw_drop (menubar);
175 else
176 widget_move (&menubar->widget, 0,
177 menubar-> menu[menubar->selected]->start_x);
180 static inline void menubar_remove (WMenu *menubar)
182 menubar->subsel = 0;
183 if (menubar->dropped){
184 menubar->dropped = 0;
185 do_refresh ();
186 menubar->dropped = 1;
190 static void menubar_left (WMenu *menu)
192 menubar_remove (menu);
193 menu->selected = (menu->selected - 1) % menu->items;
194 if (menu->selected < 0)
195 menu->selected = menu->items -1;
196 menubar_drop_compute (menu);
197 menubar_draw (menu);
200 static void menubar_right (WMenu *menu)
202 menubar_remove (menu);
203 menu->selected = (menu->selected + 1) % menu->items;
204 menubar_drop_compute (menu);
205 menubar_draw (menu);
208 static void menubar_finish (WMenu *menubar)
210 menubar->dropped = 0;
211 menubar->active = 0;
212 menubar->widget.lines = 1;
213 widget_want_hotkey (menubar->widget, 0);
214 dlg_select_nth_widget (menubar->widget.parent,
215 menubar->previous_selection);
216 do_refresh ();
219 static void menubar_drop (WMenu *menubar, int selected)
221 menubar->dropped = 1;
222 menubar->selected = selected;
223 menubar->subsel = 0;
224 menubar_drop_compute (menubar);
225 menubar_draw (menubar);
228 static void menubar_execute (WMenu *menubar, int entry)
230 const Menu menu = menubar->menu [menubar->selected];
231 const callfn call_back = menu->entries [entry].call_back;
233 is_right = menubar->selected != 0;
235 /* This used to be the other way round, i.e. first callback and
236 then menubar_finish. The new order (hack?) is needed to make
237 change_panel () work which is used in quick_view_cmd () -- Norbert
239 menubar_finish (menubar);
240 (*call_back)(0);
241 do_refresh ();
244 static void menubar_move (WMenu *menubar, int step)
246 const Menu menu = menubar->menu [menubar->selected];
248 menubar_paint_idx (menubar, menubar->subsel, MENU_ENTRY_COLOR);
249 do {
250 menubar->subsel += step;
251 if (menubar->subsel < 0)
252 menubar->subsel = menu->count - 1;
254 menubar->subsel %= menu->count;
255 } while (!menu->entries [menubar->subsel].call_back);
256 menubar_paint_idx (menubar, menubar->subsel, MENU_SELECTED_COLOR);
259 static int menubar_handle_key (WMenu *menubar, int key)
261 int i;
263 /* Lowercase */
264 if (key < 256 && isalpha (key)) /* Linux libc.so.5.x.x bug fix */
265 key = tolower (key);
267 if (is_abort_char (key)){
268 menubar_finish (menubar);
269 return 1;
272 if (key == KEY_F(1)) {
273 if (menubar->dropped) {
274 interactive_display (NULL,
275 (menubar->menu [menubar->selected])->help_node);
276 } else {
277 interactive_display (NULL, "[Menu Bar]");
279 menubar_draw (menubar);
280 return 1;
283 if (key == KEY_LEFT || key == XCTRL('b')){
284 menubar_left (menubar);
285 return 1;
286 } else if (key == KEY_RIGHT || key == XCTRL ('f')){
287 menubar_right (menubar);
288 return 1;
291 /* .ado: NT Alpha can not allow CTRL in Menubar */
292 #if defined(NATIVE_WIN32)
293 if (!key)
294 return 0;
295 #endif
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->hotkey == key){
303 menubar_drop (menubar, i);
304 return 1;
307 if (key == KEY_ENTER || key == XCTRL ('n') || key == KEY_DOWN
308 || key == '\n'){
309 menubar_drop (menubar, menubar->selected);
310 return 1;
312 return 1;
313 } else {
314 const int selected = menubar->selected;
315 const Menu menu = menubar->menu [selected];
316 const int items = menu->count;
318 for (i = 0; i < items; i++){
319 if (!menu->entries [i].call_back)
320 continue;
322 if (key != menu->entries [i].hot_key)
323 continue;
325 menubar_execute (menubar, i);
326 return 1;
329 if (key == KEY_ENTER || key == '\n'){
330 menubar_execute (menubar, menubar->subsel);
331 return 1;
335 if (key == KEY_DOWN || key == XCTRL ('n'))
336 menubar_move (menubar, 1);
338 if (key == KEY_UP || key == XCTRL ('p'))
339 menubar_move (menubar, -1);
341 return 0;
344 static int menubar_callback (Dlg_head *h, WMenu *menubar, int msg, int par)
346 switch (msg){
347 /* We do not want the focus unless we have been activated */
348 case WIDGET_FOCUS:
349 if (menubar->active){
350 widget_want_cursor (menubar->widget, 1);
352 /* Trick to get all the mouse events */
353 menubar->widget.lines = LINES;
355 /* Trick to get all of the hotkeys */
356 widget_want_hotkey (menubar->widget, 1);
357 menubar->subsel = 0;
358 menubar_drop_compute (menubar);
359 menubar_draw (menubar);
360 return 1;
361 } else
362 return 0;
364 /* We don't want the buttonbar to activate while using the menubar */
365 case WIDGET_HOTKEY:
366 case WIDGET_KEY:
367 if (menubar->active){
368 menubar_handle_key (menubar, par);
369 return 1;
370 } else
371 return 0;
373 case WIDGET_CURSOR:
374 /* Put the cursor in a suitable place */
375 return 0;
377 case WIDGET_UNFOCUS:
378 if (menubar->active)
379 return 0;
380 else {
381 widget_want_cursor (menubar->widget, 0);
382 return 1;
385 case WIDGET_DRAW:
386 if (menubar_visible)
387 menubar_draw (menubar);
389 return default_proc (h, msg, par);
393 menubar_event (Gpm_Event *event, WMenu *menubar)
395 int was_active;
396 int new_selection;
397 int left_x, right_x, bottom_y;
399 if (!(event->type & (GPM_UP|GPM_DOWN|GPM_DRAG)))
400 return MOU_NORMAL;
402 if (!menubar->dropped){
403 menubar->previous_selection = dlg_item_number(menubar->widget.parent);
404 menubar->active = 1;
405 menubar->dropped = 1;
406 was_active = 0;
407 } else
408 was_active = 1;
410 /* Mouse operations on the menubar */
411 if (event->y == 1 || !was_active){
412 if (event->type & GPM_UP)
413 return MOU_NORMAL;
415 new_selection = 0;
416 while (new_selection < menubar->items
417 && event->x > menubar->menu[new_selection]->start_x
419 new_selection++;
421 if (new_selection) /* Don't set the invalid value -1 */
422 --new_selection;
424 if (!was_active){
425 menubar->selected = new_selection;
426 dlg_select_widget (menubar->widget.parent, menubar);
427 menubar_drop_compute (menubar);
428 menubar_draw (menubar);
429 return MOU_NORMAL;
432 menubar_remove (menubar);
434 menubar->selected = new_selection;
436 menubar_drop_compute (menubar);
437 menubar_draw (menubar);
438 return MOU_NORMAL;
441 if (!menubar->dropped)
442 return MOU_NORMAL;
444 /* Ignore the events on anything below the third line */
445 if (event->y <= 2)
446 return MOU_NORMAL;
448 /* Else, the mouse operation is on the menus or it is not */
449 left_x = menubar->menu[menubar->selected]->start_x;
450 right_x = left_x + menubar->max_entry_len + 4;
451 if (right_x > menubar->widget.cols)
453 left_x = menubar->widget.cols - menubar->max_entry_len - 3;
454 right_x = menubar->widget.cols - 1;
457 bottom_y = (menubar->menu [menubar->selected])->count + 3;
459 if ((event->x > left_x) && (event->x < right_x) && (event->y < bottom_y)){
460 int pos = event->y - 3;
462 if (!menubar->menu [menubar->selected]->entries [pos].call_back)
463 return MOU_NORMAL;
465 menubar_paint_idx (menubar, menubar->subsel, MENU_ENTRY_COLOR);
466 menubar->subsel = pos;
467 menubar_paint_idx (menubar, menubar->subsel, MENU_SELECTED_COLOR);
469 if (event->type & GPM_UP)
470 menubar_execute (menubar, pos);
471 } else
472 if (event->type & GPM_DOWN)
473 menubar_finish (menubar);
475 return MOU_NORMAL;
478 static void menubar_destroy (WMenu *menubar)
483 * Properly space menubar items. Should be called when menubar is created
484 * and also when widget width is changed (i.e. upon xterm resize).
486 void
487 menubar_arrange(WMenu* menubar)
489 register int i, start_x = 1;
490 int items = menubar->items;
492 #ifndef RESIZABLE_MENUBAR
493 int gap = 3;
495 for (i = 0; i < items; i++)
497 int len = strlen(menubar->menu[i]->name);
498 menubar->menu[i]->start_x = start_x;
499 start_x += len + gap;
502 #else /* RESIZABLE_MENUBAR */
504 int gap = menubar->widget.cols - 2;
506 /* First, calculate gap between items... */
507 for (i = 0; i < items; i++)
509 /* preserve length here, to be used below */
510 gap -= (menubar->menu[i]->start_x = strlen(menubar->menu[i]->name));
513 gap /= (items - 1);
515 if (gap <= 0)
517 /* We are out of luck - window is too narrow... */
518 gap = 1;
521 /* ...and now fix start positions of menubar items */
522 for (i = 0; i < items; i++)
524 int len = menubar->menu[i]->start_x;
525 menubar->menu[i]->start_x = start_x;
526 start_x += len + gap;
528 #endif /* RESIZABLE_MENUBAR */
531 void
532 destroy_menu (Menu menu)
534 g_free (menu->name);
535 g_free (menu->help_node);
536 g_free (menu);
539 WMenu *menubar_new (int y, int x, int cols, Menu menu [], int items)
541 WMenu *menubar = g_new0 (WMenu, 1); /* FIXME: subsel used w/o being set */
543 init_widget (&menubar->widget, y, x, 1, cols,
544 (callback_fn) menubar_callback,
545 (destroy_fn) menubar_destroy,
546 (mouse_h) menubar_event, NULL);
547 menubar->menu = menu;
548 menubar->active = 0;
549 menubar->dropped = 0;
550 menubar->items = items;
551 menubar->selected = 0;
552 widget_want_cursor (menubar->widget, 0);
553 menubar_arrange(menubar);
555 return menubar;