big dialogs: set_curosr2 -> set_dlg_cursor.
[elinks.git] / src / bfu / dialog.c
blob85afd0a6bbd8b36ba94801113377edc39a84c08f
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 set_box(&dlg_data->real_box,
86 dlg_data->box.x + (DIALOG_LEFT_BORDER + 1),
87 dlg_data->box.y + (DIALOG_TOP_BORDER + 1),
88 dlg_data->box.width - 2 * (DIALOG_LEFT_BORDER + 1),
89 dlg_data->box.height - 2 * (DIALOG_TOP_BORDER + 1));
91 draw_border(term, &dlg_data->real_box, get_bfu_color(term, "dialog.frame"), DIALOG_FRAME);
93 assert(dlg_data->dlg->title);
95 title_color = get_bfu_color(term, "dialog.title");
96 if (title_color && dlg_data->real_box.width > 2) {
97 unsigned char *title = dlg_data->dlg->title;
98 int titlelen = strlen(title);
99 int titlecells = titlelen;
100 int x, y;
102 #ifdef CONFIG_UTF8
103 if (term->utf8_cp)
104 titlecells = utf8_ptr2cells(title,
105 &title[titlelen]);
106 #endif /* CONFIG_UTF8 */
108 titlecells = int_min(dlg_data->real_box.width - 2, titlecells);
110 #ifdef CONFIG_UTF8
111 if (term->utf8_cp)
112 titlelen = utf8_cells2bytes(title, titlecells,
113 NULL);
114 #endif /* CONFIG_UTF8 */
116 x = (dlg_data->real_box.width - titlecells) / 2 + dlg_data->real_box.x;
117 y = dlg_data->real_box.y - 1;
120 draw_dlg_text(term, dlg_data, x - 1, y, " ", 1, 0, title_color);
121 draw_dlg_text(term, dlg_data, x, y, title, titlelen, 0, title_color);
122 draw_dlg_text(term, dlg_data, x + titlecells, y, " ", 1, 0,
123 title_color);
127 update_all_widgets(dlg_data);
129 redraw_from_window(dlg_data->win);
132 static void
133 select_dlg_item(struct dialog_data *dlg_data, struct widget_data *widget_data)
135 select_widget(dlg_data, widget_data);
137 if (widget_data->widget->ops->select)
138 widget_data->widget->ops->select(dlg_data, widget_data);
141 static const struct widget_ops *const widget_type_to_ops[] = {
142 &checkbox_ops,
143 &field_ops,
144 &field_pass_ops,
145 &button_ops,
146 &listbox_ops,
147 &text_ops,
150 static struct widget_data *
151 init_widget(struct dialog_data *dlg_data, int i)
153 struct widget_data *widget_data = &dlg_data->widgets_data[i];
155 memset(widget_data, 0, sizeof(*widget_data));
156 widget_data->widget = &dlg_data->dlg->widgets[i];
158 if (widget_data->widget->datalen) {
159 widget_data->cdata = mem_alloc(widget_data->widget->datalen);
160 if (!widget_data->cdata) {
161 return NULL;
163 memcpy(widget_data->cdata,
164 widget_data->widget->data,
165 widget_data->widget->datalen);
168 widget_data->widget->ops = widget_type_to_ops[widget_data->widget->type];
170 if (widget_has_history(widget_data)) {
171 init_list(widget_data->info.field.history);
172 widget_data->info.field.cur_hist =
173 (struct input_history_entry *) &widget_data->info.field.history;
176 if (widget_data->widget->ops->init)
177 widget_data->widget->ops->init(dlg_data, widget_data);
179 return widget_data;
182 static int
183 check_range(struct dialog_data *dlg_data, struct widget_data *widget_data)
185 if (!dlg_data->dlg->layout.only_widgets) {
186 struct box *box = &widget_data->box;
187 struct box *dlgbox = &dlg_data->real_box;
188 int y = box->y - dlgbox->y;
190 if ((y < dlg_data->y) || (y >= dlg_data->y + dlgbox->height)) {
191 /* This calculates the offset of the window's top. */
192 dlg_data->y = (y / dlgbox->height) * dlgbox->height;
193 return 1;
196 return 0;
199 void
200 select_widget(struct dialog_data *dlg_data, struct widget_data *widget_data)
202 struct widget_data *previously_selected_widget;
204 previously_selected_widget = selected_widget(dlg_data);
206 dlg_data->selected_widget_id = widget_data - dlg_data->widgets_data;
208 if (check_range(dlg_data, widget_data)) {
209 redraw_from_window(dlg_data->win);
210 return;
213 display_widget(dlg_data, previously_selected_widget);
214 display_widget(dlg_data, widget_data);
218 struct widget_data *
219 select_widget_by_id(struct dialog_data *dlg_data, int i)
221 struct widget_data *widget_data;
223 if (i >= dlg_data->number_of_widgets)
224 return NULL;
226 widget_data = &dlg_data->widgets_data[i];
227 select_widget(dlg_data, widget_data);
229 return widget_data;
232 static void
233 cycle_widget_focus(struct dialog_data *dlg_data, int direction)
235 int prev_selected = dlg_data->selected_widget_id;
236 struct widget_data *previously_selected_widget;
238 previously_selected_widget = selected_widget(dlg_data);
240 do {
241 dlg_data->selected_widget_id += direction;
243 if (dlg_data->selected_widget_id >= dlg_data->number_of_widgets)
244 dlg_data->selected_widget_id = 0;
245 else if (dlg_data->selected_widget_id < 0)
246 dlg_data->selected_widget_id = dlg_data->number_of_widgets - 1;
248 } while (!widget_is_focusable(selected_widget(dlg_data))
249 && dlg_data->selected_widget_id != prev_selected);
251 if (check_range(dlg_data, selected_widget(dlg_data))) {
252 redraw_from_window(dlg_data->win);
253 return;
256 display_widget(dlg_data, previously_selected_widget);
257 display_widget(dlg_data, selected_widget(dlg_data));
258 redraw_from_window(dlg_data->win);
261 static void
262 dialog_ev_init(struct dialog_data *dlg_data)
264 int i;
266 dlg_data->y = 0;
267 /* TODO: foreachback_widget() */
268 for (i = dlg_data->number_of_widgets - 1; i >= 0; i--) {
269 struct widget_data *widget_data;
271 widget_data = init_widget(dlg_data, i);
273 /* Make sure the selected widget is focusable */
274 if (widget_data
275 && widget_is_focusable(widget_data))
276 dlg_data->selected_widget_id = i;
280 #ifdef CONFIG_MOUSE
281 static void
282 dialog_ev_mouse(struct dialog_data *dlg_data)
284 struct widget_data *widget_data;
286 foreach_widget(dlg_data, widget_data) {
287 if (widget_data->widget->ops->mouse
288 && widget_data->widget->ops->mouse(dlg_data, widget_data)
289 == EVENT_PROCESSED)
290 break;
293 #endif /* CONFIG_MOUSE */
295 /* Look up for a button with matching flag. */
296 static void
297 select_button_by_flag(struct dialog_data *dlg_data, int flag)
299 struct widget_data *widget_data;
301 foreach_widget(dlg_data, widget_data) {
302 if (widget_data->widget->type == WIDGET_BUTTON
303 && widget_data->widget->info.button.flags & flag) {
304 select_dlg_item(dlg_data, widget_data);
305 break;
310 /* Look up for a button with matching starting letter. */
311 static void
312 select_button_by_key(struct dialog_data *dlg_data)
314 term_event_char_T key;
315 #ifdef CONFIG_UTF8
316 int codepage;
317 #endif
319 struct widget_data *widget_data;
320 struct term_event *ev = dlg_data->term_event;
322 if (!check_kbd_label_key(ev)) return;
324 #ifdef CONFIG_UTF8
325 key = unicode_fold_label_case(get_kbd_key(ev));
326 codepage = get_opt_codepage_tree(dlg_data->win->term->spec, "charset", NULL);
327 #else
328 key = toupper(get_kbd_key(ev));
329 #endif
331 foreach_widget(dlg_data, widget_data) {
332 int hk_pos;
333 unsigned char *hk_ptr;
334 term_event_char_T hk_char;
336 if (widget_data->widget->type != WIDGET_BUTTON)
337 continue;
339 hk_ptr = widget_data->widget->text;
340 if (!*hk_ptr)
341 continue;
343 /* We first try to match marked hotkey if there is
344 * one else we fallback to first character in button
345 * name. */
346 hk_pos = widget_data->widget->info.button.hotkey_pos;
347 if (hk_pos >= 0)
348 hk_ptr += hk_pos + 1;
350 #ifdef CONFIG_UTF8
351 hk_char = cp_to_unicode(codepage, &hk_ptr,
352 strchr(hk_ptr, '\0'));
353 /* hk_char can be UCS_NO_CHAR only if the text of the
354 * widget is not in the expected codepage. */
355 assert(hk_char != UCS_NO_CHAR);
356 if_assert_failed continue;
357 hk_char = unicode_fold_label_case(hk_char);
358 #else
359 hk_char = toupper(*hk_ptr);
360 #endif
362 if (hk_char == key) {
363 select_dlg_item(dlg_data, widget_data);
364 break;
369 static void
370 dialog_ev_kbd(struct dialog_data *dlg_data)
372 struct widget_data *widget_data = selected_widget(dlg_data);
373 const struct widget_ops *ops = widget_data->widget->ops;
374 /* XXX: KEYMAP_EDIT ? --pasky */
375 enum menu_action action_id;
376 struct term_event *ev = dlg_data->term_event;
378 /* First let the widget try out. */
379 if (ops->kbd && ops->kbd(dlg_data, widget_data) == EVENT_PROCESSED)
380 return;
382 action_id = kbd_action(KEYMAP_MENU, ev, NULL);
383 switch (action_id) {
384 case ACT_MENU_SELECT:
385 /* Can we select? */
386 if (ops->select) {
387 ops->select(dlg_data, widget_data);
389 break;
390 case ACT_MENU_ENTER:
391 /* Submit button. */
392 if (ops->select) {
393 ops->select(dlg_data, widget_data);
394 break;
397 if (widget_is_textfield(widget_data)
398 || check_kbd_modifier(ev, KBD_MOD_CTRL)
399 || check_kbd_modifier(ev, KBD_MOD_ALT)) {
400 select_button_by_flag(dlg_data, B_ENTER);
402 break;
403 case ACT_MENU_CANCEL:
404 /* Cancel button. */
405 select_button_by_flag(dlg_data, B_ESC);
406 break;
407 case ACT_MENU_NEXT_ITEM:
408 case ACT_MENU_DOWN:
409 case ACT_MENU_RIGHT:
410 /* Cycle focus. */
411 cycle_widget_focus(dlg_data, 1);
412 break;
413 case ACT_MENU_PREVIOUS_ITEM:
414 case ACT_MENU_UP:
415 case ACT_MENU_LEFT:
416 /* Cycle focus (reverse). */
417 cycle_widget_focus(dlg_data, -1);
418 break;
419 case ACT_MENU_REDRAW:
420 redraw_terminal_cls(dlg_data->win->term);
421 break;
422 default:
423 select_button_by_key(dlg_data);
424 break;
428 static void
429 dialog_ev_abort(struct dialog_data *dlg_data)
431 struct widget_data *widget_data;
433 if (dlg_data->dlg->refresh) {
434 struct dialog_refresh *refresh = dlg_data->dlg->refresh;
436 kill_timer(&refresh->timer);
437 mem_free(refresh);
440 if (dlg_data->dlg->abort)
441 dlg_data->dlg->abort(dlg_data);
443 foreach_widget(dlg_data, widget_data) {
444 mem_free_if(widget_data->cdata);
445 if (widget_has_history(widget_data))
446 free_list(widget_data->info.field.history);
449 freeml(dlg_data->ml);
450 dlg_data->y = 0;
453 /* TODO: use EVENT_PROCESSED/EVENT_NOT_PROCESSED. */
454 static void
455 dialog_func(struct window *win, struct term_event *ev)
457 struct dialog_data *dlg_data = win->data;
459 dlg_data->win = win;
460 dlg_data->term_event = ev;
462 /* Look whether user event handlers can help us.. */
463 if (dlg_data->dlg->handle_event &&
464 (dlg_data->dlg->handle_event(dlg_data) == EVENT_PROCESSED)) {
465 return;
468 switch (ev->ev) {
469 case EVENT_INIT:
470 dialog_ev_init(dlg_data);
471 /* fallback */
472 case EVENT_RESIZE:
473 case EVENT_REDRAW:
474 redraw_dialog(dlg_data, 1);
475 break;
477 case EVENT_MOUSE:
478 #ifdef CONFIG_MOUSE
479 dialog_ev_mouse(dlg_data);
480 #endif
481 break;
483 case EVENT_KBD:
484 dialog_ev_kbd(dlg_data);
485 break;
487 case EVENT_ABORT:
488 dialog_ev_abort(dlg_data);
489 break;
494 check_dialog(struct dialog_data *dlg_data)
496 struct widget_data *widget_data;
498 foreach_widget(dlg_data, widget_data) {
499 if (widget_data->widget->type != WIDGET_CHECKBOX &&
500 !widget_is_textfield(widget_data))
501 continue;
503 if (widget_data->widget->handler &&
504 widget_data->widget->handler(dlg_data, widget_data)
505 == EVENT_NOT_PROCESSED) {
506 select_widget(dlg_data, widget_data);
507 redraw_dialog(dlg_data, 0);
508 return 1;
512 return 0;
515 widget_handler_status_T
516 cancel_dialog(struct dialog_data *dlg_data, struct widget_data *xxx)
518 delete_window(dlg_data->win);
519 return EVENT_PROCESSED;
523 update_dialog_data(struct dialog_data *dlg_data)
525 struct widget_data *widget_data;
527 foreach_widget(dlg_data, widget_data) {
528 if (!widget_data->widget->datalen)
529 continue;
530 memcpy(widget_data->widget->data,
531 widget_data->cdata,
532 widget_data->widget->datalen);
535 return 0;
538 widget_handler_status_T
539 ok_dialog(struct dialog_data *dlg_data, struct widget_data *widget_data)
541 done_handler_T *done = widget_data->widget->info.button.done;
542 void *done_data = widget_data->widget->info.button.done_data;
544 if (check_dialog(dlg_data)) return EVENT_NOT_PROCESSED;
546 update_dialog_data(dlg_data);
548 if (done) done(done_data);
549 return cancel_dialog(dlg_data, widget_data);
552 /* Clear dialog fields (if widget has clear callback). */
553 widget_handler_status_T
554 clear_dialog(struct dialog_data *dlg_data, struct widget_data *xxx)
556 struct widget_data *widget_data;
558 foreach_widget(dlg_data, widget_data) {
559 if (widget_data->widget->ops->clear)
560 widget_data->widget->ops->clear(dlg_data, widget_data);
563 /* Move focus to the first widget. It helps with bookmark search dialog
564 * (and others). */
565 select_widget_by_id(dlg_data, 0);
567 redraw_dialog(dlg_data, 0);
568 return EVENT_PROCESSED;
572 static void
573 format_widgets(struct terminal *term, struct dialog_data *dlg_data,
574 int x, int *y, int w, int h, int *rw, int format_only)
576 struct widget_data *wdata = dlg_data->widgets_data;
577 int widgets = dlg_data->number_of_widgets;
579 /* TODO: Do something if (*y) gets > height. */
580 for (; widgets > 0; widgets--, wdata++, (*y)++) {
581 switch (wdata->widget->type) {
582 case WIDGET_FIELD_PASS:
583 case WIDGET_FIELD:
584 dlg_format_field(term, dlg_data, wdata, x, y, w, rw, ALIGN_LEFT,
585 format_only);
586 break;
588 case WIDGET_LISTBOX:
589 dlg_format_listbox(term, dlg_data, wdata, x, y, w, h, rw,
590 ALIGN_LEFT, format_only);
591 break;
593 case WIDGET_TEXT:
594 dlg_format_text(dlg_data, wdata, x, y, w, rw, h,
595 format_only);
596 break;
598 case WIDGET_CHECKBOX:
600 int group = widget_has_group(wdata);
602 if (group > 0 && dlg_data->dlg->layout.float_groups) {
603 int size;
605 /* Find group size */
606 for (size = 1; widgets > 0; size++, widgets--) {
607 struct widget_data *next = &wdata[size];
609 if (group != widget_has_group(next))
610 break;
613 dlg_format_group(term, dlg_data, wdata, size, x, y, w, rw,
614 format_only);
615 wdata += size - 1;
617 } else {
619 /* No horizontal space between checkboxes belonging to
620 * the same group. */
621 dlg_format_checkbox(term, dlg_data, wdata, x, y, w, rw,
622 ALIGN_LEFT, format_only);
623 if (widgets > 1
624 && group == widget_has_group(&wdata[1]))
625 (*y)--;
628 break;
630 /* We assume that the buttons are all stuffed at the very end
631 * of the dialog. */
632 case WIDGET_BUTTON:
633 dlg_format_buttons(term, dlg_data, wdata, widgets,
634 x, y, w, rw, ALIGN_CENTER, format_only);
635 return;
640 void
641 generic_dialog_layouter(struct dialog_data *dlg_data)
643 struct terminal *term = dlg_data->win->term;
644 int w = dialog_max_width(term);
645 int height = dialog_max_height(term);
646 int x = 0, y, rw;
648 #ifdef CONFIG_UTF8
649 if (term->utf8_cp)
650 rw = int_min(w, utf8_ptr2cells(dlg_data->dlg->title, NULL));
651 else
652 #endif /* CONFIG_UTF8 */
653 rw = int_min(w, strlen(dlg_data->dlg->title));
654 y = dlg_data->dlg->layout.padding_top ? 0 : -1;
656 format_widgets(term, dlg_data, x, &y, w, height, &rw, 1);
658 /* Update the width to respond to the required minimum width */
659 if (dlg_data->dlg->layout.fit_datalen) {
660 int_lower_bound(&rw, dlg_data->dlg->widgets->datalen);
661 int_upper_bound(&w, rw);
662 } else if (!dlg_data->dlg->layout.maximize_width) {
663 w = rw;
666 draw_dialog(dlg_data, w, y);
668 y = dlg_data->box.y + DIALOG_TB + dlg_data->dlg->layout.padding_top;
669 x = dlg_data->box.x + DIALOG_LB;
671 format_widgets(term, dlg_data, x, &y, w, height, NULL, 0);
675 void
676 draw_dialog(struct dialog_data *dlg_data, int width, int height)
678 struct terminal *term = dlg_data->win->term;
679 int dlg_width = int_min(term->width, width + 2 * DIALOG_LB);
680 int dlg_height = int_min(term->height, height + 2 * DIALOG_TB);
682 set_box(&dlg_data->box,
683 (term->width - dlg_width) / 2, (term->height - dlg_height) / 2,
684 dlg_width, dlg_height);
686 draw_box(term, &dlg_data->box, ' ', 0,
687 get_bfu_color(term, "dialog.generic"));
689 if (get_opt_bool("ui.dialogs.shadows", NULL)) {
690 /* Draw shadow */
691 draw_shadow(term, &dlg_data->box,
692 get_bfu_color(term, "dialog.shadow"), 2, 1);
693 #ifdef CONFIG_UTF8
694 if (term->utf8_cp)
695 fix_dwchar_around_box(term, &dlg_data->box, 0, 2, 1);
696 #endif /* CONFIG_UTF8 */
698 #ifdef CONFIG_UTF8
699 else if (term->utf8_cp)
700 fix_dwchar_around_box(term, &dlg_data->box, 0, 0, 0);
701 #endif /* CONFIG_UTF8 */
704 /* Timer callback for @refresh->timer. As explained in @install_timer,
705 * this function must erase the expired timer ID from all variables. */
706 static void
707 do_refresh_dialog(struct dialog_data *dlg_data)
709 struct dialog_refresh *refresh = dlg_data->dlg->refresh;
710 enum dlg_refresh_code refresh_code;
712 assert(refresh && refresh->handler);
714 refresh_code = refresh->handler(dlg_data, refresh->data);
716 if (refresh_code == REFRESH_CANCEL
717 || refresh_code == REFRESH_STOP) {
718 refresh->timer = TIMER_ID_UNDEF;
719 if (refresh_code == REFRESH_CANCEL)
720 cancel_dialog(dlg_data, NULL);
721 return;
724 /* We want dialog_has_refresh() to be true while drawing
725 * so we can not set the timer to -1. */
726 if (refresh_code == REFRESH_DIALOG) {
727 redraw_dialog(dlg_data, 1);
730 install_timer(&refresh->timer, RESOURCE_INFO_REFRESH,
731 (void (*)(void *)) do_refresh_dialog, dlg_data);
732 /* The expired timer ID has now been erased. */
735 void
736 refresh_dialog(struct dialog_data *dlg_data, dialog_refresh_handler_T handler, void *data)
738 struct dialog_refresh *refresh = dlg_data->dlg->refresh;
740 if (!refresh) {
741 refresh = mem_calloc(1, sizeof(*refresh));
742 if (!refresh) return;
744 dlg_data->dlg->refresh = refresh;
746 } else {
747 kill_timer(&refresh->timer);
750 refresh->handler = handler;
751 refresh->data = data;
752 install_timer(&refresh->timer, RESOURCE_INFO_REFRESH,
753 (void (*)(void *)) do_refresh_dialog, dlg_data);