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
, "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_UTF_8 */
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_UTF_8 */
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")
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
) goto utf8
;
435 #endif /* CONFIG_UTF_8 */
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(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
,
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
,
514 draw_char(term
, xbase
+ x
- !!hk_state
,
520 #endif /* CONFIG_UTF_8 */
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")) {
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_UTF_8 */
570 fix_dwchar_around_box(term
, &box
, 1, 0, 0);
571 #endif /* CONFIG_UTF_8 */
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_from_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(text
, '~');
800 memmove(match
, match
+ 1, strlen(match
));
802 match
= strcasestr(text
, 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
*tab
= get_current_tab(term
);
873 struct session
*ses
= tab
? 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_CANCEL
:
941 if (list_has_next(win
->term
->windows
, win
)
942 && win
->next
->handler
== mainmenu_handler
)
943 delete_window_ev(win
, ev
);
945 delete_window_ev(win
, NULL
);
951 term_event_key_T key
= get_kbd_key(ev
);
954 || check_kbd_modifier(ev
, KBD_MOD_ALT
)) {
955 delete_window_ev(win
, ev
);
959 if (!check_kbd_label_key(ev
))
962 s
= check_hotkeys(menu
, key
, win
->term
);
964 if (s
|| check_not_so_hot_keys(menu
, key
, win
->term
))
965 scroll_menu(menu
, 0, 1);
969 display_menu(win
->term
, menu
);
972 select_menu(win
->term
, menu
);
977 menu_handler(struct window
*win
, struct term_event
*ev
)
979 struct menu
*menu
= win
->data
;
987 get_parent_ptr(win
, &menu
->parent_x
, &menu
->parent_y
);
988 count_menu_size(win
->term
, menu
);
989 /* do_menu sets menu->selected = 0. If that
990 * item isn't actually selectable, correct
991 * menu->selected here. */
992 scroll_menu(menu
, 0, 1);
993 display_menu(win
->term
, menu
);
998 menu_mouse_handler(menu
, ev
);
999 #endif /* CONFIG_MOUSE */
1003 menu_kbd_handler(menu
, ev
);
1007 free_menu_items(menu
->items
);
1014 do_mainmenu(struct terminal
*term
, struct menu_item
*items
,
1015 void *data
, int sel
)
1021 if (!term
->main_menu
) {
1022 term
->main_menu
= mem_calloc(1, sizeof(*menu
));
1023 if (!term
->main_menu
) return;
1027 menu
= term
->main_menu
;
1028 menu
->selected
= (sel
== -1 ? 0 : sel
);
1029 menu
->items
= items
;
1031 menu
->size
= count_items(items
);
1035 clear_hotkeys_cache(menu
);
1037 init_hotkeys(term
, menu
);
1039 add_window(term
, mainmenu_handler
, menu
);
1041 /* This should be fine because add_window will call
1042 * mainmenu_handler which will assign the window to menu->win.
1045 deselect_mainmenu(term
, menu
);
1047 foreach (win
, term
->windows
) {
1048 if (win
->data
== menu
) {
1050 add_to_list(term
->windows
, win
);
1051 display_mainmenu(term
, menu
);
1058 select_menu(term
, menu
);
1063 display_mainmenu(struct terminal
*term
, struct menu
*menu
)
1065 struct color_pair
*normal_color
= get_bfu_color(term
, "menu.normal");
1066 struct color_pair
*selected_color
= get_bfu_color(term
, "menu.selected");
1071 /* FIXME: menu horizontal scrolling do not work well yet, we need to cache
1072 * menu items width and recalculate them only when needed (ie. language change)
1073 * instead of looping and calculate them each time. --Zas */
1075 /* Try to make current selected menu entry visible. */
1076 if (menu
->selected
< menu
->first
) {
1077 int num_items_offscreen
= menu
->selected
- menu
->first
;
1079 menu
->first
+= num_items_offscreen
;
1080 menu
->last
+= num_items_offscreen
;
1081 } else if (menu
->selected
> menu
->last
) {
1082 int num_items_offscreen
= menu
->last
- menu
->selected
;
1084 menu
->first
-= num_items_offscreen
;
1085 menu
->last
-= num_items_offscreen
;
1088 if (menu
->last
<= 0)
1089 menu
->last
= menu
->size
- 1;
1091 int_bounds(&menu
->last
, 0, menu
->size
- 1);
1092 int_bounds(&menu
->first
, 0, menu
->last
);
1094 set_box(&box
, 0, 0, term
->width
, 1);
1095 draw_box(term
, &box
, ' ', 0, normal_color
);
1097 if (menu
->first
!= 0) {
1098 box
.width
= L_MAINMENU_SPACE
;
1099 draw_box(term
, &box
, '<', 0, normal_color
);
1102 p
+= L_MAINMENU_SPACE
;
1104 for (i
= menu
->first
; i
< menu
->size
; i
++) {
1105 struct menu_item
*mi
= &menu
->items
[i
];
1106 struct color_pair
*color
= normal_color
;
1107 unsigned char *text
= mi
->text
;
1108 int l
= mi
->hotkey_pos
;
1110 int selected
= (i
== menu
->selected
);
1113 if (mi_text_translate(mi
))
1114 text
= _(text
, term
);
1116 textlen
= strlen(text
) - !!l
;
1119 screencnt
= utf8_ptr2cells(text
, NULL
) - !!l
;
1121 #endif /* CONFIG_UTF_8 */
1122 screencnt
= textlen
;
1125 color
= selected_color
;
1129 box
.width
= L_MAINTEXT_SPACE
+ L_TEXT_SPACE
1131 + R_TEXT_SPACE
+ R_MAINTEXT_SPACE
;
1133 #endif /* CONFIG_UTF_8 */
1134 box
.width
= L_MAINTEXT_SPACE
+ L_TEXT_SPACE
1136 + R_TEXT_SPACE
+ R_MAINTEXT_SPACE
;
1138 draw_box(term
, &box
, ' ', 0, color
);
1139 set_cursor(term
, p
, 0, 1);
1140 set_window_ptr(menu
->win
, p
, 1);
1143 p
+= L_MAINTEXT_SPACE
;
1146 draw_menu_left_text_hk(term
, text
, l
,
1147 p
, 0, textlen
+ R_TEXT_SPACE
+ L_TEXT_SPACE
,
1150 draw_menu_left_text(term
, text
, textlen
,
1151 p
, 0, textlen
+ R_TEXT_SPACE
+ L_TEXT_SPACE
,
1157 if (p
>= term
->width
- R_MAINMENU_SPACE
)
1160 p
+= R_MAINTEXT_SPACE
+ R_TEXT_SPACE
+ L_TEXT_SPACE
;
1164 int_lower_bound(&menu
->last
, menu
->first
);
1165 if (menu
->last
< menu
->size
- 1) {
1168 struct screen_char
*schar
;
1170 schar
= get_char(term
, term
->width
- R_MAINMENU_SPACE
, 0);
1171 /* Is second cell of double-width char on the place where
1172 * first char of the R_MAINMENU_SPACE will be displayed? */
1173 if (schar
->data
== UCS_NO_CHAR
) {
1174 /* Replace double-width char with ' '. */
1176 draw_char_data(term
, term
->width
- R_MAINMENU_SPACE
- 1,
1183 term
->width
- R_MAINMENU_SPACE
, 0,
1184 R_MAINMENU_SPACE
, 1);
1185 draw_box(term
, &box
, '>', 0, normal_color
);
1188 redraw_from_window(menu
->win
);
1194 mainmenu_mouse_handler(struct menu
*menu
, struct term_event
*ev
)
1196 struct window
*win
= menu
->win
;
1197 struct menu_item
*item
;
1200 if (check_mouse_wheel(ev
))
1203 /* Mouse was clicked outside the mainmenu bar */
1204 if (ev
->info
.mouse
.y
) {
1205 if (check_mouse_action(ev
, B_DOWN
)) {
1206 deselect_mainmenu(win
->term
, menu
);
1207 display_mainmenu(win
->term
, menu
);
1213 /* First check if the mouse button was pressed in the side of the
1214 * terminal and simply scroll one step in that direction else iterate
1215 * through the menu items to see if it was pressed on a label. */
1216 if (ev
->info
.mouse
.x
< L_MAINMENU_SPACE
) {
1219 } else if (ev
->info
.mouse
.x
>= win
->term
->width
- R_MAINMENU_SPACE
) {
1223 int p
= L_MAINMENU_SPACE
;
1225 /* We don't initialize to menu->first here, since it breaks
1226 * horizontal scrolling using mouse in some cases. --Zas */
1227 foreach_menu_item (item
, menu
->items
) {
1228 unsigned char *text
= item
->text
;
1230 if (!mi_has_left_text(item
)) continue;
1232 if (mi_text_translate(item
))
1233 text
= _(item
->text
, win
->term
);
1235 /* The label width is made up of a little padding on
1236 * the sides followed by the text width substracting
1237 * one char if it has hotkeys (the '~' char) */
1238 p
+= L_MAINTEXT_SPACE
+ L_TEXT_SPACE
1239 + strlen(text
) - !!item
->hotkey_pos
1240 + R_TEXT_SPACE
+ R_MAINTEXT_SPACE
;
1242 if (ev
->info
.mouse
.x
< p
) {
1243 scroll
= (item
- menu
->items
) - menu
->selected
;
1250 scroll_menu(menu
, scroll
, 1);
1251 display_mainmenu(win
->term
, menu
);
1254 /* We need to select the menu item even if we didn't scroll
1255 * apparently because we will delete any drop down menus
1256 * in the clicking process. */
1257 select_menu(win
->term
, menu
);
1262 mainmenu_kbd_handler(struct menu
*menu
, struct term_event
*ev
)
1264 struct window
*win
= menu
->win
;
1265 enum menu_action action_id
= kbd_action(KEYMAP_MENU
, ev
, NULL
);
1267 switch (action_id
) {
1268 case ACT_MENU_ENTER
:
1271 case ACT_MENU_PAGE_UP
:
1272 case ACT_MENU_PAGE_DOWN
:
1273 case ACT_MENU_SELECT
:
1274 select_menu(win
->term
, menu
);
1278 scroll_menu(menu
, -menu
->selected
, 0);
1282 scroll_menu(menu
, menu
->size
- menu
->selected
- 1, 0);
1285 case ACT_MENU_NEXT_ITEM
:
1286 case ACT_MENU_PREVIOUS_ITEM
:
1287 /* This is pretty western centric since `what is next'?
1288 * Anyway we cycle clockwise by resetting the action ... */
1289 action_id
= (action_id
== ACT_MENU_NEXT_ITEM
)
1290 ? ACT_MENU_RIGHT
: ACT_MENU_LEFT
;
1291 /* ... and then letting left/right handling take over. */
1294 case ACT_MENU_RIGHT
:
1295 scroll_menu(menu
, action_id
== ACT_MENU_LEFT
? -1 : 1, 1);
1298 case ACT_MENU_REDRAW
:
1299 /* Just call display_mainmenu() */
1303 /* Fallback to see if any hotkey matches the pressed key */
1304 if (check_kbd_label_key(ev
)
1305 && check_hotkeys(menu
, get_kbd_key(ev
), win
->term
)) {
1306 display_mainmenu(win
->term
, menu
);
1307 select_menu(win
->term
, menu
);
1312 case ACT_MENU_CANCEL
:
1313 deselect_mainmenu(win
->term
, menu
);
1317 /* Redraw the menu */
1318 display_mainmenu(win
->term
, menu
);
1322 mainmenu_handler(struct window
*win
, struct term_event
*ev
)
1324 struct menu
*menu
= win
->data
;
1332 display_mainmenu(win
->term
, menu
);
1337 mainmenu_mouse_handler(menu
, ev
);
1338 #endif /* CONFIG_MOUSE */
1342 mainmenu_kbd_handler(menu
, ev
);
1350 /* For dynamic menus the last (cleared) item is used to mark the end. */
1351 #define realloc_menu_items(mi_, size) \
1352 mem_align_alloc(mi_, size, (size) + 2, 0xF)
1355 new_menu(enum menu_item_flags flags
)
1357 struct menu_item
*mi
= NULL
;
1359 if (realloc_menu_items(&mi
, 0)) mi
->flags
= flags
;
1365 add_to_menu(struct menu_item
**mi
, unsigned char *text
, unsigned char *rtext
,
1366 enum main_action action_id
, menu_func_T func
, void *data
,
1367 enum menu_item_flags flags
)
1369 int n
= count_items(*mi
);
1370 /* XXX: Don't clear the last and special item. */
1371 struct menu_item
*item
= realloc_menu_items(mi
, n
+ 1);
1377 /* Shift current last item by one place. */
1378 copy_struct(item
+ 1, item
);
1380 /* Setup the new item. All menu items share the item_free value. */
1381 SET_MENU_ITEM(item
, text
, rtext
, action_id
, func
, data
,
1382 item
->flags
| flags
, HKS_SHOW
, 0);
1385 #undef L_MAINMENU_SPACE
1386 #undef R_MAINMENU_SPACE
1387 #undef L_MAINTEXT_SPACE
1388 #undef R_MAINTEXT_SPACE
1389 #undef L_RTEXT_SPACE
1390 #undef R_RTEXT_SPACE
1393 #undef MENU_BORDER_SIZE