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. */
20 * \brief Source: pulldown menu code
28 #include <sys/types.h>
32 #include "../src/tty/tty.h"
33 #include "../src/skin/skin.h"
34 #include "../src/tty/mouse.h"
35 #include "../src/tty/key.h" /* key macros */
41 #include "main.h" /* is_right */
44 int menubar_visible
= 1; /* This is the new default */
47 create_menu (const char *name
, menu_entry
*entries
, int count
, const char *help_node
)
51 menu
= g_new (Menu
, 1);
53 menu
->max_entry_len
= 20;
54 menu
->entries
= entries
;
55 menu
->text
= parse_hotkey (name
);
57 if (entries
!= (menu_entry
*) NULL
) {
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
);
70 menu
->help_node
= g_strdup (help_node
);
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
) {
91 tty_setcolor (MENU_ENTRY_COLOR
);
93 widget_move (&menubar
->widget
, y
, x
- 1);
94 tty_print_alt_char (ACS_LTEE
);
96 tty_draw_hline (menubar
->widget
.y
+ y
, menubar
->widget
.x
+ x
,
97 ACS_HLINE
, menubar
->max_entry_len
+ 2);
99 widget_move (&menubar
->widget
, y
, x
+ menubar
->max_entry_len
+ 2);
100 tty_print_alt_char (ACS_RTEE
);
103 tty_setcolor (color
);
104 widget_move (&menubar
->widget
, y
, x
);
105 tty_print_char ((unsigned char) entry
->first_letter
);
106 tty_draw_hline (-1, -1, ' ', menubar
->max_entry_len
+ 1); /* clear line */
107 tty_print_string (entry
->text
.start
);
109 if (entry
->text
.hotkey
!= 0) {
110 tty_setcolor (color
== MENU_SELECTED_COLOR
?
111 MENU_HOTSEL_COLOR
: MENU_HOT_COLOR
);
112 tty_print_anychar (entry
->text
.hotkey
);
116 if (entry
->text
.end
!= NULL
)
117 tty_print_string (entry
->text
.end
);
119 /* move cursor to the start of entry text */
120 widget_move (&menubar
->widget
, y
, x
+ 1);
124 static 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;
130 if (column
+ menubar
->max_entry_len
+ 4 > menubar
->widget
.cols
)
131 column
= menubar
->widget
.cols
- menubar
->max_entry_len
- 4;
133 tty_setcolor (MENU_ENTRY_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_set_color (WMenu
*menubar
, int current
, gboolean hotkey
)
149 if (!menubar
->active
)
150 tty_setcolor (hotkey
? COLOR_HOT_FOCUS
: SELECTED_COLOR
);
151 else if (current
== menubar
->selected
)
152 tty_setcolor (hotkey
? MENU_HOTSEL_COLOR
: MENU_SELECTED_COLOR
);
154 tty_setcolor (hotkey
? MENU_HOT_COLOR
: MENU_ENTRY_COLOR
);
157 static void menubar_draw (WMenu
*menubar
)
159 const int items
= menubar
->items
;
162 /* First draw the complete menubar */
163 tty_setcolor (menubar
->active
? MENU_ENTRY_COLOR
: SELECTED_COLOR
);
164 tty_draw_hline (menubar
->widget
.y
, menubar
->widget
.x
, ' ', menubar
->widget
.cols
);
166 /* Now each one of the entries */
167 for (i
= 0; i
< items
; i
++){
168 menubar_set_color (menubar
, i
, FALSE
);
169 widget_move (&menubar
->widget
, 0, menubar
->menu
[i
]->start_x
);
171 tty_print_string (menubar
->menu
[i
]->text
.start
);
173 if (menubar
->menu
[i
]->text
.hotkey
!= 0) {
174 menubar_set_color (menubar
, i
, TRUE
);
175 tty_print_anychar (menubar
->menu
[i
]->text
.hotkey
);
176 menubar_set_color (menubar
, i
, FALSE
);
178 if (menubar
->menu
[i
]->text
.end
!= NULL
)
179 tty_print_string (menubar
->menu
[i
]->text
.end
);
182 if (menubar
->dropped
)
183 menubar_draw_drop (menubar
);
185 widget_move (&menubar
->widget
, 0,
186 menubar
-> menu
[menubar
->selected
]->start_x
);
189 static void menubar_remove (WMenu
*menubar
)
192 if (menubar
->dropped
) {
193 menubar
->dropped
= 0;
195 menubar
->dropped
= 1;
199 static void menubar_left (WMenu
*menu
)
201 menubar_remove (menu
);
202 menu
->selected
= (menu
->selected
- 1) % menu
->items
;
203 if (menu
->selected
< 0)
204 menu
->selected
= menu
->items
-1;
205 menubar_drop_compute (menu
);
209 static void menubar_right (WMenu
*menu
)
211 menubar_remove (menu
);
212 menu
->selected
= (menu
->selected
+ 1) % menu
->items
;
213 menubar_drop_compute (menu
);
218 menubar_finish (WMenu
*menubar
)
220 menubar
->dropped
= 0;
222 menubar
->widget
.lines
= 1;
223 widget_want_hotkey (menubar
->widget
, 0);
225 dlg_select_by_id (menubar
->widget
.parent
, menubar
->previous_widget
);
229 static void menubar_drop (WMenu
*menubar
, int selected
)
231 menubar
->dropped
= 1;
232 menubar
->selected
= selected
;
234 menubar_drop_compute (menubar
);
235 menubar_draw (menubar
);
238 static void menubar_execute (WMenu
*menubar
, int entry
)
240 const Menu
*menu
= menubar
->menu
[menubar
->selected
];
241 const callfn call_back
= menu
->entries
[entry
].call_back
;
243 is_right
= menubar
->selected
!= 0;
245 /* This used to be the other way round, i.e. first callback and
246 then menubar_finish. The new order (hack?) is needed to make
247 change_panel () work which is used in quick_view_cmd () -- Norbert
249 menubar_finish (menubar
);
254 static void menubar_move (WMenu
*menubar
, int step
)
256 const Menu
*menu
= menubar
->menu
[menubar
->selected
];
258 menubar_paint_idx (menubar
, menubar
->subsel
, MENU_ENTRY_COLOR
);
260 menubar
->subsel
+= step
;
261 if (menubar
->subsel
< 0)
262 menubar
->subsel
= menu
->count
- 1;
264 menubar
->subsel
%= menu
->count
;
265 } while (!menu
->entries
[menubar
->subsel
].call_back
);
266 menubar_paint_idx (menubar
, menubar
->subsel
, MENU_SELECTED_COLOR
);
269 static int menubar_handle_key (WMenu
*menubar
, int key
)
274 if (isascii (key
)) key
= g_ascii_tolower (key
);
276 if (is_abort_char (key
)){
277 menubar_finish (menubar
);
281 if (key
== KEY_F(1)) {
282 if (menubar
->dropped
) {
283 interactive_display (NULL
,
284 (menubar
->menu
[menubar
->selected
])->help_node
);
286 interactive_display (NULL
, "[Menu Bar]");
288 menubar_draw (menubar
);
292 if (key
== KEY_LEFT
|| key
== XCTRL('b')){
293 menubar_left (menubar
);
295 } else if (key
== KEY_RIGHT
|| key
== XCTRL ('f')){
296 menubar_right (menubar
);
300 if (!menubar
->dropped
){
301 const int items
= menubar
->items
;
302 for (i
= 0; i
< items
; i
++) {
303 const Menu
*menu
= menubar
->menu
[i
];
305 if (menu
->text
.hotkey
!= 0) {
306 if (menu
->text
.hotkey
== key
) {
307 menubar_drop (menubar
, i
);
312 if (key
== KEY_ENTER
|| key
== XCTRL ('n')
313 || key
== KEY_DOWN
|| key
== '\n') {
315 menubar_drop (menubar
, menubar
->selected
);
320 const int selected
= menubar
->selected
;
321 const Menu
*menu
= menubar
->menu
[selected
];
322 const int items
= menu
->count
;
324 for (i
= 0; i
< items
; i
++) {
325 if (!menu
->entries
[i
].call_back
)
328 if (menu
->entries
[i
].text
.hotkey
!= 0) {
329 if (key
!= menu
->entries
[i
].text
.hotkey
)
332 menubar_execute (menubar
, i
);
337 if (key
== KEY_ENTER
|| key
== '\n') {
338 menubar_execute (menubar
, menubar
->subsel
);
343 if (key
== KEY_DOWN
|| key
== XCTRL ('n'))
344 menubar_move (menubar
, 1);
346 if (key
== KEY_UP
|| key
== XCTRL ('p'))
347 menubar_move (menubar
, -1);
353 menubar_callback (Widget
*w
, widget_msg_t msg
, int parm
)
355 WMenu
*menubar
= (WMenu
*) w
;
358 /* We do not want the focus unless we have been activated */
360 if (!menubar
->active
)
361 return MSG_NOT_HANDLED
;
363 widget_want_cursor (menubar
->widget
, 1);
365 /* Trick to get all the mouse events */
366 menubar
->widget
.lines
= LINES
;
368 /* Trick to get all of the hotkeys */
369 widget_want_hotkey (menubar
->widget
, 1);
371 menubar_drop_compute (menubar
);
372 menubar_draw (menubar
);
375 /* We don't want the buttonbar to activate while using the menubar */
378 if (menubar
->active
) {
379 menubar_handle_key (menubar
, parm
);
382 return MSG_NOT_HANDLED
;
385 /* Put the cursor in a suitable place */
386 return MSG_NOT_HANDLED
;
390 return MSG_NOT_HANDLED
;
392 widget_want_cursor (menubar
->widget
, 0);
397 if (menubar_visible
) {
398 menubar_draw (menubar
);
404 /* try show menu after screen resize */
405 send_message (w
, WIDGET_FOCUS
, 0);
409 return default_proc (msg
, parm
);
414 menubar_event (Gpm_Event
*event
, void *data
)
416 WMenu
*menubar
= data
;
417 Menu
*menu
= menubar
->menu
[menubar
->selected
];
419 int left_x
, right_x
, bottom_y
;
421 /* ignore unsupported events */
422 if ((event
->type
& (GPM_UP
| GPM_DOWN
| GPM_DRAG
)) == 0)
425 /* ignore wheel events if menu is inactive */
426 if (!menubar
->active
&& (event
->buttons
& (GPM_B_MIDDLE
| GPM_B_UP
| GPM_B_DOWN
)))
429 /* detect the menu state */
430 if (!menubar
->dropped
) {
431 menubar
->previous_widget
= menubar
->widget
.parent
->current
->dlg_id
;
433 menubar
->dropped
= 1;
437 /* Mouse operations on the menubar */
438 if (event
->y
== 1 || !was_active
){
439 if (event
->type
& GPM_UP
)
442 /* wheel events on menubar */
443 if (event
->buttons
& GPM_B_UP
)
444 menubar_left (menubar
);
445 else if (event
->buttons
& GPM_B_DOWN
)
446 menubar_right (menubar
);
448 int new_selection
= 0;
450 while (new_selection
< menubar
->items
451 && event
->x
> menubar
->menu
[new_selection
]->start_x
)
454 if (new_selection
) /* Don't set the invalid value -1 */
458 menubar
->selected
= new_selection
;
459 dlg_select_widget (menubar
);
461 menubar_remove (menubar
);
462 menubar
->selected
= new_selection
;
464 menubar_drop_compute (menubar
);
465 menubar_draw (menubar
);
470 if (!menubar
->dropped
|| (event
->y
< 2))
473 /* middle click -- everywhere */
474 if ((event
->buttons
& GPM_B_MIDDLE
) && (event
->type
& GPM_DOWN
)) {
475 menubar_execute (menubar
, menubar
->subsel
);
479 /* the mouse operation is on the menus or it is not */
480 left_x
= menu
->start_x
;
481 right_x
= left_x
+ menubar
->max_entry_len
+ 3;
482 if (right_x
> menubar
->widget
.cols
) {
483 left_x
= menubar
->widget
.cols
- menubar
->max_entry_len
- 3;
484 right_x
= menubar
->widget
.cols
;
487 bottom_y
= menu
->count
+ 3;
489 if ((event
->x
>= left_x
) && (event
->x
<= right_x
) && (event
->y
<= bottom_y
)){
490 int pos
= event
->y
- 3;
493 if ((event
->buttons
& GPM_B_UP
) && (event
->type
& GPM_DOWN
)) {
494 menubar_move (menubar
, -1);
497 if ((event
->buttons
& GPM_B_DOWN
) && (event
->type
& GPM_DOWN
)) {
498 menubar_move (menubar
, 1);
502 /* ignore events above and below dropped down menu */
503 if ((pos
< 0) || (pos
>= menu
->count
))
506 if (menubar
->menu
[menubar
->selected
]->entries
[pos
].call_back
) {
507 menubar_paint_idx (menubar
, menubar
->subsel
, MENU_ENTRY_COLOR
);
508 menubar
->subsel
= pos
;
509 menubar_paint_idx (menubar
, menubar
->subsel
, MENU_SELECTED_COLOR
);
511 if (event
->type
& GPM_UP
)
512 menubar_execute (menubar
, menubar
->subsel
);
515 /* use click not wheel to close menu */
516 if ((event
->type
& GPM_DOWN
) && !(event
->buttons
& (GPM_B_UP
| GPM_B_DOWN
)))
517 menubar_finish (menubar
);
523 * Properly space menubar items. Should be called when menubar is created
524 * and also when widget width is changed (i.e. upon xterm resize).
527 menubar_arrange(WMenu
* menubar
)
529 register int i
, start_x
= 1;
530 int items
= menubar
->items
;
532 #ifndef RESIZABLE_MENUBAR
535 for (i
= 0; i
< items
; i
++)
537 int len
= hotkey_width (menubar
->menu
[i
]->text
);
538 menubar
->menu
[i
]->start_x
= start_x
;
539 start_x
+= len
+ gap
;
542 #else /* RESIZABLE_MENUBAR */
544 int gap
= menubar
->widget
.cols
- 2;
546 /* First, calculate gap between items... */
547 for (i
= 0; i
< items
; i
++)
549 /* preserve length here, to be used below */
550 gap
-= (menubar
->menu
[i
]->start_x
= hotkey_width (menubar
->menu
[i
]->text
));
557 /* We are out of luck - window is too narrow... */
561 /* ...and now fix start positions of menubar items */
562 for (i
= 0; i
< items
; i
++)
564 int len
= menubar
->menu
[i
]->start_x
;
565 menubar
->menu
[i
]->start_x
= start_x
;
566 start_x
+= len
+ gap
;
568 #endif /* RESIZABLE_MENUBAR */
572 destroy_menu (Menu
*menu
)
574 release_hotkey (menu
->text
);
575 if (menu
->entries
!= NULL
) {
577 for (me
= 0; me
< menu
->count
; me
++) {
578 if (menu
->entries
[me
].label
[0] != '\0') {
579 release_hotkey (menu
->entries
[me
].text
);
584 g_free (menu
->help_node
);
589 menubar_new (int y
, int x
, int cols
, Menu
*menu
[], int items
)
591 WMenu
*menubar
= g_new0 (WMenu
, 1);
593 init_widget (&menubar
->widget
, y
, x
, 1, cols
,
594 menubar_callback
, menubar_event
);
595 menubar
->menu
= menu
;
597 menubar
->dropped
= 0;
598 menubar
->items
= items
;
599 menubar
->selected
= 0;
601 widget_want_cursor (menubar
->widget
, 0);
602 menubar_arrange (menubar
);