1 /* Menu system implementation. */
4 #define _GNU_SOURCE /* XXX: we _WANT_ strcasestr() ! */
16 #include "bfu/hotkey.h"
17 #include "bfu/inpfield.h"
19 #include "config/kbdbind.h"
20 #include "intl/gettext/libintl.h"
21 #include "session/session.h"
22 #include "terminal/draw.h"
23 #include "terminal/event.h"
24 #include "terminal/kbd.h"
25 #include "terminal/mouse.h"
26 #include "terminal/tab.h"
27 #include "terminal/terminal.h"
28 #include "terminal/window.h"
29 #include "util/color.h"
30 #include "util/conv.h"
31 #include "util/memory.h"
32 #include "viewer/action.h"
34 /* Left and right main menu reserved spaces. */
35 #define L_MAINMENU_SPACE 2
36 #define R_MAINMENU_SPACE 2
38 /* Left and right padding spaces around labels in main menu. */
39 #define L_MAINTEXT_SPACE 1
40 #define R_MAINTEXT_SPACE 1
42 /* Spaces before and after right text of submenu. */
43 #define L_RTEXT_SPACE 1
44 #define R_RTEXT_SPACE 1
46 /* Spaces before and after left text of submenu. */
47 #define L_TEXT_SPACE 1
48 #define R_TEXT_SPACE 1
50 /* Border size in submenu. */
51 #define MENU_BORDER_SIZE 1
54 /* Types and structures */
56 /* Submenu indicator, displayed at right. */
57 static unsigned char m_submenu
[] = ">>";
58 static int m_submenu_len
= sizeof(m_submenu
) - 1;
61 static window_handler_T menu_handler
;
62 static window_handler_T mainmenu_handler
;
63 static void display_mainmenu(struct terminal
*term
, struct menu
*menu
);
64 static void set_menu_selection(struct menu
*menu
, int pos
);
68 deselect_mainmenu(struct terminal
*term
, struct menu
*menu
)
71 del_from_list(menu
->win
);
72 add_to_list_end(term
->windows
, menu
->win
);
76 count_items(struct menu_item
*items
)
81 struct menu_item
*item
;
83 foreach_menu_item (item
, items
) {
92 free_menu_items(struct menu_item
*items
)
94 struct menu_item
*item
;
96 if (!items
|| !(items
->flags
& FREE_ANY
)) return;
98 /* Note that flags & FREE_DATA applies only when menu is aborted;
99 * it is zeroed when some menu field is selected. */
101 foreach_menu_item (item
, items
) {
102 if (item
->flags
& FREE_TEXT
) mem_free_if(item
->text
);
103 if (item
->flags
& FREE_RTEXT
) mem_free_if(item
->rtext
);
104 if (item
->flags
& FREE_DATA
) mem_free_if(item
->data
);
111 do_menu_selected(struct terminal
*term
, struct menu_item
*items
,
112 void *data
, int selected
, int hotkeys
)
114 struct menu
*menu
= mem_calloc(1, sizeof(*menu
));
117 menu
->selected
= selected
;
120 menu
->size
= count_items(items
);
121 menu
->hotkeys
= hotkeys
;
125 refresh_hotkeys(term
, menu
);
126 add_window(term
, menu_handler
, menu
);
128 free_menu_items(items
);
133 do_menu(struct terminal
*term
, struct menu_item
*items
, void *data
, int hotkeys
)
135 do_menu_selected(term
, items
, data
, 0, hotkeys
);
139 select_menu_item(struct terminal
*term
, struct menu_item
*it
, void *data
)
141 /* We save these values due to delete_window() call below. */
142 menu_func_T func
= it
->func
;
143 void *it_data
= it
->data
;
144 enum main_action action_id
= it
->action_id
;
146 if (!mi_is_selectable(it
)) return;
148 if (!mi_is_submenu(it
)) {
149 /* Don't free data! */
150 it
->flags
&= ~FREE_DATA
;
152 while (!list_empty(term
->windows
)) {
153 struct window
*win
= term
->windows
.next
;
155 if (win
->handler
!= menu_handler
156 && win
->handler
!= mainmenu_handler
)
159 if (win
->handler
== mainmenu_handler
) {
160 deselect_mainmenu(term
, win
->data
);
161 redraw_terminal(term
);
167 if (action_id
!= ACT_MAIN_NONE
&& !func
) {
168 struct session
*ses
= data
;
170 do_action(ses
, action_id
, 1);
174 assertm(func
!= NULL
, "No menu function");
175 if_assert_failed
return;
177 func(term
, it_data
, data
);
181 select_menu(struct terminal
*term
, struct menu
*menu
)
183 if (menu
->selected
< 0 || menu
->selected
>= menu
->size
)
186 select_menu_item(term
, &menu
->items
[menu
->selected
], menu
->data
);
189 /* Get desired width for left text in menu item, accounting spacing. */
191 get_menuitem_text_width(struct terminal
*term
, struct menu_item
*mi
)
195 if (!mi_has_left_text(mi
)) return 0;
198 if (mi_text_translate(mi
))
199 text
= _(text
, term
);
201 if (!text
[0]) return 0;
205 return L_TEXT_SPACE
+ utf8_ptr2cells(text
, NULL
)
206 - !!mi
->hotkey_pos
+ R_TEXT_SPACE
;
208 #endif /* CONFIG_UTF8 */
209 return L_TEXT_SPACE
+ strlen(text
)
210 - !!mi
->hotkey_pos
+ R_TEXT_SPACE
;
213 /* Get desired width for right text in menu item, accounting spacing. */
215 get_menuitem_rtext_width(struct terminal
*term
, struct menu_item
*mi
)
219 if (mi_is_submenu(mi
)) {
220 rtext_width
= L_RTEXT_SPACE
+ m_submenu_len
+ R_RTEXT_SPACE
;
222 } else if (mi
->action_id
!= ACT_MAIN_NONE
) {
223 struct string keystroke
;
225 if (init_string(&keystroke
)) {
226 add_keystroke_action_to_string(&keystroke
, mi
->action_id
, KEYMAP_MAIN
);
227 rtext_width
= L_RTEXT_SPACE
+ keystroke
.length
+ R_RTEXT_SPACE
;
228 done_string(&keystroke
);
231 } else if (mi_has_right_text(mi
)) {
232 unsigned char *rtext
= mi
->rtext
;
234 if (mi_rtext_translate(mi
))
235 rtext
= _(rtext
, term
);
238 rtext_width
= L_RTEXT_SPACE
+ strlen(rtext
) + R_RTEXT_SPACE
;
245 get_menuitem_width(struct terminal
*term
, struct menu_item
*mi
, int max_width
)
247 int text_width
= get_menuitem_text_width(term
, mi
);
248 int rtext_width
= get_menuitem_rtext_width(term
, mi
);
250 int_upper_bound(&text_width
, max_width
);
251 int_upper_bound(&rtext_width
, max_width
- text_width
);
253 return text_width
+ rtext_width
;
257 count_menu_size(struct terminal
*term
, struct menu
*menu
)
259 struct menu_item
*item
;
260 int width
= term
->width
- MENU_BORDER_SIZE
* 2;
261 int height
= term
->height
- MENU_BORDER_SIZE
* 2;
262 int my
= int_min(menu
->size
, height
);
265 foreach_menu_item (item
, menu
->items
) {
266 int_lower_bound(&mx
, get_menuitem_width(term
, item
, width
));
270 menu
->parent_x
, menu
->parent_y
,
271 mx
+ MENU_BORDER_SIZE
* 2,
272 my
+ MENU_BORDER_SIZE
* 2);
274 int_bounds(&menu
->box
.x
, 0, width
- mx
);
275 int_bounds(&menu
->box
.y
, 0, height
- my
);
279 scroll_menu(struct menu
*menu
, int steps
, int wrap
)
282 int s
= steps
? steps
/abs(steps
) : 1; /* Selectable item search direction. */
284 /* menu->selected sometimes became -2 and caused infinite loops.
285 * That should no longer be possible. */
286 assert(menu
->selected
>= -1);
287 if_assert_failed
return;
289 if (menu
->size
<= 0) {
297 start
= pos
= menu
->selected
;
300 /* The caller wants us to check that menu->selected is
301 * actually selectable, and correct it if not. */
308 pos
+= s
, steps
-= s
;
313 } else if (pos
>= menu
->size
&& s
== 1) {
317 pos
= menu
->size
- 1;
320 } else if (pos
< 0 && s
== -1) {
322 pos
= menu
->size
- 1;
327 } else if (!mi_is_selectable(&menu
->items
[pos
])) {
333 if (start
== -1) start
= 0;
338 if (!mi_is_selectable(&menu
->items
[pos
]))
340 set_menu_selection(menu
, pos
);
343 /* Set menu->selected = pos, and adjust menu->first if needed.
344 * This neither redraws the menu nor runs the item's action.
345 * The caller must ensure that menu->items[pos] is selectable. */
347 set_menu_selection(struct menu
*menu
, int pos
)
351 assert(pos
>= 0 && pos
< menu
->size
);
352 assert(mi_is_selectable(&menu
->items
[pos
]));
353 if_assert_failed
return;
355 menu
->selected
= pos
;
357 height
= int_max(1, menu
->box
.height
- MENU_BORDER_SIZE
* 2);
359 /* The rest is not needed for horizontal menus like the mainmenu.
360 * FIXME: We need a better way to figure out which menus are horizontal and
361 * which are vertical (normal) --jonas */
362 if (height
== 1) return;
364 scr_i
= int_min((height
- 1) / 2, SCROLL_ITEMS
);
366 int_bounds(&menu
->first
, menu
->selected
- height
+ scr_i
+ 1, menu
->selected
- scr_i
);
367 int_bounds(&menu
->first
, 0, menu
->size
- height
);
370 /* width - number of standard terminal cells to be displayed (text + whitespace
371 * separators). For double-width glyph width == 2.
372 * len - length of text in bytes */
374 draw_menu_left_text(struct terminal
*term
, unsigned char *text
, int len
,
375 int x
, int y
, int width
, struct color_pair
*color
)
377 int w
= width
- (L_TEXT_SPACE
+ R_TEXT_SPACE
);
382 if (len
< 0) len
= strlen(text
);
387 max_len
= utf8_cells2bytes(text
, w
, NULL
);
391 #endif /* CONFIG_UTF8 */
394 if (len
> max_len
) len
= max_len
;
396 draw_text(term
, x
+ L_TEXT_SPACE
, y
, text
, len
, 0, color
);
401 draw_menu_left_text_hk(struct terminal
*term
, unsigned char *text
,
402 int hotkey_pos
, int x
, int y
, int width
,
403 struct color_pair
*color
, int selected
)
405 struct color_pair
*hk_color
= get_bfu_color(term
, "menu.hotkey.normal");
406 struct color_pair
*hk_color_sel
= get_bfu_color(term
, "menu.hotkey.selected");
407 enum screen_char_attr hk_attr
= get_opt_bool("ui.dialogs.underline_hotkeys", NULL
)
408 ? SCREEN_ATTR_UNDERLINE
: 0;
410 int xbase
= x
+ L_TEXT_SPACE
;
411 int w
= width
- (L_TEXT_SPACE
+ R_TEXT_SPACE
);
414 unsigned char *text2
, *end
;
418 /* For redundant hotkeys highlighting. */
421 if (hotkey_pos
< 0) hotkey_pos
= -hotkey_pos
, double_hk
= 1;
424 if (!hotkey_pos
|| w
<= 0) return;
427 struct color_pair
*tmp
= hk_color
;
429 hk_color
= hk_color_sel
;
434 if (term
->utf8_cp
) goto utf8
;
435 #endif /* CONFIG_UTF8 */
437 for (x
= 0; x
- !!hk_state
< w
&& (c
= text
[x
]); x
++) {
438 if (!hk_state
&& x
== hotkey_pos
- 1) {
445 draw_char(term
, xbase
+ x
- 1, y
, c
, hk_attr
,
446 (double_hk
? hk_color_sel
: hk_color
));
448 draw_char(term
, xbase
+ x
- 1, y
, c
, hk_attr
, hk_color
);
449 #endif /* CONFIG_DEBUG */
452 draw_char(term
, xbase
+ x
- !!hk_state
, y
, c
, 0, color
);
459 end
= strchr((const char *)text
, '\0');
461 for (x
= 0; x
- !!hk_state
< w
&& *text2
; x
++) {
464 data
= utf8_to_unicode(&text2
, end
);
465 if (!hk_state
&& (int)(text2
- text
) == hotkey_pos
) {
470 if (unicode_to_cell(data
) == 2) {
471 if (x
< w
&& xbase
+ x
< term
->width
) {
473 draw_char(term
, xbase
+ x
- 1, y
,
475 (double_hk
? hk_color_sel
478 draw_char(term
, xbase
+ x
- 1, y
,
479 data
, hk_attr
, hk_color
);
480 #endif /* CONFIG_DEBUG */
482 draw_char(term
, xbase
+ x
- 1, y
,
483 UCS_NO_CHAR
, 0, hk_color
);
485 draw_char(term
, xbase
+ x
- 1, y
,
486 UCS_ORPHAN_CELL
, 0, hk_color
);
490 draw_char(term
, xbase
+ x
- 1, y
,
492 (double_hk
? hk_color_sel
495 draw_char(term
, xbase
+ x
- 1, y
,
496 data
, hk_attr
, hk_color
);
497 #endif /* CONFIG_DEBUG */
501 if (unicode_to_cell(data
) == 2) {
502 if (x
- !!hk_state
+ 1 < w
&&
503 xbase
+ x
- !!hk_state
+ 1 < term
->width
) {
504 draw_char(term
, xbase
+ x
- !!hk_state
,
507 draw_char(term
, xbase
+ x
- !!hk_state
,
508 y
, UCS_NO_CHAR
, 0, color
);
510 draw_char(term
, xbase
+ x
- !!hk_state
,
511 y
, UCS_ORPHAN_CELL
, 0, color
);
514 draw_char(term
, xbase
+ x
- !!hk_state
,
520 #endif /* CONFIG_UTF8 */
524 draw_menu_right_text(struct terminal
*term
, unsigned char *text
, int len
,
525 int x
, int y
, int width
, struct color_pair
*color
)
527 int w
= width
- (L_RTEXT_SPACE
+ R_RTEXT_SPACE
);
531 if (len
< 0) len
= strlen(text
);
533 if (len
> w
) len
= w
;
535 x
+= w
- len
+ L_RTEXT_SPACE
+ L_TEXT_SPACE
;
537 draw_text(term
, x
, y
, text
, len
, 0, color
);
541 display_menu(struct terminal
*term
, struct menu
*menu
)
543 struct color_pair
*normal_color
= get_bfu_color(term
, "menu.normal");
544 struct color_pair
*selected_color
= get_bfu_color(term
, "menu.selected");
545 struct color_pair
*frame_color
= get_bfu_color(term
, "menu.frame");
551 menu
->box
.x
+ MENU_BORDER_SIZE
,
552 menu
->box
.y
+ MENU_BORDER_SIZE
,
553 int_max(0, menu
->box
.width
- MENU_BORDER_SIZE
* 2),
554 int_max(0, menu
->box
.height
- MENU_BORDER_SIZE
* 2));
556 draw_box(term
, &box
, ' ', 0, normal_color
);
557 draw_border(term
, &box
, frame_color
, 1);
559 if (get_opt_bool("ui.dialogs.shadows", NULL
)) {
561 draw_shadow(term
, &menu
->box
,
562 get_bfu_color(term
, "dialog.shadow"), 2, 1);
565 fix_dwchar_around_box(term
, &box
, 1, 2, 1);
566 #endif /* CONFIG_UTF8 */
569 else if (term
->utf8_cp
)
570 fix_dwchar_around_box(term
, &box
, 1, 0, 0);
571 #endif /* CONFIG_UTF8 */
573 menu_height
= box
.height
;
576 for (p
= menu
->first
;
577 p
< menu
->size
&& p
< menu
->first
+ menu_height
;
579 struct color_pair
*color
= normal_color
;
580 struct menu_item
*mi
= &menu
->items
[p
];
581 int selected
= (p
== menu
->selected
);
585 if (mi_is_end_of_menu(mi
))
586 INTERNAL("Unexpected end of menu [%p:%d]", mi
, p
);
590 /* This entry is selected. */
591 color
= selected_color
;
593 set_cursor(term
, box
.x
, box
.y
, 1);
594 set_window_ptr(menu
->win
, menu
->box
.x
+ menu
->box
.width
, box
.y
);
595 draw_box(term
, &box
, ' ', 0, color
);
598 if (mi_is_horizontal_bar(mi
)) {
599 /* Horizontal separator */
600 draw_border_char(term
, menu
->box
.x
, box
.y
,
601 BORDER_SRTEE
, frame_color
);
603 draw_box(term
, &box
, BORDER_SHLINE
,
604 SCREEN_ATTR_FRAME
, frame_color
);
606 draw_border_char(term
, box
.x
+ box
.width
, box
.y
,
607 BORDER_SLTEE
, frame_color
);
612 if (mi_has_left_text(mi
)) {
613 int l
= mi
->hotkey_pos
;
614 unsigned char *text
= mi
->text
;
616 if (mi_text_translate(mi
))
617 text
= _(text
, term
);
619 if (!mi_is_selectable(mi
))
623 draw_menu_left_text_hk(term
, text
, l
,
624 box
.x
, box
.y
, box
.width
, color
,
628 draw_menu_left_text(term
, text
, -1,
629 box
.x
, box
.y
, box
.width
, color
);
633 if (mi_is_submenu(mi
)) {
634 draw_menu_right_text(term
, m_submenu
, m_submenu_len
,
635 menu
->box
.x
, box
.y
, box
.width
, color
);
636 } else if (mi
->action_id
!= ACT_MAIN_NONE
) {
637 struct string keystroke
;
640 /* Help to detect action + right text. --Zas */
641 if (mi_has_right_text(mi
)) {
642 if (color
== selected_color
)
643 color
= normal_color
;
645 color
= selected_color
;
647 #endif /* CONFIG_DEBUG */
649 if (init_string(&keystroke
)) {
650 add_keystroke_action_to_string(&keystroke
,
653 draw_menu_right_text(term
, keystroke
.source
,
657 done_string(&keystroke
);
660 } else if (mi_has_right_text(mi
)) {
661 unsigned char *rtext
= mi
->rtext
;
663 if (mi_rtext_translate(mi
))
664 rtext
= _(rtext
, term
);
667 /* There's a right text, so print it */
668 draw_menu_right_text(term
, rtext
, -1,
670 box
.y
, box
.width
, color
);
675 redraw_windows(REDRAW_IN_FRONT_OF_WINDOW
, menu
->win
);
681 menu_mouse_handler(struct menu
*menu
, struct term_event
*ev
)
683 struct window
*win
= menu
->win
;
684 int scroll_direction
= 1;
686 switch (get_mouse_button(ev
)) {
688 scroll_direction
= -1;
691 if (check_mouse_action(ev
, B_DOWN
)) {
692 scroll_menu(menu
, scroll_direction
, 1);
693 display_menu(win
->term
, menu
);
698 if (!check_mouse_position(ev
, &menu
->box
)) {
699 if (check_mouse_action(ev
, B_DOWN
)) {
700 delete_window_ev(win
, ev
);
704 struct window
*end
= (struct window
*) &win
->term
->windows
;
706 for (w1
= win
; w1
!= end
; w1
= w1
->next
) {
709 if (w1
->handler
== mainmenu_handler
) {
710 if (!ev
->info
.mouse
.y
)
711 delete_window_ev(win
, ev
);
715 if (w1
->handler
!= menu_handler
) break;
719 if (check_mouse_position(ev
, &m1
->box
)) {
720 delete_window_ev(win
, ev
);
727 int sel
= ev
->info
.mouse
.y
- menu
->box
.y
- 1 + menu
->first
;
729 if (sel
>= 0 && sel
< menu
->size
730 && mi_is_selectable(&menu
->items
[sel
])) {
731 set_menu_selection(menu
, sel
);
732 display_menu(win
->term
, menu
);
733 select_menu(win
->term
, menu
);
742 menu_page_up(struct menu
*menu
)
744 int current
= int_max(0, int_min(menu
->selected
, menu
->size
- 1));
749 for (i
= current
- 1; i
> 0; i
--)
750 if (mi_is_horizontal_bar(&menu
->items
[i
])) {
755 step
= current
- next_sep
+ 1;
756 int_bounds(&step
, 0, int_min(current
, DIST
));
758 scroll_menu(menu
, -step
, 0);
762 menu_page_down(struct menu
*menu
)
764 int current
= int_max(0, int_min(menu
->selected
, menu
->size
- 1));
767 int next_sep
= menu
->size
- 1;
769 for (i
= current
+ 1; i
< menu
->size
; i
++)
770 if (mi_is_horizontal_bar(&menu
->items
[i
])) {
775 step
= next_sep
- current
+ 1;
776 int_bounds(&step
, 0, int_min(menu
->size
- 1 - current
, DIST
));
778 scroll_menu(menu
, step
, 0);
784 search_menu_item(struct menu_item
*item
, unsigned char *buffer
,
785 struct terminal
*term
)
787 unsigned char *text
, *match
;
789 /* set_menu_selection asserts selectability. */
790 if (!mi_has_left_text(item
) || !mi_is_selectable(item
)) return 0;
792 text
= mi_text_translate(item
) ? _(item
->text
, term
) : item
->text
;
794 /* Crap. We have to remove the hotkey markers '~' */
795 text
= stracpy(text
);
798 match
= strchr((const char *)text
, '~');
800 memmove(match
, match
+ 1, strlen(match
));
802 match
= strcasestr((const char *)text
, (const char *)buffer
);
808 static enum input_line_code
809 menu_search_handler(struct input_line
*line
, int action_id
)
811 struct menu
*menu
= line
->data
;
812 struct terminal
*term
= menu
->win
->term
;
813 unsigned char *buffer
= line
->buffer
;
815 int pos
= menu
->selected
;
820 case ACT_EDIT_REDRAW
:
821 return INPUT_LINE_PROCEED
;
824 /* XXX: The input line dialog window is above the menu window.
825 * Remove it from the top, so that select_menu() will correctly
826 * remove all the windows it has to and then readd it. */
827 win
= term
->windows
.next
;
829 select_menu(term
, menu
);
830 add_to_list(term
->windows
, win
);
831 return INPUT_LINE_CANCEL
;
833 case ACT_EDIT_PREVIOUS_ITEM
:
838 case ACT_EDIT_NEXT_ITEM
:
844 /* If there is nothing to match with don't start searching */
845 if (!*buffer
) return INPUT_LINE_PROCEED
;
851 struct menu_item
*item
= &menu
->items
[pos
];
853 if (search_menu_item(item
, buffer
, term
)) {
854 set_menu_selection(menu
, pos
);
855 display_menu(term
, menu
);
856 return INPUT_LINE_PROCEED
;
861 if (pos
== menu
->size
) pos
= 0;
862 else if (pos
< 0) pos
= menu
->size
- 1;
863 } while (pos
!= start
);
865 return INPUT_LINE_CANCEL
;
869 search_menu(struct menu
*menu
)
871 struct terminal
*term
= menu
->win
->term
;
872 struct window
*current_tab
= get_current_tab(term
);
873 struct session
*ses
= current_tab
? current_tab
->data
: NULL
;
874 unsigned char *prompt
= _("Search menu/", term
);
876 if (menu
->size
< 1 || !ses
) return;
878 input_field_line(ses
, prompt
, menu
, NULL
, menu_search_handler
);
882 menu_kbd_handler(struct menu
*menu
, struct term_event
*ev
)
884 struct window
*win
= menu
->win
;
885 enum menu_action action_id
= kbd_action(KEYMAP_MENU
, ev
, NULL
);
891 if (list_has_next(win
->term
->windows
, win
)
892 && win
->next
->handler
== mainmenu_handler
) {
893 struct window
*next_win
= win
->next
;
895 delete_window_ev(win
, ev
);
897 select_menu(next_win
->term
, next_win
->data
);
902 if (action_id
== ACT_MENU_RIGHT
)
909 scroll_menu(menu
, -1, 1);
913 scroll_menu(menu
, 1, 1);
917 scroll_menu(menu
, -menu
->selected
, 0);
921 scroll_menu(menu
, menu
->size
- menu
->selected
- 1, 0);
924 case ACT_MENU_PAGE_UP
:
928 case ACT_MENU_PAGE_DOWN
:
929 menu_page_down(menu
);
933 case ACT_MENU_SELECT
:
936 case ACT_MENU_SEARCH
:
940 case ACT_MENU_REDRAW
:
941 display_menu(win
->term
, menu
);
942 redraw_terminal_cls(win
->term
);
945 case ACT_MENU_CANCEL
:
946 if (list_has_next(win
->term
->windows
, win
)
947 && win
->next
->handler
== mainmenu_handler
)
948 delete_window_ev(win
, ev
);
950 delete_window_ev(win
, NULL
);
956 term_event_key_T key
= get_kbd_key(ev
);
959 || check_kbd_modifier(ev
, KBD_MOD_ALT
)) {
960 delete_window_ev(win
, ev
);
964 if (!check_kbd_label_key(ev
))
967 if (menu
->hotkeys
!= -1) {
968 s
= check_hotkeys(menu
, key
, win
->term
);
970 if (s
|| check_not_so_hot_keys(menu
, key
, win
->term
)) {
971 scroll_menu(menu
, 0, 1);
974 struct terminal
*term
= win
->term
;
975 struct keybinding
*auto_complete
= kbd_nm_lookup(KEYMAP_EDIT
, "auto-complete");
977 delete_window_ev(win
, NULL
);
978 term_send_event(term
, ev
);
981 struct term_event complete
;
983 complete
.ev
= EVENT_KBD
;
984 complete
.info
.keyboard
= auto_complete
->kbd
;
985 term_send_event(term
, &complete
);
992 display_menu(win
->term
, menu
);
995 select_menu(win
->term
, menu
);
1000 menu_handler(struct window
*win
, struct term_event
*ev
)
1002 struct menu
*menu
= win
->data
;
1009 get_parent_ptr(win
, &menu
->parent_x
, &menu
->parent_y
);
1011 count_menu_size(win
->term
, menu
);
1012 /* do_menu sets menu->selected = 0. If that
1013 * item isn't actually selectable, correct
1014 * menu->selected here. */
1015 scroll_menu(menu
, 0, 1);
1016 display_menu(win
->term
, menu
);
1021 menu_mouse_handler(menu
, ev
);
1022 #endif /* CONFIG_MOUSE */
1026 menu_kbd_handler(menu
, ev
);
1030 free_menu_items(menu
->items
);
1037 do_mainmenu(struct terminal
*term
, struct menu_item
*items
,
1038 void *data
, int sel
)
1044 if (!term
->main_menu
) {
1045 term
->main_menu
= mem_calloc(1, sizeof(*menu
));
1046 if (!term
->main_menu
) return;
1050 menu
= term
->main_menu
;
1051 menu
->selected
= (sel
== -1 ? 0 : sel
);
1052 menu
->items
= items
;
1054 menu
->size
= count_items(items
);
1058 clear_hotkeys_cache(menu
);
1060 init_hotkeys(term
, menu
);
1062 add_window(term
, mainmenu_handler
, menu
);
1064 /* This should be fine because add_window will call
1065 * mainmenu_handler which will assign the window to menu->win.
1068 deselect_mainmenu(term
, menu
);
1070 foreach (win
, term
->windows
) {
1071 if (win
->data
== menu
) {
1073 add_to_list(term
->windows
, win
);
1074 display_mainmenu(term
, menu
);
1081 select_menu(term
, menu
);
1086 display_mainmenu(struct terminal
*term
, struct menu
*menu
)
1088 struct color_pair
*normal_color
= get_bfu_color(term
, "menu.normal");
1089 struct color_pair
*selected_color
= get_bfu_color(term
, "menu.selected");
1094 /* FIXME: menu horizontal scrolling do not work well yet, we need to cache
1095 * menu items width and recalculate them only when needed (ie. language change)
1096 * instead of looping and calculate them each time. --Zas */
1098 /* Try to make current selected menu entry visible. */
1099 if (menu
->selected
< menu
->first
) {
1100 int num_items_offscreen
= menu
->selected
- menu
->first
;
1102 menu
->first
+= num_items_offscreen
;
1103 menu
->last
+= num_items_offscreen
;
1104 } else if (menu
->selected
> menu
->last
) {
1105 int num_items_offscreen
= menu
->last
- menu
->selected
;
1107 menu
->first
-= num_items_offscreen
;
1108 menu
->last
-= num_items_offscreen
;
1111 if (menu
->last
<= 0)
1112 menu
->last
= menu
->size
- 1;
1114 int_bounds(&menu
->last
, 0, menu
->size
- 1);
1115 int_bounds(&menu
->first
, 0, menu
->last
);
1117 set_box(&box
, 0, 0, term
->width
, 1);
1118 draw_box(term
, &box
, ' ', 0, normal_color
);
1120 if (menu
->first
!= 0) {
1121 box
.width
= L_MAINMENU_SPACE
;
1122 draw_box(term
, &box
, '<', 0, normal_color
);
1125 p
+= L_MAINMENU_SPACE
;
1127 for (i
= menu
->first
; i
< menu
->size
; i
++) {
1128 struct menu_item
*mi
= &menu
->items
[i
];
1129 struct color_pair
*color
= normal_color
;
1130 unsigned char *text
= mi
->text
;
1131 int l
= mi
->hotkey_pos
;
1133 int selected
= (i
== menu
->selected
);
1136 if (mi_text_translate(mi
))
1137 text
= _(text
, term
);
1139 textlen
= strlen(text
) - !!l
;
1142 screencnt
= utf8_ptr2cells(text
, NULL
) - !!l
;
1144 #endif /* CONFIG_UTF8 */
1145 screencnt
= textlen
;
1148 color
= selected_color
;
1152 box
.width
= L_MAINTEXT_SPACE
+ L_TEXT_SPACE
1154 + R_TEXT_SPACE
+ R_MAINTEXT_SPACE
;
1156 #endif /* CONFIG_UTF8 */
1157 box
.width
= L_MAINTEXT_SPACE
+ L_TEXT_SPACE
1159 + R_TEXT_SPACE
+ R_MAINTEXT_SPACE
;
1161 draw_box(term
, &box
, ' ', 0, color
);
1162 set_cursor(term
, p
, 0, 1);
1163 set_window_ptr(menu
->win
, p
, 1);
1166 p
+= L_MAINTEXT_SPACE
;
1169 draw_menu_left_text_hk(term
, text
, l
,
1170 p
, 0, textlen
+ R_TEXT_SPACE
+ L_TEXT_SPACE
,
1173 draw_menu_left_text(term
, text
, textlen
,
1174 p
, 0, textlen
+ R_TEXT_SPACE
+ L_TEXT_SPACE
,
1180 if (p
>= term
->width
- R_MAINMENU_SPACE
)
1183 p
+= R_MAINTEXT_SPACE
+ R_TEXT_SPACE
+ L_TEXT_SPACE
;
1187 int_lower_bound(&menu
->last
, menu
->first
);
1188 if (menu
->last
< menu
->size
- 1) {
1190 if (term
->utf8_cp
) {
1191 struct screen_char
*schar
;
1193 schar
= get_char(term
, term
->width
- R_MAINMENU_SPACE
, 0);
1194 /* Is second cell of double-width char on the place where
1195 * first char of the R_MAINMENU_SPACE will be displayed? */
1196 if (schar
->data
== UCS_NO_CHAR
) {
1197 /* Replace double-width char with UCS_ORPHAN_CELL. */
1199 draw_char_data(term
, term
->width
- R_MAINMENU_SPACE
- 1,
1200 0, UCS_ORPHAN_CELL
);
1206 term
->width
- R_MAINMENU_SPACE
, 0,
1207 R_MAINMENU_SPACE
, 1);
1208 draw_box(term
, &box
, '>', 0, normal_color
);
1211 redraw_windows(REDRAW_IN_FRONT_OF_WINDOW
, menu
->win
);
1217 mainmenu_mouse_handler(struct menu
*menu
, struct term_event
*ev
)
1219 struct window
*win
= menu
->win
;
1220 struct menu_item
*item
;
1223 if (check_mouse_wheel(ev
))
1226 /* Mouse was clicked outside the mainmenu bar */
1227 if (ev
->info
.mouse
.y
) {
1228 if (check_mouse_action(ev
, B_DOWN
)) {
1229 deselect_mainmenu(win
->term
, menu
);
1230 display_mainmenu(win
->term
, menu
);
1236 /* First check if the mouse button was pressed in the side of the
1237 * terminal and simply scroll one step in that direction else iterate
1238 * through the menu items to see if it was pressed on a label. */
1239 if (ev
->info
.mouse
.x
< L_MAINMENU_SPACE
) {
1242 } else if (ev
->info
.mouse
.x
>= win
->term
->width
- R_MAINMENU_SPACE
) {
1246 int p
= L_MAINMENU_SPACE
;
1248 /* We don't initialize to menu->first here, since it breaks
1249 * horizontal scrolling using mouse in some cases. --Zas */
1250 foreach_menu_item (item
, menu
->items
) {
1251 unsigned char *text
= item
->text
;
1253 if (!mi_has_left_text(item
)) continue;
1255 if (mi_text_translate(item
))
1256 text
= _(item
->text
, win
->term
);
1258 /* The label width is made up of a little padding on
1259 * the sides followed by the text width substracting
1260 * one char if it has hotkeys (the '~' char) */
1261 p
+= L_MAINTEXT_SPACE
+ L_TEXT_SPACE
1262 + strlen(text
) - !!item
->hotkey_pos
1263 + R_TEXT_SPACE
+ R_MAINTEXT_SPACE
;
1265 if (ev
->info
.mouse
.x
< p
) {
1266 scroll
= (item
- menu
->items
) - menu
->selected
;
1273 scroll_menu(menu
, scroll
, 1);
1274 display_mainmenu(win
->term
, menu
);
1277 /* We need to select the menu item even if we didn't scroll
1278 * apparently because we will delete any drop down menus
1279 * in the clicking process. */
1280 select_menu(win
->term
, menu
);
1285 mainmenu_kbd_handler(struct menu
*menu
, struct term_event
*ev
)
1287 struct window
*win
= menu
->win
;
1288 enum menu_action action_id
= kbd_action(KEYMAP_MENU
, ev
, NULL
);
1290 switch (action_id
) {
1291 case ACT_MENU_ENTER
:
1294 case ACT_MENU_PAGE_UP
:
1295 case ACT_MENU_PAGE_DOWN
:
1296 case ACT_MENU_SELECT
:
1297 select_menu(win
->term
, menu
);
1301 scroll_menu(menu
, -menu
->selected
, 0);
1305 scroll_menu(menu
, menu
->size
- menu
->selected
- 1, 0);
1308 case ACT_MENU_NEXT_ITEM
:
1309 case ACT_MENU_PREVIOUS_ITEM
:
1310 /* This is pretty western centric since `what is next'?
1311 * Anyway we cycle clockwise by resetting the action ... */
1312 action_id
= (action_id
== ACT_MENU_NEXT_ITEM
)
1313 ? ACT_MENU_RIGHT
: ACT_MENU_LEFT
;
1314 /* ... and then letting left/right handling take over. */
1317 case ACT_MENU_RIGHT
:
1318 scroll_menu(menu
, action_id
== ACT_MENU_LEFT
? -1 : 1, 1);
1321 case ACT_MENU_REDRAW
:
1322 display_mainmenu(win
->term
, menu
);
1323 redraw_terminal_cls(win
->term
);
1327 /* Fallback to see if any hotkey matches the pressed key */
1328 if (check_kbd_label_key(ev
)
1329 && check_hotkeys(menu
, get_kbd_key(ev
), win
->term
)) {
1330 display_mainmenu(win
->term
, menu
);
1331 select_menu(win
->term
, menu
);
1336 case ACT_MENU_CANCEL
:
1337 deselect_mainmenu(win
->term
, menu
);
1341 /* Redraw the menu */
1342 display_mainmenu(win
->term
, menu
);
1346 mainmenu_handler(struct window
*win
, struct term_event
*ev
)
1348 struct menu
*menu
= win
->data
;
1356 display_mainmenu(win
->term
, menu
);
1361 mainmenu_mouse_handler(menu
, ev
);
1362 #endif /* CONFIG_MOUSE */
1366 mainmenu_kbd_handler(menu
, ev
);
1374 /* For dynamic menus the last (cleared) item is used to mark the end. */
1375 #define realloc_menu_items(mi_, size) \
1376 mem_align_alloc(mi_, size, (size) + 2, 0xF)
1379 new_menu(enum menu_item_flags flags
)
1381 struct menu_item
*mi
= NULL
;
1383 if (realloc_menu_items(&mi
, 0)) mi
->flags
= flags
;
1389 add_to_menu(struct menu_item
**mi
, unsigned char *text
, unsigned char *rtext
,
1390 enum main_action action_id
, menu_func_T func
, void *data
,
1391 enum menu_item_flags flags
)
1393 int n
= count_items(*mi
);
1394 /* XXX: Don't clear the last and special item. */
1395 struct menu_item
*item
= realloc_menu_items(mi
, n
+ 1);
1401 /* Shift current last item by one place. */
1402 copy_struct(item
+ 1, item
);
1404 /* Setup the new item. All menu items share the item_free value. */
1405 SET_MENU_ITEM(item
, text
, rtext
, action_id
, func
, data
,
1406 item
->flags
| flags
, HKS_SHOW
, 0);
1409 #undef L_MAINMENU_SPACE
1410 #undef R_MAINMENU_SPACE
1411 #undef L_MAINTEXT_SPACE
1412 #undef R_MAINTEXT_SPACE
1413 #undef L_RTEXT_SPACE
1414 #undef R_RTEXT_SPACE
1417 #undef MENU_BORDER_SIZE