Change "utf_8" to "utf8" in most identifiers.
[elinks.git] / src / bfu / menu.c
blobd91450bd78f7e92568e86ff77a682b9f3ad7bfed
1 /* Menu system implementation. */
3 #ifndef _GNU_SOURCE
4 #define _GNU_SOURCE /* XXX: we _WANT_ strcasestr() ! */
5 #endif
7 #ifdef HAVE_CONFIG_H
8 #include "config.h"
9 #endif
11 #include <stdlib.h>
12 #include <string.h>
14 #include "elinks.h"
16 #include "bfu/hotkey.h"
17 #include "bfu/inpfield.h"
18 #include "bfu/menu.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;
60 /* Prototypes */
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);
67 void
68 deselect_mainmenu(struct terminal *term, struct menu *menu)
70 menu->selected = -1;
71 del_from_list(menu->win);
72 add_to_list_end(term->windows, menu->win);
75 static inline int
76 count_items(struct menu_item *items)
78 int i = 0;
80 if (items) {
81 struct menu_item *item;
83 foreach_menu_item (item, items) {
84 i++;
88 return i;
91 static void
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);
107 mem_free(items);
110 void
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));
116 if (menu) {
117 menu->selected = selected;
118 menu->items = items;
119 menu->data = data;
120 menu->size = count_items(items);
121 menu->hotkeys = hotkeys;
122 #ifdef CONFIG_NLS
123 menu->lang = -1;
124 #endif
125 refresh_hotkeys(term, menu);
126 add_window(term, menu_handler, menu);
127 } else {
128 free_menu_items(items);
132 void
133 do_menu(struct terminal *term, struct menu_item *items, void *data, int hotkeys)
135 do_menu_selected(term, items, data, 0, hotkeys);
138 static void
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)
157 break;
159 if (win->handler == mainmenu_handler) {
160 deselect_mainmenu(term, win->data);
161 redraw_terminal(term);
162 } else
163 delete_window(win);
167 if (action_id != ACT_MAIN_NONE && !func) {
168 struct session *ses = data;
170 do_action(ses, action_id, 1);
171 return;
174 assertm(func, "No menu function");
175 if_assert_failed return;
177 func(term, it_data, data);
180 static inline void
181 select_menu(struct terminal *term, struct menu *menu)
183 if (menu->selected < 0 || menu->selected >= menu->size)
184 return;
186 select_menu_item(term, &menu->items[menu->selected], menu->data);
189 /* Get desired width for left text in menu item, accounting spacing. */
190 static int
191 get_menuitem_text_width(struct terminal *term, struct menu_item *mi)
193 unsigned char *text;
195 if (!mi_has_left_text(mi)) return 0;
197 text = mi->text;
198 if (mi_text_translate(mi))
199 text = _(text, term);
201 if (!text[0]) return 0;
203 #ifdef CONFIG_UTF_8
204 if (term->utf8)
205 return L_TEXT_SPACE + utf8_ptr2cells(text, NULL)
206 - !!mi->hotkey_pos + R_TEXT_SPACE;
207 else
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. */
214 static int
215 get_menuitem_rtext_width(struct terminal *term, struct menu_item *mi)
217 int rtext_width = 0;
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);
237 if (rtext[0])
238 rtext_width = L_RTEXT_SPACE + strlen(rtext) + R_RTEXT_SPACE;
241 return rtext_width;
244 static int
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;
256 static void
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);
263 int mx = 0;
265 foreach_menu_item (item, menu->items) {
266 int_lower_bound(&mx, get_menuitem_width(term, item, width));
269 set_box(&menu->box,
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);
278 static void
279 scroll_menu(struct menu *menu, int steps, int wrap)
281 int pos, start;
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) {
290 no_item:
291 /* Menu is empty. */
292 menu->selected = -1;
293 menu->first = 0;
294 return;
297 start = pos = menu->selected;
299 if (!steps) {
300 /* The caller wants us to check that menu->selected is
301 * actually selectable, and correct it if not. */
302 steps = 1;
303 if (pos >= 0)
304 --pos;
307 while (steps) {
308 pos += s, steps -= s;
310 while (1) {
311 if (start == pos) {
312 goto select_item;
313 } else if (pos >= menu->size && s == 1) {
314 if (wrap) {
315 pos = 0;
316 } else {
317 pos = menu->size - 1;
318 goto select_item;
320 } else if (pos < 0 && s == -1) {
321 if (wrap) {
322 pos = menu->size - 1;
323 } else {
324 pos = 0;
325 goto select_item;
327 } else if (!mi_is_selectable(&menu->items[pos])) {
328 pos += s;
329 } else {
330 break;
333 if (start == -1) start = 0;
337 select_item:
338 if (!mi_is_selectable(&menu->items[pos]))
339 goto no_item;
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. */
346 static void
347 set_menu_selection(struct menu *menu, int pos)
349 int height, scr_i;
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 */
373 static inline void
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);
378 int max_len;
380 if (w <= 0) return;
382 if (len < 0) len = strlen(text);
383 if (!len) return;
385 #ifdef CONFIG_UTF_8
386 if (term->utf8) {
387 max_len = utf8_cells2bytes(text, w, NULL);
388 if (max_len <= 0)
389 return;
390 } else
391 #endif /* CONFIG_UTF_8 */
392 max_len = w;
394 if (len > max_len) len = max_len;
396 draw_text(term, x + L_TEXT_SPACE, y, text, len, 0, color);
400 static inline void
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;
409 unsigned char c;
410 int xbase = x + L_TEXT_SPACE;
411 int w = width - (L_TEXT_SPACE + R_TEXT_SPACE);
412 int hk_state = 0;
413 #ifdef CONFIG_UTF_8
414 unsigned char *text2, *end;
415 #endif
417 #ifdef CONFIG_DEBUG
418 /* For redundant hotkeys highlighting. */
419 int double_hk = 0;
421 if (hotkey_pos < 0) hotkey_pos = -hotkey_pos, double_hk = 1;
422 #endif
424 if (!hotkey_pos || w <= 0) return;
426 if (selected) {
427 struct color_pair *tmp = hk_color;
429 hk_color = hk_color_sel;
430 hk_color_sel = tmp;
433 #ifdef CONFIG_UTF_8
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) {
439 hk_state = 1;
440 continue;
443 if (hk_state == 1) {
444 #ifdef CONFIG_DEBUG
445 draw_char(term, xbase + x - 1, y, c, hk_attr,
446 (double_hk ? hk_color_sel : hk_color));
447 #else
448 draw_char(term, xbase + x - 1, y, c, hk_attr, hk_color);
449 #endif /* CONFIG_DEBUG */
450 hk_state = 2;
451 } else {
452 draw_char(term, xbase + x - !!hk_state, y, c, 0, color);
455 return;
457 #ifdef CONFIG_UTF_8
458 utf8:
459 end = strchr(text, '\0');
460 text2 = text;
461 for (x = 0; x - !!hk_state < w && *text2; x++) {
462 unicode_val_T data;
464 data = utf8_to_unicode(&text2, end);
465 if (!hk_state && (int)(text2 - text) == hotkey_pos) {
466 hk_state = 1;
467 continue;
469 if (hk_state == 1) {
470 if (unicode_to_cell(data) == 2) {
471 if (x < w && xbase + x < term->width) {
472 #ifdef CONFIG_DEBUG
473 draw_char(term, xbase + x - 1, y,
474 data, hk_attr,
475 (double_hk ? hk_color_sel
476 : hk_color));
477 #else
478 draw_char(term, xbase + x - 1, y,
479 data, hk_attr, hk_color);
480 #endif /* CONFIG_DEBUG */
481 x++;
482 draw_char(term, xbase + x - 1, y,
483 UCS_NO_CHAR, 0, hk_color);
484 } else {
485 draw_char(term, xbase + x - 1, y,
486 ' ', 0, hk_color);
488 } else {
489 #ifdef CONFIG_DEBUG
490 draw_char(term, xbase + x - 1, y,
491 data, hk_attr,
492 (double_hk ? hk_color_sel
493 : hk_color));
494 #else
495 draw_char(term, xbase + x - 1, y,
496 data, hk_attr, hk_color);
497 #endif /* CONFIG_DEBUG */
499 hk_state = 2;
500 } else {
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,
505 y, data, 0, color);
506 x++;
507 draw_char(term, xbase + x - !!hk_state,
508 y, UCS_NO_CHAR, 0, color);
509 } else {
510 draw_char(term, xbase + x - !!hk_state,
511 y, ' ', 0, color);
513 } else {
514 draw_char(term, xbase + x - !!hk_state,
515 y, data, 0, color);
520 #endif /* CONFIG_UTF_8 */
523 static inline void
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);
529 if (w <= 0) return;
531 if (len < 0) len = strlen(text);
532 if (!len) return;
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);
540 static void
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");
546 struct box box;
547 int p;
548 int menu_height;
550 set_box(&box,
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")) {
560 /* Draw shadow */
561 draw_shadow(term, &menu->box,
562 get_bfu_color(term, "dialog.shadow"), 2, 1);
563 #ifdef CONFIG_UTF_8
564 if (term->utf8)
565 fix_dwchar_around_box(term, &box, 1, 2, 1);
566 #endif /* CONFIG_UTF_8 */
568 #ifdef CONFIG_UTF_8
569 else if(term->utf8)
570 fix_dwchar_around_box(term, &box, 1, 0, 0);
571 #endif /* CONFIG_UTF_8 */
573 menu_height = box.height;
574 box.height = 1;
576 for (p = menu->first;
577 p < menu->size && p < menu->first + menu_height;
578 p++, box.y++) {
579 struct color_pair *color = normal_color;
580 struct menu_item *mi = &menu->items[p];
581 int selected = (p == menu->selected);
583 #ifdef CONFIG_DEBUG
584 /* Sanity check. */
585 if (mi_is_end_of_menu(mi))
586 INTERNAL("Unexpected end of menu [%p:%d]", mi, p);
587 #endif
589 if (selected) {
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);
609 continue;
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))
620 l = 0;
622 if (l) {
623 draw_menu_left_text_hk(term, text, l,
624 box.x, box.y, box.width, color,
625 selected);
627 } else {
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;
639 #ifdef CONFIG_DEBUG
640 /* Help to detect action + right text. --Zas */
641 if (mi_has_right_text(mi)) {
642 if (color == selected_color)
643 color = normal_color;
644 else
645 color = selected_color;
647 #endif /* CONFIG_DEBUG */
649 if (init_string(&keystroke)) {
650 add_keystroke_action_to_string(&keystroke,
651 mi->action_id,
652 KEYMAP_MAIN);
653 draw_menu_right_text(term, keystroke.source,
654 keystroke.length,
655 menu->box.x, box.y,
656 box.width, color);
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);
666 if (*rtext) {
667 /* There's a right text, so print it */
668 draw_menu_right_text(term, rtext, -1,
669 menu->box.x,
670 box.y, box.width, color);
675 redraw_from_window(menu->win);
679 #ifdef CONFIG_MOUSE
680 static void
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)) {
687 case B_WHEEL_UP:
688 scroll_direction = -1;
689 /* Fall thru */
690 case B_WHEEL_DOWN:
691 if (check_mouse_action(ev, B_DOWN)) {
692 scroll_menu(menu, scroll_direction, 1);
693 display_menu(win->term, menu);
695 return;
698 if (!check_mouse_position(ev, &menu->box)) {
699 if (check_mouse_action(ev, B_DOWN)) {
700 delete_window_ev(win, ev);
702 } else {
703 struct window *w1;
704 struct window *end = (struct window *) &win->term->windows;
706 for (w1 = win; w1 != end; w1 = w1->next) {
707 struct menu *m1;
709 if (w1->handler == mainmenu_handler) {
710 if (!ev->info.mouse.y)
711 delete_window_ev(win, ev);
712 break;
715 if (w1->handler != menu_handler) break;
717 m1 = w1->data;
719 if (check_mouse_position(ev, &m1->box)) {
720 delete_window_ev(win, ev);
721 break;
726 } else {
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);
737 #endif
739 #define DIST 5
741 static void
742 menu_page_up(struct menu *menu)
744 int current = int_max(0, int_min(menu->selected, menu->size - 1));
745 int step;
746 int i;
747 int next_sep = 0;
749 for (i = current - 1; i > 0; i--)
750 if (mi_is_horizontal_bar(&menu->items[i])) {
751 next_sep = i;
752 break;
755 step = current - next_sep + 1;
756 int_bounds(&step, 0, int_min(current, DIST));
758 scroll_menu(menu, -step, 0);
761 static void
762 menu_page_down(struct menu *menu)
764 int current = int_max(0, int_min(menu->selected, menu->size - 1));
765 int step;
766 int i;
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])) {
771 next_sep = i;
772 break;
775 step = next_sep - current + 1;
776 int_bounds(&step, 0, int_min(menu->size - 1 - current, DIST));
778 scroll_menu(menu, step, 0);
781 #undef DIST
783 static inline int
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);
796 if (!text) return 0;
798 match = strchr(text, '~');
799 if (match)
800 memmove(match, match + 1, strlen(match));
802 match = strcasestr(text, buffer);
803 mem_free(text);
805 return !!match;
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;
814 struct window *win;
815 int pos = menu->selected;
816 int start;
817 int direction;
819 switch (action_id) {
820 case ACT_EDIT_REDRAW:
821 return INPUT_LINE_PROCEED;
823 case ACT_EDIT_ENTER:
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;
828 del_from_list(win);
829 select_menu(term, menu);
830 add_to_list(term->windows, win);
831 return INPUT_LINE_CANCEL;
833 case ACT_EDIT_PREVIOUS_ITEM:
834 pos--;
835 direction = -1;
836 break;
838 case ACT_EDIT_NEXT_ITEM:
839 pos++;
840 default:
841 direction = 1;
844 /* If there is nothing to match with don't start searching */
845 if (!*buffer) return INPUT_LINE_PROCEED;
847 pos %= menu->size;
849 start = pos;
850 do {
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;
859 pos += direction;
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;
868 static void
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);
881 static void
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);
886 int s = 0;
888 switch (action_id) {
889 case ACT_MENU_LEFT:
890 case ACT_MENU_RIGHT:
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);
899 return;
902 if (action_id == ACT_MENU_RIGHT)
903 goto enter;
905 delete_window(win);
906 return;
908 case ACT_MENU_UP:
909 scroll_menu(menu, -1, 1);
910 break;
912 case ACT_MENU_DOWN:
913 scroll_menu(menu, 1, 1);
914 break;
916 case ACT_MENU_HOME:
917 scroll_menu(menu, -menu->selected, 0);
918 break;
920 case ACT_MENU_END:
921 scroll_menu(menu, menu->size - menu->selected - 1, 0);
922 break;
924 case ACT_MENU_PAGE_UP:
925 menu_page_up(menu);
926 break;
928 case ACT_MENU_PAGE_DOWN:
929 menu_page_down(menu);
930 break;
932 case ACT_MENU_ENTER:
933 case ACT_MENU_SELECT:
934 goto enter;
936 case ACT_MENU_SEARCH:
937 search_menu(menu);
938 break;
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);
944 else
945 delete_window_ev(win, NULL);
947 return;
949 default:
951 term_event_key_T key = get_kbd_key(ev);
953 if (is_kbd_fkey(key)
954 || check_kbd_modifier(ev, KBD_MOD_ALT)) {
955 delete_window_ev(win, ev);
956 return;
959 if (!check_kbd_label_key(ev))
960 break;
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);
970 if (s) {
971 enter:
972 select_menu(win->term, menu);
976 static void
977 menu_handler(struct window *win, struct term_event *ev)
979 struct menu *menu = win->data;
981 menu->win = win;
983 switch (ev->ev) {
984 case EVENT_INIT:
985 case EVENT_RESIZE:
986 case EVENT_REDRAW:
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);
994 break;
996 case EVENT_MOUSE:
997 #ifdef CONFIG_MOUSE
998 menu_mouse_handler(menu, ev);
999 #endif /* CONFIG_MOUSE */
1000 break;
1002 case EVENT_KBD:
1003 menu_kbd_handler(menu, ev);
1004 break;
1006 case EVENT_ABORT:
1007 free_menu_items(menu->items);
1008 break;
1013 void
1014 do_mainmenu(struct terminal *term, struct menu_item *items,
1015 void *data, int sel)
1017 int init = 0;
1018 struct menu *menu;
1019 struct window *win;
1021 if (!term->main_menu) {
1022 term->main_menu = mem_calloc(1, sizeof(*menu));
1023 if (!term->main_menu) return;
1024 init = 1;
1027 menu = term->main_menu;
1028 menu->selected = (sel == -1 ? 0 : sel);
1029 menu->items = items;
1030 menu->data = data;
1031 menu->size = count_items(items);
1032 menu->hotkeys = 1;
1034 #ifdef CONFIG_NLS
1035 clear_hotkeys_cache(menu);
1036 #endif
1037 init_hotkeys(term, menu);
1038 if (init) {
1039 add_window(term, mainmenu_handler, menu);
1040 win = menu->win;
1041 /* This should be fine because add_window will call
1042 * mainmenu_handler which will assign the window to menu->win.
1044 assert(win);
1045 deselect_mainmenu(term, menu);
1046 } else {
1047 foreach (win, term->windows) {
1048 if (win->data == menu) {
1049 del_from_list(win);
1050 add_to_list(term->windows, win);
1051 display_mainmenu(term, menu);
1052 break;
1057 if (sel != -1) {
1058 select_menu(term, menu);
1062 static void
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");
1067 int p = 0;
1068 int i;
1069 struct box box;
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;
1109 int textlen;
1110 int selected = (i == menu->selected);
1111 int screencnt;
1113 if (mi_text_translate(mi))
1114 text = _(text, term);
1116 textlen = strlen(text) - !!l;
1117 #ifdef CONFIG_UTF_8
1118 if (term->utf8)
1119 screencnt = utf8_ptr2cells(text, NULL) - !!l;
1120 else
1121 #endif /* CONFIG_UTF_8 */
1122 screencnt = textlen;
1124 if (selected) {
1125 color = selected_color;
1126 box.x = p;
1127 #ifdef CONFIG_UTF_8
1128 if (term->utf8)
1129 box.width = L_MAINTEXT_SPACE + L_TEXT_SPACE
1130 + screencnt
1131 + R_TEXT_SPACE + R_MAINTEXT_SPACE;
1132 else
1133 #endif /* CONFIG_UTF_8 */
1134 box.width = L_MAINTEXT_SPACE + L_TEXT_SPACE
1135 + textlen
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;
1145 if (l) {
1146 draw_menu_left_text_hk(term, text, l,
1147 p, 0, textlen + R_TEXT_SPACE + L_TEXT_SPACE,
1148 color, selected);
1149 } else {
1150 draw_menu_left_text(term, text, textlen,
1151 p, 0, textlen + R_TEXT_SPACE + L_TEXT_SPACE,
1152 color);
1155 p += screencnt;
1157 if (p >= term->width - R_MAINMENU_SPACE)
1158 break;
1160 p += R_MAINTEXT_SPACE + R_TEXT_SPACE + L_TEXT_SPACE;
1163 menu->last = i - 1;
1164 int_lower_bound(&menu->last, menu->first);
1165 if (menu->last < menu->size - 1) {
1166 #ifdef CONFIG_UTF_8
1167 if (term->utf8) {
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 ' '. */
1175 schar++;
1176 draw_char_data(term, term->width - R_MAINMENU_SPACE - 1,
1177 0, ' ');
1180 #endif
1182 set_box(&box,
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);
1192 #ifdef CONFIG_MOUSE
1193 static void
1194 mainmenu_mouse_handler(struct menu *menu, struct term_event *ev)
1196 struct window *win = menu->win;
1197 struct menu_item *item;
1198 int scroll = 0;
1200 if (check_mouse_wheel(ev))
1201 return;
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);
1210 return;
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) {
1217 scroll = -1;
1219 } else if (ev->info.mouse.x >= win->term->width - R_MAINMENU_SPACE) {
1220 scroll = 1;
1222 } else {
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;
1244 break;
1249 if (scroll) {
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);
1259 #endif
1261 static void
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:
1269 case ACT_MENU_DOWN:
1270 case ACT_MENU_UP:
1271 case ACT_MENU_PAGE_UP:
1272 case ACT_MENU_PAGE_DOWN:
1273 case ACT_MENU_SELECT:
1274 select_menu(win->term, menu);
1275 return;
1277 case ACT_MENU_HOME:
1278 scroll_menu(menu, -menu->selected, 0);
1279 break;
1281 case ACT_MENU_END:
1282 scroll_menu(menu, menu->size - menu->selected - 1, 0);
1283 break;
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. */
1293 case ACT_MENU_LEFT:
1294 case ACT_MENU_RIGHT:
1295 scroll_menu(menu, action_id == ACT_MENU_LEFT ? -1 : 1, 1);
1296 break;
1298 case ACT_MENU_REDRAW:
1299 /* Just call display_mainmenu() */
1300 break;
1302 default:
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);
1309 return;
1312 case ACT_MENU_CANCEL:
1313 deselect_mainmenu(win->term, menu);
1314 break;
1317 /* Redraw the menu */
1318 display_mainmenu(win->term, menu);
1321 static void
1322 mainmenu_handler(struct window *win, struct term_event *ev)
1324 struct menu *menu = win->data;
1326 menu->win = win;
1328 switch (ev->ev) {
1329 case EVENT_INIT:
1330 case EVENT_RESIZE:
1331 case EVENT_REDRAW:
1332 display_mainmenu(win->term, menu);
1333 break;
1335 case EVENT_MOUSE:
1336 #ifdef CONFIG_MOUSE
1337 mainmenu_mouse_handler(menu, ev);
1338 #endif /* CONFIG_MOUSE */
1339 break;
1341 case EVENT_KBD:
1342 mainmenu_kbd_handler(menu, ev);
1343 break;
1345 case EVENT_ABORT:
1346 break;
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)
1354 struct menu_item *
1355 new_menu(enum menu_item_flags flags)
1357 struct menu_item *mi = NULL;
1359 if (realloc_menu_items(&mi, 0)) mi->flags = flags;
1361 return mi;
1364 void
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);
1373 if (!item) return;
1375 item += n;
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
1391 #undef L_TEXT_SPACE
1392 #undef R_TEXT_SPACE
1393 #undef MENU_BORDER_SIZE