BFU UTF-8: select_button_by_key() folds the case of Unicode characters.
[elinks.git] / src / bfu / dialog.c
blobeab5e81c11d9ee1226670a5e0f813b74be8803ef
1 /* Dialog box implementation. */
3 #ifdef HAVE_CONFIG_H
4 #include "config.h"
5 #endif
7 #include <stdlib.h>
8 #include <string.h>
10 #include "elinks.h"
12 #include "bfu/dialog.h"
13 #include "config/kbdbind.h"
14 #include "config/options.h"
15 #include "intl/charsets.h"
16 #include "intl/gettext/libintl.h"
17 #include "terminal/draw.h"
18 #include "main/timer.h"
19 #include "terminal/kbd.h"
20 #include "terminal/terminal.h"
21 #include "terminal/window.h"
22 #include "util/color.h"
23 #include "util/conv.h"
24 #include "util/error.h"
25 #include "util/memlist.h"
26 #include "util/memory.h"
27 #include "util/string.h"
30 static window_handler_T dialog_func;
32 struct dialog_data *
33 do_dialog(struct terminal *term, struct dialog *dlg,
34 struct memory_list *ml)
36 struct dialog_data *dlg_data;
38 dlg_data = mem_calloc(1, sizeof(*dlg_data) +
39 sizeof(struct widget_data) * dlg->number_of_widgets);
40 if (!dlg_data) {
41 /* Worry not: freeml() checks whether its argument is NULL. */
42 freeml(ml);
43 return NULL;
46 dlg_data->dlg = dlg;
47 dlg_data->number_of_widgets = dlg->number_of_widgets;
48 dlg_data->ml = ml;
49 add_window(term, dialog_func, dlg_data);
51 return dlg_data;
54 static void cycle_widget_focus(struct dialog_data *dlg_data, int direction);
56 static void
57 update_all_widgets(struct dialog_data *dlg_data)
59 struct widget_data *widget_data;
61 /* Iterate backwards rather than forwards so that listboxes are drawn
62 * last, which means that they can grab the cursor. Yes, 'tis hacky. */
63 foreach_widget_back(dlg_data, widget_data) {
64 display_widget(dlg_data, widget_data);
68 void
69 redraw_dialog(struct dialog_data *dlg_data, int layout)
71 struct terminal *term = dlg_data->win->term;
72 struct color_pair *title_color;
74 if (layout) {
75 dlg_data->dlg->layouter(dlg_data);
76 /* This might not be the best place. We need to be able
77 * to make focusability of widgets dynamic so widgets
78 * like scrollable text don't receive focus when there
79 * is nothing to scroll. */
80 if (!widget_is_focusable(selected_widget(dlg_data)))
81 cycle_widget_focus(dlg_data, 1);
84 if (!dlg_data->dlg->layout.only_widgets) {
85 struct box box;
87 set_box(&box,
88 dlg_data->box.x + (DIALOG_LEFT_BORDER + 1),
89 dlg_data->box.y + (DIALOG_TOP_BORDER + 1),
90 dlg_data->box.width - 2 * (DIALOG_LEFT_BORDER + 1),
91 dlg_data->box.height - 2 * (DIALOG_TOP_BORDER + 1));
93 draw_border(term, &box, get_bfu_color(term, "dialog.frame"), DIALOG_FRAME);
95 assert(dlg_data->dlg->title);
97 title_color = get_bfu_color(term, "dialog.title");
98 if (title_color && box.width > 2) {
99 unsigned char *title = dlg_data->dlg->title;
100 int titlelen = strlen(title);
101 int titlecells = titlelen;
102 int x, y;
104 #ifdef CONFIG_UTF_8
105 if (term->utf8)
106 titlecells = utf8_ptr2cells(title,
107 &title[titlelen]);
108 #endif /* CONFIG_UTF_8 */
110 titlecells = int_min(box.width - 2, titlecells);
112 #ifdef CONFIG_UTF_8
113 if (term->utf8)
114 titlelen = utf8_cells2bytes(title, titlecells,
115 NULL);
116 #endif /* CONFIG_UTF_8 */
118 x = (box.width - titlecells) / 2 + box.x;
119 y = box.y - 1;
122 draw_text(term, x - 1, y, " ", 1, 0, title_color);
123 draw_text(term, x, y, title, titlelen, 0, title_color);
124 draw_text(term, x + titlecells, y, " ", 1, 0,
125 title_color);
129 update_all_widgets(dlg_data);
131 redraw_from_window(dlg_data->win);
134 static void
135 select_dlg_item(struct dialog_data *dlg_data, struct widget_data *widget_data)
137 select_widget(dlg_data, widget_data);
139 if (widget_data->widget->ops->select)
140 widget_data->widget->ops->select(dlg_data, widget_data);
143 static struct widget_ops *widget_type_to_ops[] = {
144 &checkbox_ops,
145 &field_ops,
146 &field_pass_ops,
147 &button_ops,
148 &listbox_ops,
149 &text_ops,
152 static struct widget_data *
153 init_widget(struct dialog_data *dlg_data, int i)
155 struct widget_data *widget_data = &dlg_data->widgets_data[i];
157 memset(widget_data, 0, sizeof(*widget_data));
158 widget_data->widget = &dlg_data->dlg->widgets[i];
160 if (widget_data->widget->datalen) {
161 widget_data->cdata = mem_alloc(widget_data->widget->datalen);
162 if (!widget_data->cdata) {
163 return NULL;
165 memcpy(widget_data->cdata,
166 widget_data->widget->data,
167 widget_data->widget->datalen);
170 widget_data->widget->ops = widget_type_to_ops[widget_data->widget->type];
172 if (widget_has_history(widget_data)) {
173 init_list(widget_data->info.field.history);
174 widget_data->info.field.cur_hist =
175 (struct input_history_entry *) &widget_data->info.field.history;
178 if (widget_data->widget->ops->init)
179 widget_data->widget->ops->init(dlg_data, widget_data);
181 return widget_data;
184 void
185 select_widget(struct dialog_data *dlg_data, struct widget_data *widget_data)
187 struct widget_data *previously_selected_widget;
189 previously_selected_widget = selected_widget(dlg_data);
191 dlg_data->selected_widget_id = widget_data - dlg_data->widgets_data;
193 display_widget(dlg_data, previously_selected_widget);
194 display_widget(dlg_data, widget_data);
198 struct widget_data *
199 select_widget_by_id(struct dialog_data *dlg_data, int i)
201 struct widget_data *widget_data;
203 if (i >= dlg_data->number_of_widgets)
204 return NULL;
206 widget_data = &dlg_data->widgets_data[i];
207 select_widget(dlg_data, widget_data);
209 return widget_data;
212 static void
213 cycle_widget_focus(struct dialog_data *dlg_data, int direction)
215 int prev_selected = dlg_data->selected_widget_id;
216 struct widget_data *previously_selected_widget;
218 previously_selected_widget = selected_widget(dlg_data);
220 do {
221 dlg_data->selected_widget_id += direction;
223 if (dlg_data->selected_widget_id >= dlg_data->number_of_widgets)
224 dlg_data->selected_widget_id = 0;
225 else if (dlg_data->selected_widget_id < 0)
226 dlg_data->selected_widget_id = dlg_data->number_of_widgets - 1;
228 } while (!widget_is_focusable(selected_widget(dlg_data))
229 && dlg_data->selected_widget_id != prev_selected);
231 display_widget(dlg_data, previously_selected_widget);
232 display_widget(dlg_data, selected_widget(dlg_data));
233 redraw_from_window(dlg_data->win);
236 static void
237 dialog_ev_init(struct dialog_data *dlg_data)
239 int i;
241 /* TODO: foreachback_widget() */
242 for (i = dlg_data->number_of_widgets - 1; i >= 0; i--) {
243 struct widget_data *widget_data;
245 widget_data = init_widget(dlg_data, i);
247 /* Make sure the selected widget is focusable */
248 if (widget_data
249 && widget_is_focusable(widget_data))
250 dlg_data->selected_widget_id = i;
254 #ifdef CONFIG_MOUSE
255 static void
256 dialog_ev_mouse(struct dialog_data *dlg_data)
258 struct widget_data *widget_data;
260 foreach_widget(dlg_data, widget_data) {
261 if (widget_data->widget->ops->mouse
262 && widget_data->widget->ops->mouse(dlg_data, widget_data)
263 == EVENT_PROCESSED)
264 break;
267 #endif /* CONFIG_MOUSE */
269 /* Look up for a button with matching flag. */
270 static void
271 select_button_by_flag(struct dialog_data *dlg_data, int flag)
273 struct widget_data *widget_data;
275 foreach_widget(dlg_data, widget_data) {
276 if (widget_data->widget->type == WIDGET_BUTTON
277 && widget_data->widget->info.button.flags & flag) {
278 select_dlg_item(dlg_data, widget_data);
279 break;
284 /* Look up for a button with matching starting letter. */
285 static void
286 select_button_by_key(struct dialog_data *dlg_data)
288 #ifdef CONFIG_UTF_8
289 unicode_val_T key;
290 int codepage;
291 #else
292 unsigned char key;
293 #endif
295 struct widget_data *widget_data;
296 struct term_event *ev = dlg_data->term_event;
298 if (!check_kbd_label_key(ev)) return;
300 #ifdef CONFIG_UTF_8
301 key = unicode_fold_label_case(get_kbd_key(ev));
302 codepage = get_opt_codepage_tree(dlg_data->win->term->spec, "charset");
303 #else
304 key = toupper(get_kbd_key(ev));
305 #endif
307 foreach_widget(dlg_data, widget_data) {
308 int hk_pos;
309 unsigned char *hk_ptr;
310 #ifdef CONFIG_UTF_8
311 unicode_val_T hk_char;
312 #else
313 unsigned char hk_char;
314 #endif
316 if (widget_data->widget->type != WIDGET_BUTTON)
317 continue;
319 /* We first try to match marked hotkey if there is
320 * one else we fallback to first character in button
321 * name. */
322 hk_pos = widget_data->widget->info.button.hotkey_pos;
323 if (hk_pos >= 0)
324 hk_ptr = &widget_data->widget->text[hk_pos + 1];
325 else
326 hk_ptr = widget_data->widget->text;
328 #ifdef CONFIG_UTF_8
329 if (is_cp_utf8(codepage))
330 hk_char = utf_8_to_unicode(&hk_ptr,
331 strchr(hk_ptr, '\0'));
332 else
333 hk_char = cp2u(codepage, *hk_ptr);
334 hk_char = unicode_fold_label_case(hk_char);
335 #else
336 hk_char = toupper(*hk_ptr);
337 #endif
339 if (hk_char == key) {
340 select_dlg_item(dlg_data, widget_data);
341 break;
346 static void
347 dialog_ev_kbd(struct dialog_data *dlg_data)
349 struct widget_data *widget_data = selected_widget(dlg_data);
350 struct widget_ops *ops = widget_data->widget->ops;
351 /* XXX: KEYMAP_EDIT ? --pasky */
352 enum menu_action action_id;
353 struct term_event *ev = dlg_data->term_event;
355 /* First let the widget try out. */
356 if (ops->kbd && ops->kbd(dlg_data, widget_data) == EVENT_PROCESSED)
357 return;
359 action_id = kbd_action(KEYMAP_MENU, ev, NULL);
360 switch (action_id) {
361 case ACT_MENU_SELECT:
362 /* Can we select? */
363 if (ops->select) {
364 ops->select(dlg_data, widget_data);
366 break;
367 case ACT_MENU_ENTER:
368 /* Submit button. */
369 if (ops->select) {
370 ops->select(dlg_data, widget_data);
371 break;
374 if (widget_is_textfield(widget_data)
375 || check_kbd_modifier(ev, KBD_MOD_CTRL)
376 || check_kbd_modifier(ev, KBD_MOD_ALT)) {
377 select_button_by_flag(dlg_data, B_ENTER);
379 break;
380 case ACT_MENU_CANCEL:
381 /* Cancel button. */
382 select_button_by_flag(dlg_data, B_ESC);
383 break;
384 case ACT_MENU_NEXT_ITEM:
385 case ACT_MENU_DOWN:
386 case ACT_MENU_RIGHT:
387 /* Cycle focus. */
388 cycle_widget_focus(dlg_data, 1);
389 break;
390 case ACT_MENU_PREVIOUS_ITEM:
391 case ACT_MENU_UP:
392 case ACT_MENU_LEFT:
393 /* Cycle focus (reverse). */
394 cycle_widget_focus(dlg_data, -1);
395 break;
396 case ACT_MENU_REDRAW:
397 redraw_terminal_cls(dlg_data->win->term);
398 break;
399 default:
400 select_button_by_key(dlg_data);
401 break;
405 static void
406 dialog_ev_abort(struct dialog_data *dlg_data)
408 struct widget_data *widget_data;
410 if (dlg_data->dlg->refresh) {
411 struct dialog_refresh *refresh = dlg_data->dlg->refresh;
413 kill_timer(&refresh->timer);
414 mem_free(refresh);
417 if (dlg_data->dlg->abort)
418 dlg_data->dlg->abort(dlg_data);
420 foreach_widget(dlg_data, widget_data) {
421 mem_free_if(widget_data->cdata);
422 if (widget_has_history(widget_data))
423 free_list(widget_data->info.field.history);
426 freeml(dlg_data->ml);
429 /* TODO: use EVENT_PROCESSED/EVENT_NOT_PROCESSED. */
430 static void
431 dialog_func(struct window *win, struct term_event *ev)
433 struct dialog_data *dlg_data = win->data;
435 dlg_data->win = win;
436 dlg_data->term_event = ev;
438 /* Look whether user event handlers can help us.. */
439 if (dlg_data->dlg->handle_event &&
440 (dlg_data->dlg->handle_event(dlg_data) == EVENT_PROCESSED)) {
441 return;
444 switch (ev->ev) {
445 case EVENT_INIT:
446 dialog_ev_init(dlg_data);
447 /* fallback */
448 case EVENT_RESIZE:
449 case EVENT_REDRAW:
450 redraw_dialog(dlg_data, 1);
451 break;
453 case EVENT_MOUSE:
454 #ifdef CONFIG_MOUSE
455 dialog_ev_mouse(dlg_data);
456 #endif
457 break;
459 case EVENT_KBD:
460 dialog_ev_kbd(dlg_data);
461 break;
463 case EVENT_ABORT:
464 dialog_ev_abort(dlg_data);
465 break;
470 check_dialog(struct dialog_data *dlg_data)
472 struct widget_data *widget_data;
474 foreach_widget(dlg_data, widget_data) {
475 if (widget_data->widget->type != WIDGET_CHECKBOX &&
476 !widget_is_textfield(widget_data))
477 continue;
479 if (widget_data->widget->handler &&
480 widget_data->widget->handler(dlg_data, widget_data)) {
481 select_widget(dlg_data, widget_data);
482 redraw_dialog(dlg_data, 0);
483 return 1;
487 return 0;
490 widget_handler_status_T
491 cancel_dialog(struct dialog_data *dlg_data, struct widget_data *xxx)
493 delete_window(dlg_data->win);
494 return EVENT_PROCESSED;
498 update_dialog_data(struct dialog_data *dlg_data)
500 struct widget_data *widget_data;
502 foreach_widget(dlg_data, widget_data) {
503 if (!widget_data->widget->datalen)
504 continue;
505 memcpy(widget_data->widget->data,
506 widget_data->cdata,
507 widget_data->widget->datalen);
510 return 0;
513 widget_handler_status_T
514 ok_dialog(struct dialog_data *dlg_data, struct widget_data *widget_data)
516 done_handler_T *done = widget_data->widget->info.button.done;
517 void *done_data = widget_data->widget->info.button.done_data;
519 if (check_dialog(dlg_data)) return EVENT_NOT_PROCESSED;
521 update_dialog_data(dlg_data);
523 if (done) done(done_data);
524 return cancel_dialog(dlg_data, widget_data);
527 /* Clear dialog fields (if widget has clear callback). */
528 widget_handler_status_T
529 clear_dialog(struct dialog_data *dlg_data, struct widget_data *xxx)
531 struct widget_data *widget_data;
533 foreach_widget(dlg_data, widget_data) {
534 if (widget_data->widget->ops->clear)
535 widget_data->widget->ops->clear(dlg_data, widget_data);
538 /* Move focus to the first widget. It helps with bookmark search dialog
539 * (and others). */
540 select_widget_by_id(dlg_data, 0);
542 redraw_dialog(dlg_data, 0);
543 return EVENT_PROCESSED;
547 static void
548 format_widgets(struct terminal *term, struct dialog_data *dlg_data,
549 int x, int *y, int w, int h, int *rw, int format_only)
551 struct widget_data *wdata = dlg_data->widgets_data;
552 int widgets = dlg_data->number_of_widgets;
554 /* TODO: Do something if (*y) gets > height. */
555 for (; widgets > 0; widgets--, wdata++, (*y)++) {
556 switch (wdata->widget->type) {
557 case WIDGET_FIELD_PASS:
558 case WIDGET_FIELD:
559 dlg_format_field(term, wdata, x, y, w, rw, ALIGN_LEFT,
560 format_only);
561 break;
563 case WIDGET_LISTBOX:
564 dlg_format_listbox(term, wdata, x, y, w, h, rw,
565 ALIGN_LEFT, format_only);
566 break;
568 case WIDGET_TEXT:
569 dlg_format_text(term, wdata, x, y, w, rw, h,
570 format_only);
571 break;
573 case WIDGET_CHECKBOX:
575 int group = widget_has_group(wdata);
577 if (group > 0 && dlg_data->dlg->layout.float_groups) {
578 int size;
580 /* Find group size */
581 for (size = 1; widgets > 0; size++, widgets--) {
582 struct widget_data *next = &wdata[size];
584 if (group != widget_has_group(next))
585 break;
588 dlg_format_group(term, wdata, size, x, y, w, rw,
589 format_only);
590 wdata += size - 1;
592 } else {
594 /* No horizontal space between checkboxes belonging to
595 * the same group. */
596 dlg_format_checkbox(term, wdata, x, y, w, rw,
597 ALIGN_LEFT, format_only);
598 if (widgets > 1
599 && group == widget_has_group(&wdata[1]))
600 (*y)--;
603 break;
605 /* We assume that the buttons are all stuffed at the very end
606 * of the dialog. */
607 case WIDGET_BUTTON:
608 dlg_format_buttons(term, wdata, widgets,
609 x, y, w, rw, ALIGN_CENTER, format_only);
610 return;
615 void
616 generic_dialog_layouter(struct dialog_data *dlg_data)
618 struct terminal *term = dlg_data->win->term;
619 int w = dialog_max_width(term);
620 int height = dialog_max_height(term);
621 int x = 0, y, rw;
623 #ifdef CONFIG_UTF_8
624 if (term->utf8)
625 rw = int_min(w, utf8_ptr2cells(dlg_data->dlg->title, NULL));
626 else
627 #endif /* CONFIG_UTF_8 */
628 rw = int_min(w, strlen(dlg_data->dlg->title));
629 y = dlg_data->dlg->layout.padding_top ? 0 : -1;
631 format_widgets(term, dlg_data, x, &y, w, height, &rw, 1);
633 /* Update the width to respond to the required minimum width */
634 if (dlg_data->dlg->layout.fit_datalen) {
635 int_lower_bound(&rw, dlg_data->dlg->widgets->datalen);
636 int_upper_bound(&w, rw);
637 } else if (!dlg_data->dlg->layout.maximize_width) {
638 w = rw;
641 draw_dialog(dlg_data, w, y);
643 y = dlg_data->box.y + DIALOG_TB + dlg_data->dlg->layout.padding_top;
644 x = dlg_data->box.x + DIALOG_LB;
646 format_widgets(term, dlg_data, x, &y, w, height, NULL, 0);
650 void
651 draw_dialog(struct dialog_data *dlg_data, int width, int height)
653 struct terminal *term = dlg_data->win->term;
654 int dlg_width = int_min(term->width, width + 2 * DIALOG_LB);
655 int dlg_height = int_min(term->height, height + 2 * DIALOG_TB);
657 set_box(&dlg_data->box,
658 (term->width - dlg_width) / 2, (term->height - dlg_height) / 2,
659 dlg_width, dlg_height);
661 draw_box(term, &dlg_data->box, ' ', 0,
662 get_bfu_color(term, "dialog.generic"));
664 if (get_opt_bool("ui.dialogs.shadows")) {
665 /* Draw shadow */
666 draw_shadow(term, &dlg_data->box,
667 get_bfu_color(term, "dialog.shadow"), 2, 1);
668 #ifdef CONFIG_UTF_8
669 if (term->utf8)
670 fix_dwchar_around_box(term, &dlg_data->box, 0, 2, 1);
671 #endif /* CONFIG_UTF_8 */
673 #ifdef CONFIG_UTF_8
674 else if(term->utf8)
675 fix_dwchar_around_box(term, &dlg_data->box, 0, 0, 0);
676 #endif /* CONFIG_UTF_8 */
679 static void
680 do_refresh_dialog(struct dialog_data *dlg_data)
682 struct dialog_refresh *refresh = dlg_data->dlg->refresh;
683 enum dlg_refresh_code refresh_code;
685 assert(refresh && refresh->handler);
687 refresh_code = refresh->handler(dlg_data, refresh->data);
689 if (refresh_code == REFRESH_CANCEL
690 || refresh_code == REFRESH_STOP) {
691 refresh->timer = TIMER_ID_UNDEF;
692 if (refresh_code == REFRESH_CANCEL)
693 cancel_dialog(dlg_data, NULL);
694 return;
697 /* We want dialog_has_refresh() to be true while drawing
698 * so we can not set the timer to -1. */
699 if (refresh_code == REFRESH_DIALOG) {
700 redraw_dialog(dlg_data, 1);
703 install_timer(&refresh->timer, RESOURCE_INFO_REFRESH,
704 (void (*)(void *)) do_refresh_dialog, dlg_data);
707 void
708 refresh_dialog(struct dialog_data *dlg_data, dialog_refresh_handler_T handler, void *data)
710 struct dialog_refresh *refresh = dlg_data->dlg->refresh;
712 if (!refresh) {
713 refresh = mem_calloc(1, sizeof(*refresh));
714 if (!refresh) return;
716 dlg_data->dlg->refresh = refresh;
718 } else {
719 kill_timer(&refresh->timer);
722 refresh->handler = handler;
723 refresh->data = data;
724 install_timer(&refresh->timer, RESOURCE_INFO_REFRESH,
725 (void (*)(void *)) do_refresh_dialog, dlg_data);