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
)
288 term_event_char_T key
;
293 struct widget_data
*widget_data
;
294 struct term_event
*ev
= dlg_data
->term_event
;
296 if (!check_kbd_label_key(ev
)) return;
299 key
= unicode_fold_label_case(get_kbd_key(ev
));
300 codepage
= get_opt_codepage_tree(dlg_data
->win
->term
->spec
, "charset");
302 key
= toupper(get_kbd_key(ev
));
305 foreach_widget(dlg_data
, widget_data
) {
307 unsigned char *hk_ptr
;
308 term_event_char_T hk_char
;
310 if (widget_data
->widget
->type
!= WIDGET_BUTTON
)
313 /* We first try to match marked hotkey if there is
314 * one else we fallback to first character in button
316 hk_pos
= widget_data
->widget
->info
.button
.hotkey_pos
;
318 hk_ptr
= &widget_data
->widget
->text
[hk_pos
+ 1];
320 hk_ptr
= widget_data
->widget
->text
;
323 hk_char
= cp_to_unicode(codepage
, &hk_ptr
,
324 strchr(hk_ptr
, '\0'));
325 hk_char
= unicode_fold_label_case(hk_char
);
327 hk_char
= toupper(*hk_ptr
);
330 if (hk_char
== key
) {
331 select_dlg_item(dlg_data
, widget_data
);
338 dialog_ev_kbd(struct dialog_data
*dlg_data
)
340 struct widget_data
*widget_data
= selected_widget(dlg_data
);
341 struct widget_ops
*ops
= widget_data
->widget
->ops
;
342 /* XXX: KEYMAP_EDIT ? --pasky */
343 enum menu_action action_id
;
344 struct term_event
*ev
= dlg_data
->term_event
;
346 /* First let the widget try out. */
347 if (ops
->kbd
&& ops
->kbd(dlg_data
, widget_data
) == EVENT_PROCESSED
)
350 action_id
= kbd_action(KEYMAP_MENU
, ev
, NULL
);
352 case ACT_MENU_SELECT
:
355 ops
->select(dlg_data
, widget_data
);
361 ops
->select(dlg_data
, widget_data
);
365 if (widget_is_textfield(widget_data
)
366 || check_kbd_modifier(ev
, KBD_MOD_CTRL
)
367 || check_kbd_modifier(ev
, KBD_MOD_ALT
)) {
368 select_button_by_flag(dlg_data
, B_ENTER
);
371 case ACT_MENU_CANCEL
:
373 select_button_by_flag(dlg_data
, B_ESC
);
375 case ACT_MENU_NEXT_ITEM
:
379 cycle_widget_focus(dlg_data
, 1);
381 case ACT_MENU_PREVIOUS_ITEM
:
384 /* Cycle focus (reverse). */
385 cycle_widget_focus(dlg_data
, -1);
387 case ACT_MENU_REDRAW
:
388 redraw_terminal_cls(dlg_data
->win
->term
);
391 select_button_by_key(dlg_data
);
397 dialog_ev_abort(struct dialog_data
*dlg_data
)
399 struct widget_data
*widget_data
;
401 if (dlg_data
->dlg
->refresh
) {
402 struct dialog_refresh
*refresh
= dlg_data
->dlg
->refresh
;
404 kill_timer(&refresh
->timer
);
408 if (dlg_data
->dlg
->abort
)
409 dlg_data
->dlg
->abort(dlg_data
);
411 foreach_widget(dlg_data
, widget_data
) {
412 mem_free_if(widget_data
->cdata
);
413 if (widget_has_history(widget_data
))
414 free_list(widget_data
->info
.field
.history
);
417 freeml(dlg_data
->ml
);
420 /* TODO: use EVENT_PROCESSED/EVENT_NOT_PROCESSED. */
422 dialog_func(struct window
*win
, struct term_event
*ev
)
424 struct dialog_data
*dlg_data
= win
->data
;
427 dlg_data
->term_event
= ev
;
429 /* Look whether user event handlers can help us.. */
430 if (dlg_data
->dlg
->handle_event
&&
431 (dlg_data
->dlg
->handle_event(dlg_data
) == EVENT_PROCESSED
)) {
437 dialog_ev_init(dlg_data
);
441 redraw_dialog(dlg_data
, 1);
446 dialog_ev_mouse(dlg_data
);
451 dialog_ev_kbd(dlg_data
);
455 dialog_ev_abort(dlg_data
);
461 check_dialog(struct dialog_data
*dlg_data
)
463 struct widget_data
*widget_data
;
465 foreach_widget(dlg_data
, widget_data
) {
466 if (widget_data
->widget
->type
!= WIDGET_CHECKBOX
&&
467 !widget_is_textfield(widget_data
))
470 if (widget_data
->widget
->handler
&&
471 widget_data
->widget
->handler(dlg_data
, widget_data
)) {
472 select_widget(dlg_data
, widget_data
);
473 redraw_dialog(dlg_data
, 0);
481 widget_handler_status_T
482 cancel_dialog(struct dialog_data
*dlg_data
, struct widget_data
*xxx
)
484 delete_window(dlg_data
->win
);
485 return EVENT_PROCESSED
;
489 update_dialog_data(struct dialog_data
*dlg_data
)
491 struct widget_data
*widget_data
;
493 foreach_widget(dlg_data
, widget_data
) {
494 if (!widget_data
->widget
->datalen
)
496 memcpy(widget_data
->widget
->data
,
498 widget_data
->widget
->datalen
);
504 widget_handler_status_T
505 ok_dialog(struct dialog_data
*dlg_data
, struct widget_data
*widget_data
)
507 done_handler_T
*done
= widget_data
->widget
->info
.button
.done
;
508 void *done_data
= widget_data
->widget
->info
.button
.done_data
;
510 if (check_dialog(dlg_data
)) return EVENT_NOT_PROCESSED
;
512 update_dialog_data(dlg_data
);
514 if (done
) done(done_data
);
515 return cancel_dialog(dlg_data
, widget_data
);
518 /* Clear dialog fields (if widget has clear callback). */
519 widget_handler_status_T
520 clear_dialog(struct dialog_data
*dlg_data
, struct widget_data
*xxx
)
522 struct widget_data
*widget_data
;
524 foreach_widget(dlg_data
, widget_data
) {
525 if (widget_data
->widget
->ops
->clear
)
526 widget_data
->widget
->ops
->clear(dlg_data
, widget_data
);
529 /* Move focus to the first widget. It helps with bookmark search dialog
531 select_widget_by_id(dlg_data
, 0);
533 redraw_dialog(dlg_data
, 0);
534 return EVENT_PROCESSED
;
539 format_widgets(struct terminal
*term
, struct dialog_data
*dlg_data
,
540 int x
, int *y
, int w
, int h
, int *rw
, int format_only
)
542 struct widget_data
*wdata
= dlg_data
->widgets_data
;
543 int widgets
= dlg_data
->number_of_widgets
;
545 /* TODO: Do something if (*y) gets > height. */
546 for (; widgets
> 0; widgets
--, wdata
++, (*y
)++) {
547 switch (wdata
->widget
->type
) {
548 case WIDGET_FIELD_PASS
:
550 dlg_format_field(term
, wdata
, x
, y
, w
, rw
, ALIGN_LEFT
,
555 dlg_format_listbox(term
, wdata
, x
, y
, w
, h
, rw
,
556 ALIGN_LEFT
, format_only
);
560 dlg_format_text(term
, wdata
, x
, y
, w
, rw
, h
,
564 case WIDGET_CHECKBOX
:
566 int group
= widget_has_group(wdata
);
568 if (group
> 0 && dlg_data
->dlg
->layout
.float_groups
) {
571 /* Find group size */
572 for (size
= 1; widgets
> 0; size
++, widgets
--) {
573 struct widget_data
*next
= &wdata
[size
];
575 if (group
!= widget_has_group(next
))
579 dlg_format_group(term
, wdata
, size
, x
, y
, w
, rw
,
585 /* No horizontal space between checkboxes belonging to
587 dlg_format_checkbox(term
, wdata
, x
, y
, w
, rw
,
588 ALIGN_LEFT
, format_only
);
590 && group
== widget_has_group(&wdata
[1]))
596 /* We assume that the buttons are all stuffed at the very end
599 dlg_format_buttons(term
, wdata
, widgets
,
600 x
, y
, w
, rw
, ALIGN_CENTER
, format_only
);
607 generic_dialog_layouter(struct dialog_data
*dlg_data
)
609 struct terminal
*term
= dlg_data
->win
->term
;
610 int w
= dialog_max_width(term
);
611 int height
= dialog_max_height(term
);
616 rw
= int_min(w
, utf8_ptr2cells(dlg_data
->dlg
->title
, NULL
));
618 #endif /* CONFIG_UTF_8 */
619 rw
= int_min(w
, strlen(dlg_data
->dlg
->title
));
620 y
= dlg_data
->dlg
->layout
.padding_top
? 0 : -1;
622 format_widgets(term
, dlg_data
, x
, &y
, w
, height
, &rw
, 1);
624 /* Update the width to respond to the required minimum width */
625 if (dlg_data
->dlg
->layout
.fit_datalen
) {
626 int_lower_bound(&rw
, dlg_data
->dlg
->widgets
->datalen
);
627 int_upper_bound(&w
, rw
);
628 } else if (!dlg_data
->dlg
->layout
.maximize_width
) {
632 draw_dialog(dlg_data
, w
, y
);
634 y
= dlg_data
->box
.y
+ DIALOG_TB
+ dlg_data
->dlg
->layout
.padding_top
;
635 x
= dlg_data
->box
.x
+ DIALOG_LB
;
637 format_widgets(term
, dlg_data
, x
, &y
, w
, height
, NULL
, 0);
642 draw_dialog(struct dialog_data
*dlg_data
, int width
, int height
)
644 struct terminal
*term
= dlg_data
->win
->term
;
645 int dlg_width
= int_min(term
->width
, width
+ 2 * DIALOG_LB
);
646 int dlg_height
= int_min(term
->height
, height
+ 2 * DIALOG_TB
);
648 set_box(&dlg_data
->box
,
649 (term
->width
- dlg_width
) / 2, (term
->height
- dlg_height
) / 2,
650 dlg_width
, dlg_height
);
652 draw_box(term
, &dlg_data
->box
, ' ', 0,
653 get_bfu_color(term
, "dialog.generic"));
655 if (get_opt_bool("ui.dialogs.shadows")) {
657 draw_shadow(term
, &dlg_data
->box
,
658 get_bfu_color(term
, "dialog.shadow"), 2, 1);
661 fix_dwchar_around_box(term
, &dlg_data
->box
, 0, 2, 1);
662 #endif /* CONFIG_UTF_8 */
666 fix_dwchar_around_box(term
, &dlg_data
->box
, 0, 0, 0);
667 #endif /* CONFIG_UTF_8 */
671 do_refresh_dialog(struct dialog_data
*dlg_data
)
673 struct dialog_refresh
*refresh
= dlg_data
->dlg
->refresh
;
674 enum dlg_refresh_code refresh_code
;
676 assert(refresh
&& refresh
->handler
);
678 refresh_code
= refresh
->handler(dlg_data
, refresh
->data
);
680 if (refresh_code
== REFRESH_CANCEL
681 || refresh_code
== REFRESH_STOP
) {
682 refresh
->timer
= TIMER_ID_UNDEF
;
683 if (refresh_code
== REFRESH_CANCEL
)
684 cancel_dialog(dlg_data
, NULL
);
688 /* We want dialog_has_refresh() to be true while drawing
689 * so we can not set the timer to -1. */
690 if (refresh_code
== REFRESH_DIALOG
) {
691 redraw_dialog(dlg_data
, 1);
694 install_timer(&refresh
->timer
, RESOURCE_INFO_REFRESH
,
695 (void (*)(void *)) do_refresh_dialog
, dlg_data
);
699 refresh_dialog(struct dialog_data
*dlg_data
, dialog_refresh_handler_T handler
, void *data
)
701 struct dialog_refresh
*refresh
= dlg_data
->dlg
->refresh
;
704 refresh
= mem_calloc(1, sizeof(*refresh
));
705 if (!refresh
) return;
707 dlg_data
->dlg
->refresh
= refresh
;
710 kill_timer(&refresh
->timer
);
713 refresh
->handler
= handler
;
714 refresh
->data
= data
;
715 install_timer(&refresh
->timer
, RESOURCE_INFO_REFRESH
,
716 (void (*)(void *)) do_refresh_dialog
, dlg_data
);