1 /* Dialog box implementation. */
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
;
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
);
41 /* Worry not: freeml() checks whether its argument is NULL. */
47 dlg_data
->number_of_widgets
= dlg
->number_of_widgets
;
49 add_window(term
, dialog_func
, dlg_data
);
54 static void cycle_widget_focus(struct dialog_data
*dlg_data
, int direction
);
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
);
69 redraw_dialog(struct dialog_data
*dlg_data
, int layout
)
71 struct terminal
*term
= dlg_data
->win
->term
;
72 struct color_pair
*title_color
;
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
) {
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
;
106 titlecells
= utf8_ptr2cells(title
,
108 #endif /* CONFIG_UTF_8 */
110 titlecells
= int_min(box
.width
- 2, titlecells
);
114 titlelen
= utf8_cells2bytes(title
, titlecells
,
116 #endif /* CONFIG_UTF_8 */
118 x
= (box
.width
- titlecells
) / 2 + box
.x
;
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,
129 update_all_widgets(dlg_data
);
131 redraw_from_window(dlg_data
->win
);
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
[] = {
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
) {
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
);
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
);
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
)
206 widget_data
= &dlg_data
->widgets_data
[i
];
207 select_widget(dlg_data
, widget_data
);
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
);
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
);
237 dialog_ev_init(struct dialog_data
*dlg_data
)
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 */
249 && widget_is_focusable(widget_data
))
250 dlg_data
->selected_widget_id
= i
;
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
)
267 #endif /* CONFIG_MOUSE */
269 /* Look up for a button with matching flag. */
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
);
284 /* Look up for a button with matching starting letter. */
286 select_button_by_key(struct dialog_data
*dlg_data
)
295 struct widget_data
*widget_data
;
296 struct term_event
*ev
= dlg_data
->term_event
;
298 if (!check_kbd_label_key(ev
)) return;
301 key
= unicode_fold_label_case(get_kbd_key(ev
));
302 codepage
= get_opt_codepage_tree(dlg_data
->win
->term
->spec
, "charset");
304 key
= toupper(get_kbd_key(ev
));
307 foreach_widget(dlg_data
, widget_data
) {
309 unsigned char *hk_ptr
;
311 unicode_val_T hk_char
;
313 unsigned char hk_char
;
316 if (widget_data
->widget
->type
!= WIDGET_BUTTON
)
319 /* We first try to match marked hotkey if there is
320 * one else we fallback to first character in button
322 hk_pos
= widget_data
->widget
->info
.button
.hotkey_pos
;
324 hk_ptr
= &widget_data
->widget
->text
[hk_pos
+ 1];
326 hk_ptr
= widget_data
->widget
->text
;
329 if (is_cp_utf8(codepage
))
330 hk_char
= utf_8_to_unicode(&hk_ptr
,
331 strchr(hk_ptr
, '\0'));
333 hk_char
= cp2u(codepage
, *hk_ptr
);
334 hk_char
= unicode_fold_label_case(hk_char
);
336 hk_char
= toupper(*hk_ptr
);
339 if (hk_char
== key
) {
340 select_dlg_item(dlg_data
, widget_data
);
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
)
359 action_id
= kbd_action(KEYMAP_MENU
, ev
, NULL
);
361 case ACT_MENU_SELECT
:
364 ops
->select(dlg_data
, widget_data
);
370 ops
->select(dlg_data
, widget_data
);
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
);
380 case ACT_MENU_CANCEL
:
382 select_button_by_flag(dlg_data
, B_ESC
);
384 case ACT_MENU_NEXT_ITEM
:
388 cycle_widget_focus(dlg_data
, 1);
390 case ACT_MENU_PREVIOUS_ITEM
:
393 /* Cycle focus (reverse). */
394 cycle_widget_focus(dlg_data
, -1);
396 case ACT_MENU_REDRAW
:
397 redraw_terminal_cls(dlg_data
->win
->term
);
400 select_button_by_key(dlg_data
);
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
);
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. */
431 dialog_func(struct window
*win
, struct term_event
*ev
)
433 struct dialog_data
*dlg_data
= win
->data
;
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
)) {
446 dialog_ev_init(dlg_data
);
450 redraw_dialog(dlg_data
, 1);
455 dialog_ev_mouse(dlg_data
);
460 dialog_ev_kbd(dlg_data
);
464 dialog_ev_abort(dlg_data
);
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
))
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);
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
)
505 memcpy(widget_data
->widget
->data
,
507 widget_data
->widget
->datalen
);
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
540 select_widget_by_id(dlg_data
, 0);
542 redraw_dialog(dlg_data
, 0);
543 return EVENT_PROCESSED
;
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
:
559 dlg_format_field(term
, wdata
, x
, y
, w
, rw
, ALIGN_LEFT
,
564 dlg_format_listbox(term
, wdata
, x
, y
, w
, h
, rw
,
565 ALIGN_LEFT
, format_only
);
569 dlg_format_text(term
, wdata
, x
, y
, w
, rw
, h
,
573 case WIDGET_CHECKBOX
:
575 int group
= widget_has_group(wdata
);
577 if (group
> 0 && dlg_data
->dlg
->layout
.float_groups
) {
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
))
588 dlg_format_group(term
, wdata
, size
, x
, y
, w
, rw
,
594 /* No horizontal space between checkboxes belonging to
596 dlg_format_checkbox(term
, wdata
, x
, y
, w
, rw
,
597 ALIGN_LEFT
, format_only
);
599 && group
== widget_has_group(&wdata
[1]))
605 /* We assume that the buttons are all stuffed at the very end
608 dlg_format_buttons(term
, wdata
, widgets
,
609 x
, y
, w
, rw
, ALIGN_CENTER
, format_only
);
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
);
625 rw
= int_min(w
, utf8_ptr2cells(dlg_data
->dlg
->title
, NULL
));
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
) {
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);
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")) {
666 draw_shadow(term
, &dlg_data
->box
,
667 get_bfu_color(term
, "dialog.shadow"), 2, 1);
670 fix_dwchar_around_box(term
, &dlg_data
->box
, 0, 2, 1);
671 #endif /* CONFIG_UTF_8 */
675 fix_dwchar_around_box(term
, &dlg_data
->box
, 0, 0, 0);
676 #endif /* CONFIG_UTF_8 */
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
);
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
);
708 refresh_dialog(struct dialog_data
*dlg_data
, dialog_refresh_handler_T handler
, void *data
)
710 struct dialog_refresh
*refresh
= dlg_data
->dlg
->refresh
;
713 refresh
= mem_calloc(1, sizeof(*refresh
));
714 if (!refresh
) return;
716 dlg_data
->dlg
->refresh
= refresh
;
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
);