grafthistory: comment about downloading the pack index
[elinks/elinks-j605.git] / src / bfu / menu.c
blob792db073bb23be2fa336bc0854f527aa42520b7c
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 set_menu_selection(struct menu *menu, int pos);
66 static inline int
67 count_items(struct menu_item *items)
69 int i = 0;
71 if (items) {
72 struct menu_item *item;
74 foreach_menu_item (item, items) {
75 i++;
79 return i;
82 static void
83 free_menu_items(struct menu_item *items)
85 struct menu_item *item;
87 if (!items || !(items->flags & FREE_ANY)) return;
89 /* Note that flags & FREE_DATA applies only when menu is aborted;
90 * it is zeroed when some menu field is selected. */
92 foreach_menu_item (item, items) {
93 if (item->flags & FREE_TEXT) mem_free_if(item->text);
94 if (item->flags & FREE_RTEXT) mem_free_if(item->rtext);
95 if (item->flags & FREE_DATA) mem_free_if(item->data);
98 mem_free(items);
101 void
102 do_menu_selected(struct terminal *term, struct menu_item *items,
103 void *data, int selected, int hotkeys)
105 struct menu *menu = mem_calloc(1, sizeof(*menu));
107 if (menu) {
108 menu->selected = selected;
109 menu->items = items;
110 menu->data = data;
111 menu->size = count_items(items);
112 menu->hotkeys = hotkeys;
113 #ifdef CONFIG_NLS
114 menu->lang = -1;
115 #endif
116 refresh_hotkeys(term, menu);
117 add_window(term, menu_handler, menu);
118 } else {
119 free_menu_items(items);
123 void
124 do_menu(struct terminal *term, struct menu_item *items, void *data, int hotkeys)
126 do_menu_selected(term, items, data, 0, hotkeys);
129 static void
130 select_menu_item(struct terminal *term, struct menu_item *it, void *data)
132 /* We save these values due to delete_window() call below. */
133 menu_func_T func = it->func;
134 void *it_data = it->data;
135 enum main_action action_id = it->action_id;
137 if (!mi_is_selectable(it)) return;
139 if (!mi_is_submenu(it)) {
140 /* Don't free data! */
141 it->flags &= ~FREE_DATA;
143 while (!list_empty(term->windows)) {
144 struct window *win = term->windows.next;
146 if (win->handler != menu_handler
147 && win->handler != mainmenu_handler)
148 break;
150 delete_window(win);
154 if (action_id != ACT_MAIN_NONE && !func) {
155 struct session *ses = data;
157 do_action(ses, action_id, 1);
158 return;
161 assertm(func, "No menu function");
162 if_assert_failed return;
164 func(term, it_data, data);
167 static inline void
168 select_menu(struct terminal *term, struct menu *menu)
170 if (menu->selected < 0 || menu->selected >= menu->size)
171 return;
173 select_menu_item(term, &menu->items[menu->selected], menu->data);
176 /* Get desired width for left text in menu item, accounting spacing. */
177 static int
178 get_menuitem_text_width(struct terminal *term, struct menu_item *mi)
180 unsigned char *text;
182 if (!mi_has_left_text(mi)) return 0;
184 text = mi->text;
185 if (mi_text_translate(mi))
186 text = _(text, term);
188 if (!text[0]) return 0;
190 return L_TEXT_SPACE + strlen(text) - !!mi->hotkey_pos + R_TEXT_SPACE;
193 /* Get desired width for right text in menu item, accounting spacing. */
194 static int
195 get_menuitem_rtext_width(struct terminal *term, struct menu_item *mi)
197 int rtext_width = 0;
199 if (mi_is_submenu(mi)) {
200 rtext_width = L_RTEXT_SPACE + m_submenu_len + R_RTEXT_SPACE;
202 } else if (mi->action_id != ACT_MAIN_NONE) {
203 struct string keystroke;
205 if (init_string(&keystroke)) {
206 add_keystroke_action_to_string(&keystroke, mi->action_id, KEYMAP_MAIN);
207 rtext_width = L_RTEXT_SPACE + keystroke.length + R_RTEXT_SPACE;
208 done_string(&keystroke);
211 } else if (mi_has_right_text(mi)) {
212 unsigned char *rtext = mi->rtext;
214 if (mi_rtext_translate(mi))
215 rtext = _(rtext, term);
217 if (rtext[0])
218 rtext_width = L_RTEXT_SPACE + strlen(rtext) + R_RTEXT_SPACE;
221 return rtext_width;
224 static int
225 get_menuitem_width(struct terminal *term, struct menu_item *mi, int max_width)
227 int text_width = get_menuitem_text_width(term, mi);
228 int rtext_width = get_menuitem_rtext_width(term, mi);
230 int_upper_bound(&text_width, max_width);
231 int_upper_bound(&rtext_width, max_width - text_width);
233 return text_width + rtext_width;
236 static void
237 count_menu_size(struct terminal *term, struct menu *menu)
239 struct menu_item *item;
240 int width = term->width - MENU_BORDER_SIZE * 2;
241 int height = term->height - MENU_BORDER_SIZE * 2;
242 int my = int_min(menu->size, height);
243 int mx = 0;
245 foreach_menu_item (item, menu->items) {
246 int_lower_bound(&mx, get_menuitem_width(term, item, width));
249 set_box(&menu->box,
250 menu->parent_x, menu->parent_y,
251 mx + MENU_BORDER_SIZE * 2,
252 my + MENU_BORDER_SIZE * 2);
254 int_bounds(&menu->box.x, 0, width - mx);
255 int_bounds(&menu->box.y, 0, height - my);
258 static void
259 scroll_menu(struct menu *menu, int steps, int wrap)
261 int pos, start;
262 int s = steps ? steps/abs(steps) : 1; /* Selectable item search direction. */
264 /* menu->selected sometimes became -2 and caused infinite loops.
265 * That should no longer be possible. */
266 assert(menu->selected >= -1);
267 if_assert_failed return;
269 if (menu->size <= 0) {
270 no_item:
271 /* Menu is empty. */
272 menu->selected = -1;
273 menu->first = 0;
274 return;
277 start = pos = menu->selected;
279 if (!steps) {
280 /* The caller wants us to check that menu->selected is
281 * actually selectable, and correct it if not. */
282 steps = 1;
283 if (pos >= 0)
284 --pos;
287 while (steps) {
288 pos += s, steps -= s;
290 while (1) {
291 if (start == pos) {
292 goto select_item;
293 } else if (pos >= menu->size && s == 1) {
294 if (wrap) {
295 pos = 0;
296 } else {
297 pos = menu->size - 1;
298 goto select_item;
300 } else if (pos < 0 && s == -1) {
301 if (wrap) {
302 pos = menu->size - 1;
303 } else {
304 pos = 0;
305 goto select_item;
307 } else if (!mi_is_selectable(&menu->items[pos])) {
308 pos += s;
309 } else {
310 break;
313 if (start == -1) start = 0;
317 select_item:
318 if (!mi_is_selectable(&menu->items[pos]))
319 goto no_item;
320 set_menu_selection(menu, pos);
323 /* Set menu->selected = pos, and adjust menu->first if needed.
324 * This neither redraws the menu nor runs the item's action.
325 * The caller must ensure that menu->items[pos] is selectable. */
326 static void
327 set_menu_selection(struct menu *menu, int pos)
329 int height, scr_i;
331 assert(pos >= 0 && pos < menu->size);
332 assert(mi_is_selectable(&menu->items[pos]));
333 if_assert_failed return;
335 menu->selected = pos;
337 height = int_max(1, menu->box.height - MENU_BORDER_SIZE * 2);
339 /* The rest is not needed for horizontal menus like the mainmenu.
340 * FIXME: We need a better way to figure out which menus are horizontal and
341 * which are vertical (normal) --jonas */
342 if (height == 1) return;
344 scr_i = int_min((height - 1) / 2, SCROLL_ITEMS);
346 int_bounds(&menu->first, menu->selected - height + scr_i + 1, menu->selected - scr_i);
347 int_bounds(&menu->first, 0, menu->size - height);
350 static inline void
351 draw_menu_left_text(struct terminal *term, unsigned char *text, int len,
352 int x, int y, int width, struct color_pair *color)
354 int w = width - (L_TEXT_SPACE + R_TEXT_SPACE);
356 if (w <= 0) return;
358 if (len < 0) len = strlen(text);
359 if (!len) return;
360 if (len > w) len = w;
362 draw_text(term, x + L_TEXT_SPACE, y, text, len, 0, color);
366 static inline void
367 draw_menu_left_text_hk(struct terminal *term, unsigned char *text,
368 int hotkey_pos, int x, int y, int width,
369 struct color_pair *color, int selected)
371 struct color_pair *hk_color = get_bfu_color(term, "menu.hotkey.normal");
372 struct color_pair *hk_color_sel = get_bfu_color(term, "menu.hotkey.selected");
373 enum screen_char_attr hk_attr = get_opt_bool("ui.dialogs.underline_hotkeys")
374 ? SCREEN_ATTR_UNDERLINE : 0;
375 unsigned char c;
376 int xbase = x + L_TEXT_SPACE;
377 int w = width - (L_TEXT_SPACE + R_TEXT_SPACE);
378 int hk_state = 0;
379 #ifdef CONFIG_DEBUG
380 /* For redundant hotkeys highlighting. */
381 int double_hk = 0;
383 if (hotkey_pos < 0) hotkey_pos = -hotkey_pos, double_hk = 1;
384 #endif
386 if (!hotkey_pos || w <= 0) return;
388 if (selected) {
389 struct color_pair *tmp = hk_color;
391 hk_color = hk_color_sel;
392 hk_color_sel = tmp;
395 for (x = 0; x - !!hk_state < w && (c = text[x]); x++) {
396 if (!hk_state && x == hotkey_pos - 1) {
397 hk_state = 1;
398 continue;
401 if (hk_state == 1) {
402 #ifdef CONFIG_DEBUG
403 draw_char(term, xbase + x - 1, y, c, hk_attr,
404 (double_hk ? hk_color_sel : hk_color));
405 #else
406 draw_char(term, xbase + x - 1, y, c, hk_attr, hk_color);
407 #endif
408 hk_state = 2;
409 } else {
410 draw_char(term, xbase + x - !!hk_state, y, c, 0, color);
415 static inline void
416 draw_menu_right_text(struct terminal *term, unsigned char *text, int len,
417 int x, int y, int width, struct color_pair *color)
419 int w = width - (L_RTEXT_SPACE + R_RTEXT_SPACE);
421 if (w <= 0) return;
423 if (len < 0) len = strlen(text);
424 if (!len) return;
425 if (len > w) len = w;
427 x += w - len + L_RTEXT_SPACE + L_TEXT_SPACE;
429 draw_text(term, x, y, text, len, 0, color);
432 static void
433 display_menu(struct terminal *term, struct menu *menu)
435 struct color_pair *normal_color = get_bfu_color(term, "menu.normal");
436 struct color_pair *selected_color = get_bfu_color(term, "menu.selected");
437 struct color_pair *frame_color = get_bfu_color(term, "menu.frame");
438 struct box box;
439 int p;
440 int menu_height;
442 set_box(&box,
443 menu->box.x + MENU_BORDER_SIZE,
444 menu->box.y + MENU_BORDER_SIZE,
445 int_max(0, menu->box.width - MENU_BORDER_SIZE * 2),
446 int_max(0, menu->box.height - MENU_BORDER_SIZE * 2));
448 draw_box(term, &box, ' ', 0, normal_color);
449 draw_border(term, &box, frame_color, 1);
451 if (get_opt_bool("ui.dialogs.shadows")) {
452 /* Draw shadow */
453 draw_shadow(term, &menu->box,
454 get_bfu_color(term, "dialog.shadow"), 2, 1);
457 menu_height = box.height;
458 box.height = 1;
460 for (p = menu->first;
461 p < menu->size && p < menu->first + menu_height;
462 p++, box.y++) {
463 struct color_pair *color = normal_color;
464 struct menu_item *mi = &menu->items[p];
465 int selected = (p == menu->selected);
467 #ifdef CONFIG_DEBUG
468 /* Sanity check. */
469 if (mi_is_end_of_menu(mi))
470 INTERNAL("Unexpected end of menu [%p:%d]", mi, p);
471 #endif
473 if (selected) {
474 /* This entry is selected. */
475 color = selected_color;
477 set_cursor(term, box.x, box.y, 1);
478 set_window_ptr(menu->win, menu->box.x + menu->box.width, box.y);
479 draw_box(term, &box, ' ', 0, color);
482 if (mi_is_horizontal_bar(mi)) {
483 /* Horizontal separator */
484 draw_border_char(term, menu->box.x, box.y,
485 BORDER_SRTEE, frame_color);
487 draw_box(term, &box, BORDER_SHLINE,
488 SCREEN_ATTR_FRAME, frame_color);
490 draw_border_char(term, box.x + box.width, box.y,
491 BORDER_SLTEE, frame_color);
493 continue;
496 if (mi_has_left_text(mi)) {
497 int l = mi->hotkey_pos;
498 unsigned char *text = mi->text;
500 if (mi_text_translate(mi))
501 text = _(text, term);
503 if (!mi_is_selectable(mi))
504 l = 0;
506 if (l) {
507 draw_menu_left_text_hk(term, text, l,
508 box.x, box.y, box.width, color,
509 selected);
511 } else {
512 draw_menu_left_text(term, text, -1,
513 box.x, box.y, box.width, color);
517 if (mi_is_submenu(mi)) {
518 draw_menu_right_text(term, m_submenu, m_submenu_len,
519 menu->box.x, box.y, box.width, color);
520 } else if (mi->action_id != ACT_MAIN_NONE) {
521 struct string keystroke;
523 #ifdef CONFIG_DEBUG
524 /* Help to detect action + right text. --Zas */
525 if (mi_has_right_text(mi)) {
526 if (color == selected_color)
527 color = normal_color;
528 else
529 color = selected_color;
531 #endif /* CONFIG_DEBUG */
533 if (init_string(&keystroke)) {
534 add_keystroke_action_to_string(&keystroke,
535 mi->action_id,
536 KEYMAP_MAIN);
537 draw_menu_right_text(term, keystroke.source,
538 keystroke.length,
539 menu->box.x, box.y,
540 box.width, color);
541 done_string(&keystroke);
544 } else if (mi_has_right_text(mi)) {
545 unsigned char *rtext = mi->rtext;
547 if (mi_rtext_translate(mi))
548 rtext = _(rtext, term);
550 if (*rtext) {
551 /* There's a right text, so print it */
552 draw_menu_right_text(term, rtext, -1,
553 menu->box.x,
554 box.y, box.width, color);
559 redraw_from_window(menu->win);
563 #ifdef CONFIG_MOUSE
564 static void
565 menu_mouse_handler(struct menu *menu, struct term_event *ev)
567 struct window *win = menu->win;
568 int scroll_direction = 1;
570 switch (get_mouse_button(ev)) {
571 case B_WHEEL_UP:
572 scroll_direction = -1;
573 /* Fall thru */
574 case B_WHEEL_DOWN:
575 if (check_mouse_action(ev, B_DOWN)) {
576 scroll_menu(menu, scroll_direction, 1);
577 display_menu(win->term, menu);
579 return;
582 if (!check_mouse_position(ev, &menu->box)) {
583 if (check_mouse_action(ev, B_DOWN)) {
584 delete_window_ev(win, ev);
586 } else {
587 struct window *w1;
588 struct window *end = (struct window *) &win->term->windows;
590 for (w1 = win; w1 != end; w1 = w1->next) {
591 struct menu *m1;
593 if (w1->handler == mainmenu_handler) {
594 if (!ev->info.mouse.y)
595 delete_window_ev(win, ev);
596 break;
599 if (w1->handler != menu_handler) break;
601 m1 = w1->data;
603 if (check_mouse_position(ev, &m1->box)) {
604 delete_window_ev(win, ev);
605 break;
610 } else {
611 int sel = ev->info.mouse.y - menu->box.y - 1 + menu->first;
613 if (sel >= 0 && sel < menu->size
614 && mi_is_selectable(&menu->items[sel])) {
615 set_menu_selection(menu, sel);
616 display_menu(win->term, menu);
617 select_menu(win->term, menu);
621 #endif
623 #define DIST 5
625 static void
626 menu_page_up(struct menu *menu)
628 int current = int_max(0, int_min(menu->selected, menu->size - 1));
629 int step;
630 int i;
631 int next_sep = 0;
633 for (i = current - 1; i > 0; i--)
634 if (mi_is_horizontal_bar(&menu->items[i])) {
635 next_sep = i;
636 break;
639 step = current - next_sep + 1;
640 int_bounds(&step, 0, int_min(current, DIST));
642 scroll_menu(menu, -step, 0);
645 static void
646 menu_page_down(struct menu *menu)
648 int current = int_max(0, int_min(menu->selected, menu->size - 1));
649 int step;
650 int i;
651 int next_sep = menu->size - 1;
653 for (i = current + 1; i < menu->size; i++)
654 if (mi_is_horizontal_bar(&menu->items[i])) {
655 next_sep = i;
656 break;
659 step = next_sep - current + 1;
660 int_bounds(&step, 0, int_min(menu->size - 1 - current, DIST));
662 scroll_menu(menu, step, 0);
665 #undef DIST
667 static inline int
668 search_menu_item(struct menu_item *item, unsigned char *buffer,
669 struct terminal *term)
671 unsigned char *text, *match;
673 /* set_menu_selection asserts selectability. */
674 if (!mi_has_left_text(item) || !mi_is_selectable(item)) return 0;
676 text = mi_text_translate(item) ? _(item->text, term) : item->text;
678 /* Crap. We have to remove the hotkey markers '~' */
679 text = stracpy(text);
680 if (!text) return 0;
682 match = strchr(text, '~');
683 if (match)
684 memmove(match, match + 1, strlen(match));
686 match = strcasestr(text, buffer);
687 mem_free(text);
689 return !!match;
692 static enum input_line_code
693 menu_search_handler(struct input_line *line, int action_id)
695 struct menu *menu = line->data;
696 struct terminal *term = menu->win->term;
697 unsigned char *buffer = line->buffer;
698 struct window *win;
699 int pos = menu->selected;
700 int start;
701 int direction;
703 switch (action_id) {
704 case ACT_EDIT_REDRAW:
705 return INPUT_LINE_PROCEED;
707 case ACT_EDIT_ENTER:
708 /* XXX: The input line dialog window is above the menu window.
709 * Remove it from the top, so that select_menu() will correctly
710 * remove all the windows it has to and then readd it. */
711 win = term->windows.next;
712 del_from_list(win);
713 select_menu(term, menu);
714 add_to_list(term->windows, win);
715 return INPUT_LINE_CANCEL;
717 case ACT_EDIT_PREVIOUS_ITEM:
718 pos--;
719 direction = -1;
720 break;
722 case ACT_EDIT_NEXT_ITEM:
723 pos++;
724 default:
725 direction = 1;
728 /* If there is nothing to match with don't start searching */
729 if (!*buffer) return INPUT_LINE_PROCEED;
731 pos %= menu->size;
733 start = pos;
734 do {
735 struct menu_item *item = &menu->items[pos];
737 if (search_menu_item(item, buffer, term)) {
738 set_menu_selection(menu, pos);
739 display_menu(term, menu);
740 return INPUT_LINE_PROCEED;
743 pos += direction;
745 if (pos == menu->size) pos = 0;
746 else if (pos < 0) pos = menu->size - 1;
747 } while (pos != start);
749 return INPUT_LINE_CANCEL;
752 static void
753 search_menu(struct menu *menu)
755 struct terminal *term = menu->win->term;
756 struct window *tab = get_current_tab(term);
757 struct session *ses = tab ? tab->data : NULL;
758 unsigned char *prompt = _("Search menu/", term);
760 if (menu->size < 1 || !ses) return;
762 input_field_line(ses, prompt, menu, NULL, menu_search_handler);
765 static void
766 menu_kbd_handler(struct menu *menu, struct term_event *ev)
768 struct window *win = menu->win;
769 enum menu_action action_id = kbd_action(KEYMAP_MENU, ev, NULL);
770 int s = 0;
772 switch (action_id) {
773 case ACT_MENU_LEFT:
774 case ACT_MENU_RIGHT:
775 if (list_has_next(win->term->windows, win)
776 && win->next->handler == mainmenu_handler) {
777 struct window *next_win = win->next;
779 delete_window_ev(win, ev);
781 select_menu(next_win->term, next_win->data);
783 return;
786 if (action_id == ACT_MENU_RIGHT)
787 goto enter;
789 delete_window(win);
790 return;
792 case ACT_MENU_UP:
793 scroll_menu(menu, -1, 1);
794 break;
796 case ACT_MENU_DOWN:
797 scroll_menu(menu, 1, 1);
798 break;
800 case ACT_MENU_HOME:
801 scroll_menu(menu, -menu->selected, 0);
802 break;
804 case ACT_MENU_END:
805 scroll_menu(menu, menu->size - menu->selected - 1, 0);
806 break;
808 case ACT_MENU_PAGE_UP:
809 menu_page_up(menu);
810 break;
812 case ACT_MENU_PAGE_DOWN:
813 menu_page_down(menu);
814 break;
816 case ACT_MENU_ENTER:
817 case ACT_MENU_SELECT:
818 goto enter;
820 case ACT_MENU_SEARCH:
821 search_menu(menu);
822 break;
824 case ACT_MENU_CANCEL:
825 if (list_has_next(win->term->windows, win)
826 && win->next->handler == mainmenu_handler)
827 delete_window_ev(win, ev);
828 else
829 delete_window_ev(win, NULL);
831 return;
833 default:
835 int key = get_kbd_key(ev);
837 if ((key >= KBD_F1 && key <= KBD_F12)
838 || check_kbd_modifier(ev, KBD_MOD_ALT)) {
839 delete_window_ev(win, ev);
840 return;
843 if (!check_kbd_label_key(ev))
844 break;
846 s = check_hotkeys(menu, key, win->term);
848 if (s || check_not_so_hot_keys(menu, key, win->term))
849 scroll_menu(menu, 0, 1);
853 display_menu(win->term, menu);
854 if (s) {
855 enter:
856 select_menu(win->term, menu);
860 static void
861 menu_handler(struct window *win, struct term_event *ev)
863 struct menu *menu = win->data;
865 menu->win = win;
867 switch (ev->ev) {
868 case EVENT_INIT:
869 case EVENT_RESIZE:
870 get_parent_ptr(win, &menu->parent_x, &menu->parent_y);
871 case EVENT_REDRAW:
872 count_menu_size(win->term, menu);
873 /* do_menu sets menu->selected = 0. If that
874 * item isn't actually selectable, correct
875 * menu->selected here. */
876 scroll_menu(menu, 0, 1);
877 display_menu(win->term, menu);
878 break;
880 case EVENT_MOUSE:
881 #ifdef CONFIG_MOUSE
882 menu_mouse_handler(menu, ev);
883 #endif /* CONFIG_MOUSE */
884 break;
886 case EVENT_KBD:
887 menu_kbd_handler(menu, ev);
888 break;
890 case EVENT_ABORT:
891 free_menu_items(menu->items);
892 break;
897 void
898 do_mainmenu(struct terminal *term, struct menu_item *items,
899 void *data, int sel)
901 struct menu *menu = mem_calloc(1, sizeof(*menu));
903 if (!menu) return;
905 menu->selected = (sel == -1 ? 0 : sel);
906 menu->items = items;
907 menu->data = data;
908 menu->size = count_items(items);
909 menu->hotkeys = 1;
911 #ifdef CONFIG_NLS
912 clear_hotkeys_cache(menu);
913 #endif
914 init_hotkeys(term, menu);
915 add_window(term, mainmenu_handler, menu);
917 if (sel != -1) {
918 select_menu(term, menu);
922 static void
923 display_mainmenu(struct terminal *term, struct menu *menu)
925 struct color_pair *normal_color = get_bfu_color(term, "menu.normal");
926 struct color_pair *selected_color = get_bfu_color(term, "menu.selected");
927 int p = 0;
928 int i;
929 struct box box;
931 /* FIXME: menu horizontal scrolling do not work well yet, we need to cache
932 * menu items width and recalculate them only when needed (ie. language change)
933 * instead of looping and calculate them each time. --Zas */
935 /* Try to make current selected menu entry visible. */
936 if (menu->selected < menu->first) {
937 int num_items_offscreen = menu->selected - menu->first;
939 menu->first += num_items_offscreen;
940 menu->last += num_items_offscreen;
941 } else if (menu->selected > menu->last) {
942 int num_items_offscreen = menu->last - menu->selected;
944 menu->first -= num_items_offscreen;
945 menu->last -= num_items_offscreen;
948 if (menu->last <= 0)
949 menu->last = menu->size - 1;
951 int_bounds(&menu->last, 0, menu->size - 1);
952 int_bounds(&menu->first, 0, menu->last);
954 set_box(&box, 0, 0, term->width, 1);
955 draw_box(term, &box, ' ', 0, normal_color);
957 if (menu->first != 0) {
958 box.width = L_MAINMENU_SPACE;
959 draw_box(term, &box, '<', 0, normal_color);
962 p += L_MAINMENU_SPACE;
964 for (i = menu->first; i < menu->size; i++) {
965 struct menu_item *mi = &menu->items[i];
966 struct color_pair *color = normal_color;
967 unsigned char *text = mi->text;
968 int l = mi->hotkey_pos;
969 int textlen;
970 int selected = (i == menu->selected);
972 if (mi_text_translate(mi))
973 text = _(text, term);
975 textlen = strlen(text) - !!l;
977 if (selected) {
978 color = selected_color;
979 box.x = p;
980 box.width = L_MAINTEXT_SPACE + L_TEXT_SPACE
981 + textlen
982 + R_TEXT_SPACE + R_MAINTEXT_SPACE;
983 draw_box(term, &box, ' ', 0, color);
984 set_cursor(term, p, 0, 1);
985 set_window_ptr(menu->win, p, 1);
988 p += L_MAINTEXT_SPACE;
990 if (l) {
991 draw_menu_left_text_hk(term, text, l,
992 p, 0, textlen + R_TEXT_SPACE + L_TEXT_SPACE,
993 color, selected);
994 } else {
995 draw_menu_left_text(term, text, textlen,
996 p, 0, textlen + R_TEXT_SPACE + L_TEXT_SPACE,
997 color);
1000 p += textlen;
1002 if (p >= term->width - R_MAINMENU_SPACE)
1003 break;
1005 p += R_MAINTEXT_SPACE + R_TEXT_SPACE + L_TEXT_SPACE;
1008 menu->last = i - 1;
1009 int_lower_bound(&menu->last, menu->first);
1010 if (menu->last < menu->size - 1) {
1011 set_box(&box,
1012 term->width - R_MAINMENU_SPACE, 0,
1013 R_MAINMENU_SPACE, 1);
1014 draw_box(term, &box, '>', 0, normal_color);
1017 redraw_from_window(menu->win);
1021 #ifdef CONFIG_MOUSE
1022 static void
1023 mainmenu_mouse_handler(struct menu *menu, struct term_event *ev)
1025 struct window *win = menu->win;
1026 struct menu_item *item;
1027 int scroll = 0;
1029 if (check_mouse_wheel(ev))
1030 return;
1032 /* Mouse was clicked outside the mainmenu bar */
1033 if (ev->info.mouse.y) {
1034 if (check_mouse_action(ev, B_DOWN))
1035 delete_window_ev(win, NULL);
1037 return;
1040 /* First check if the mouse button was pressed in the side of the
1041 * terminal and simply scroll one step in that direction else iterate
1042 * through the menu items to see if it was pressed on a label. */
1043 if (ev->info.mouse.x < L_MAINMENU_SPACE) {
1044 scroll = -1;
1046 } else if (ev->info.mouse.x >= win->term->width - R_MAINMENU_SPACE) {
1047 scroll = 1;
1049 } else {
1050 int p = L_MAINMENU_SPACE;
1052 /* We don't initialize to menu->first here, since it breaks
1053 * horizontal scrolling using mouse in some cases. --Zas */
1054 foreach_menu_item (item, menu->items) {
1055 unsigned char *text = item->text;
1057 if (!mi_has_left_text(item)) continue;
1059 if (mi_text_translate(item))
1060 text = _(item->text, win->term);
1062 /* The label width is made up of a little padding on
1063 * the sides followed by the text width substracting
1064 * one char if it has hotkeys (the '~' char) */
1065 p += L_MAINTEXT_SPACE + L_TEXT_SPACE
1066 + strlen(text) - !!item->hotkey_pos
1067 + R_TEXT_SPACE + R_MAINTEXT_SPACE;
1069 if (ev->info.mouse.x < p) {
1070 scroll = (item - menu->items) - menu->selected;
1071 break;
1076 if (scroll) {
1077 scroll_menu(menu, scroll, 1);
1078 display_mainmenu(win->term, menu);
1081 /* We need to select the menu item even if we didn't scroll
1082 * apparently because we will delete any drop down menus
1083 * in the clicking process. */
1084 select_menu(win->term, menu);
1086 #endif
1088 static void
1089 mainmenu_kbd_handler(struct menu *menu, struct term_event *ev)
1091 struct window *win = menu->win;
1092 enum menu_action action_id = kbd_action(KEYMAP_MENU, ev, NULL);
1094 switch (action_id) {
1095 case ACT_MENU_ENTER:
1096 case ACT_MENU_DOWN:
1097 case ACT_MENU_UP:
1098 case ACT_MENU_PAGE_UP:
1099 case ACT_MENU_PAGE_DOWN:
1100 case ACT_MENU_SELECT:
1101 select_menu(win->term, menu);
1102 return;
1104 case ACT_MENU_HOME:
1105 scroll_menu(menu, -menu->selected, 0);
1106 break;
1108 case ACT_MENU_END:
1109 scroll_menu(menu, menu->size - menu->selected - 1, 0);
1110 break;
1112 case ACT_MENU_NEXT_ITEM:
1113 case ACT_MENU_PREVIOUS_ITEM:
1114 /* This is pretty western centric since `what is next'?
1115 * Anyway we cycle clockwise by resetting the action ... */
1116 action_id = (action_id == ACT_MENU_NEXT_ITEM)
1117 ? ACT_MENU_RIGHT : ACT_MENU_LEFT;
1118 /* ... and then letting left/right handling take over. */
1120 case ACT_MENU_LEFT:
1121 case ACT_MENU_RIGHT:
1122 scroll_menu(menu, action_id == ACT_MENU_LEFT ? -1 : 1, 1);
1123 break;
1125 case ACT_MENU_REDRAW:
1126 /* Just call display_mainmenu() */
1127 break;
1129 default:
1130 /* Fallback to see if any hotkey matches the pressed key */
1131 if (check_kbd_label_key(ev)
1132 && check_hotkeys(menu, get_kbd_key(ev), win->term)) {
1133 display_mainmenu(win->term, menu);
1134 select_menu(win->term, menu);
1136 return;
1139 case ACT_MENU_CANCEL:
1140 delete_window_ev(win, action_id != ACT_MENU_CANCEL ? ev : NULL);
1141 return;
1144 /* Redraw the menu */
1145 display_mainmenu(win->term, menu);
1148 static void
1149 mainmenu_handler(struct window *win, struct term_event *ev)
1151 struct menu *menu = win->data;
1153 menu->win = win;
1155 switch (ev->ev) {
1156 case EVENT_INIT:
1157 case EVENT_RESIZE:
1158 case EVENT_REDRAW:
1159 display_mainmenu(win->term, menu);
1160 break;
1162 case EVENT_MOUSE:
1163 #ifdef CONFIG_MOUSE
1164 mainmenu_mouse_handler(menu, ev);
1165 #endif /* CONFIG_MOUSE */
1166 break;
1168 case EVENT_KBD:
1169 mainmenu_kbd_handler(menu, ev);
1170 break;
1172 case EVENT_ABORT:
1173 break;
1177 /* For dynamic menus the last (cleared) item is used to mark the end. */
1178 #define realloc_menu_items(mi_, size) \
1179 mem_align_alloc(mi_, size, (size) + 2, struct menu_item, 0xF)
1181 struct menu_item *
1182 new_menu(enum menu_item_flags flags)
1184 struct menu_item *mi = NULL;
1186 if (realloc_menu_items(&mi, 0)) mi->flags = flags;
1188 return mi;
1191 void
1192 add_to_menu(struct menu_item **mi, unsigned char *text, unsigned char *rtext,
1193 enum main_action action_id, menu_func_T func, void *data,
1194 enum menu_item_flags flags)
1196 int n = count_items(*mi);
1197 /* XXX: Don't clear the last and special item. */
1198 struct menu_item *item = realloc_menu_items(mi, n + 1);
1200 if (!item) return;
1202 item += n;
1204 /* Shift current last item by one place. */
1205 copy_struct(item + 1, item);
1207 /* Setup the new item. All menu items share the item_free value. */
1208 SET_MENU_ITEM(item, text, rtext, action_id, func, data,
1209 item->flags | flags, HKS_SHOW, 0);
1212 #undef L_MAINMENU_SPACE
1213 #undef R_MAINMENU_SPACE
1214 #undef L_MAINTEXT_SPACE
1215 #undef R_MAINTEXT_SPACE
1216 #undef L_RTEXT_SPACE
1217 #undef R_RTEXT_SPACE
1218 #undef L_TEXT_SPACE
1219 #undef R_TEXT_SPACE
1220 #undef MENU_BORDER_SIZE