Explicit cast to (const char *) in strcasestr for C++
[elinks.git] / src / bfu / menu.c
blob7e76fd911a753b0f4abc03b83e2fa6f3fa15da80
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 != NULL, "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_UTF8
204 if (term->utf8_cp)
205 return L_TEXT_SPACE + utf8_ptr2cells(text, NULL)
206 - !!mi->hotkey_pos + R_TEXT_SPACE;
207 else
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. */
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_UTF8
386 if (term->utf8_cp) {
387 max_len = utf8_cells2bytes(text, w, NULL);
388 if (max_len <= 0)
389 return;
390 } else
391 #endif /* CONFIG_UTF8 */
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", NULL)
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_UTF8
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_UTF8
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) {
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_UTF8
458 utf8:
459 end = strchr((const char *)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 UCS_ORPHAN_CELL, 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, UCS_ORPHAN_CELL, 0, color);
513 } else {
514 draw_char(term, xbase + x - !!hk_state,
515 y, data, 0, color);
520 #endif /* CONFIG_UTF8 */
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", NULL)) {
560 /* Draw shadow */
561 draw_shadow(term, &menu->box,
562 get_bfu_color(term, "dialog.shadow"), 2, 1);
563 #ifdef CONFIG_UTF8
564 if (term->utf8_cp)
565 fix_dwchar_around_box(term, &box, 1, 2, 1);
566 #endif /* CONFIG_UTF8 */
568 #ifdef 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;
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_windows(REDRAW_IN_FRONT_OF_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((const char *)text, '~');
799 if (match)
800 memmove(match, match + 1, strlen(match));
802 match = strcasestr((const char *)text, (const char *)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 *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);
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_REDRAW:
941 display_menu(win->term, menu);
942 redraw_terminal_cls(win->term);
943 break;
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);
949 else
950 delete_window_ev(win, NULL);
952 return;
954 default:
956 term_event_key_T key = get_kbd_key(ev);
958 if (is_kbd_fkey(key)
959 || check_kbd_modifier(ev, KBD_MOD_ALT)) {
960 delete_window_ev(win, ev);
961 return;
964 if (!check_kbd_label_key(ev))
965 break;
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);
973 } else {
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);
980 if (auto_complete) {
981 struct term_event complete;
983 complete.ev = EVENT_KBD;
984 complete.info.keyboard = auto_complete->kbd;
985 term_send_event(term, &complete);
987 return;
992 display_menu(win->term, menu);
993 if (s) {
994 enter:
995 select_menu(win->term, menu);
999 static void
1000 menu_handler(struct window *win, struct term_event *ev)
1002 struct menu *menu = win->data;
1004 menu->win = win;
1006 switch (ev->ev) {
1007 case EVENT_INIT:
1008 case EVENT_RESIZE:
1009 get_parent_ptr(win, &menu->parent_x, &menu->parent_y);
1010 case EVENT_REDRAW:
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);
1017 break;
1019 case EVENT_MOUSE:
1020 #ifdef CONFIG_MOUSE
1021 menu_mouse_handler(menu, ev);
1022 #endif /* CONFIG_MOUSE */
1023 break;
1025 case EVENT_KBD:
1026 menu_kbd_handler(menu, ev);
1027 break;
1029 case EVENT_ABORT:
1030 free_menu_items(menu->items);
1031 break;
1036 void
1037 do_mainmenu(struct terminal *term, struct menu_item *items,
1038 void *data, int sel)
1040 int init = 0;
1041 struct menu *menu;
1042 struct window *win;
1044 if (!term->main_menu) {
1045 term->main_menu = mem_calloc(1, sizeof(*menu));
1046 if (!term->main_menu) return;
1047 init = 1;
1050 menu = term->main_menu;
1051 menu->selected = (sel == -1 ? 0 : sel);
1052 menu->items = items;
1053 menu->data = data;
1054 menu->size = count_items(items);
1055 menu->hotkeys = 1;
1057 #ifdef CONFIG_NLS
1058 clear_hotkeys_cache(menu);
1059 #endif
1060 init_hotkeys(term, menu);
1061 if (init) {
1062 add_window(term, mainmenu_handler, menu);
1063 win = menu->win;
1064 /* This should be fine because add_window will call
1065 * mainmenu_handler which will assign the window to menu->win.
1067 assert(win);
1068 deselect_mainmenu(term, menu);
1069 } else {
1070 foreach (win, term->windows) {
1071 if (win->data == menu) {
1072 del_from_list(win);
1073 add_to_list(term->windows, win);
1074 display_mainmenu(term, menu);
1075 break;
1080 if (sel != -1) {
1081 select_menu(term, menu);
1085 static void
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");
1090 int p = 0;
1091 int i;
1092 struct box box;
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;
1132 int textlen;
1133 int selected = (i == menu->selected);
1134 int screencnt;
1136 if (mi_text_translate(mi))
1137 text = _(text, term);
1139 textlen = strlen(text) - !!l;
1140 #ifdef CONFIG_UTF8
1141 if (term->utf8_cp)
1142 screencnt = utf8_ptr2cells(text, NULL) - !!l;
1143 else
1144 #endif /* CONFIG_UTF8 */
1145 screencnt = textlen;
1147 if (selected) {
1148 color = selected_color;
1149 box.x = p;
1150 #ifdef CONFIG_UTF8
1151 if (term->utf8_cp)
1152 box.width = L_MAINTEXT_SPACE + L_TEXT_SPACE
1153 + screencnt
1154 + R_TEXT_SPACE + R_MAINTEXT_SPACE;
1155 else
1156 #endif /* CONFIG_UTF8 */
1157 box.width = L_MAINTEXT_SPACE + L_TEXT_SPACE
1158 + textlen
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;
1168 if (l) {
1169 draw_menu_left_text_hk(term, text, l,
1170 p, 0, textlen + R_TEXT_SPACE + L_TEXT_SPACE,
1171 color, selected);
1172 } else {
1173 draw_menu_left_text(term, text, textlen,
1174 p, 0, textlen + R_TEXT_SPACE + L_TEXT_SPACE,
1175 color);
1178 p += screencnt;
1180 if (p >= term->width - R_MAINMENU_SPACE)
1181 break;
1183 p += R_MAINTEXT_SPACE + R_TEXT_SPACE + L_TEXT_SPACE;
1186 menu->last = i - 1;
1187 int_lower_bound(&menu->last, menu->first);
1188 if (menu->last < menu->size - 1) {
1189 #ifdef CONFIG_UTF8
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. */
1198 schar++;
1199 draw_char_data(term, term->width - R_MAINMENU_SPACE - 1,
1200 0, UCS_ORPHAN_CELL);
1203 #endif
1205 set_box(&box,
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);
1215 #ifdef CONFIG_MOUSE
1216 static void
1217 mainmenu_mouse_handler(struct menu *menu, struct term_event *ev)
1219 struct window *win = menu->win;
1220 struct menu_item *item;
1221 int scroll = 0;
1223 if (check_mouse_wheel(ev))
1224 return;
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);
1233 return;
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) {
1240 scroll = -1;
1242 } else if (ev->info.mouse.x >= win->term->width - R_MAINMENU_SPACE) {
1243 scroll = 1;
1245 } else {
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;
1267 break;
1272 if (scroll) {
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);
1282 #endif
1284 static void
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:
1292 case ACT_MENU_DOWN:
1293 case ACT_MENU_UP:
1294 case ACT_MENU_PAGE_UP:
1295 case ACT_MENU_PAGE_DOWN:
1296 case ACT_MENU_SELECT:
1297 select_menu(win->term, menu);
1298 return;
1300 case ACT_MENU_HOME:
1301 scroll_menu(menu, -menu->selected, 0);
1302 break;
1304 case ACT_MENU_END:
1305 scroll_menu(menu, menu->size - menu->selected - 1, 0);
1306 break;
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. */
1316 case ACT_MENU_LEFT:
1317 case ACT_MENU_RIGHT:
1318 scroll_menu(menu, action_id == ACT_MENU_LEFT ? -1 : 1, 1);
1319 break;
1321 case ACT_MENU_REDRAW:
1322 display_mainmenu(win->term, menu);
1323 redraw_terminal_cls(win->term);
1324 break;
1326 default:
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);
1333 return;
1336 case ACT_MENU_CANCEL:
1337 deselect_mainmenu(win->term, menu);
1338 break;
1341 /* Redraw the menu */
1342 display_mainmenu(win->term, menu);
1345 static void
1346 mainmenu_handler(struct window *win, struct term_event *ev)
1348 struct menu *menu = win->data;
1350 menu->win = win;
1352 switch (ev->ev) {
1353 case EVENT_INIT:
1354 case EVENT_RESIZE:
1355 case EVENT_REDRAW:
1356 display_mainmenu(win->term, menu);
1357 break;
1359 case EVENT_MOUSE:
1360 #ifdef CONFIG_MOUSE
1361 mainmenu_mouse_handler(menu, ev);
1362 #endif /* CONFIG_MOUSE */
1363 break;
1365 case EVENT_KBD:
1366 mainmenu_kbd_handler(menu, ev);
1367 break;
1369 case EVENT_ABORT:
1370 break;
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)
1378 struct menu_item *
1379 new_menu(enum menu_item_flags flags)
1381 struct menu_item *mi = NULL;
1383 if (realloc_menu_items(&mi, 0)) mi->flags = flags;
1385 return mi;
1388 void
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);
1397 if (!item) return;
1399 item += n;
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
1415 #undef L_TEXT_SPACE
1416 #undef R_TEXT_SPACE
1417 #undef MENU_BORDER_SIZE