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
) {
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
;
104 titlecells
= utf8_ptr2cells(title
,
106 #endif /* CONFIG_UTF8 */
108 titlecells
= int_min(dlg_data
->real_box
.width
- 2, titlecells
);
112 titlelen
= utf8_cells2bytes(title
, titlecells
,
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_text(term
, x
- 1, y
, " ", 1, 0, title_color
);
121 draw_text(term
, x
, y
, title
, titlelen
, 0, title_color
);
122 draw_text(term
, x
+ titlecells
, y
, " ", 1, 0,
127 update_all_widgets(dlg_data
);
129 redraw_windows(REDRAW_IN_FRONT_OF_WINDOW
, dlg_data
->win
);
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
[] = {
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
) {
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
);
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
;
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_windows(REDRAW_WINDOW_AND_FRONT
, dlg_data
->win
);
213 display_widget(dlg_data
, previously_selected_widget
);
214 display_widget(dlg_data
, 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
)
226 widget_data
= &dlg_data
->widgets_data
[i
];
227 select_widget(dlg_data
, widget_data
);
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
);
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_windows(REDRAW_WINDOW_AND_FRONT
, dlg_data
->win
);
256 display_widget(dlg_data
, previously_selected_widget
);
257 display_widget(dlg_data
, selected_widget(dlg_data
));
258 redraw_windows(REDRAW_IN_FRONT_OF_WINDOW
, dlg_data
->win
);
262 dialog_ev_init(struct dialog_data
*dlg_data
)
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 */
275 && widget_is_focusable(widget_data
))
276 dlg_data
->selected_widget_id
= i
;
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
)
293 #endif /* CONFIG_MOUSE */
295 /* Look up for a button with matching flag. */
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
);
310 /* Look up for a button with matching starting letter. */
312 select_button_by_key(struct dialog_data
*dlg_data
)
314 term_event_char_T key
;
319 struct widget_data
*widget_data
;
320 struct term_event
*ev
= dlg_data
->term_event
;
322 if (!check_kbd_label_key(ev
)) return;
325 key
= unicode_fold_label_case(get_kbd_key(ev
));
326 codepage
= get_terminal_codepage(dlg_data
->win
->term
);
328 key
= toupper(get_kbd_key(ev
));
331 foreach_widget(dlg_data
, widget_data
) {
333 unsigned char *hk_ptr
;
334 term_event_char_T hk_char
;
336 if (widget_data
->widget
->type
!= WIDGET_BUTTON
)
339 hk_ptr
= widget_data
->widget
->text
;
343 /* We first try to match marked hotkey if there is
344 * one else we fallback to first character in button
346 hk_pos
= widget_data
->widget
->info
.button
.hotkey_pos
;
348 hk_ptr
+= hk_pos
+ 1;
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
);
359 hk_char
= toupper(*hk_ptr
);
362 if (hk_char
== key
) {
363 select_dlg_item(dlg_data
, widget_data
);
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
)
382 action_id
= kbd_action(KEYMAP_MENU
, ev
, NULL
);
384 case ACT_MENU_SELECT
:
387 ops
->select(dlg_data
, widget_data
);
393 ops
->select(dlg_data
, widget_data
);
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
);
403 case ACT_MENU_CANCEL
:
405 select_button_by_flag(dlg_data
, B_ESC
);
407 case ACT_MENU_NEXT_ITEM
:
411 cycle_widget_focus(dlg_data
, 1);
413 case ACT_MENU_PREVIOUS_ITEM
:
416 /* Cycle focus (reverse). */
417 cycle_widget_focus(dlg_data
, -1);
419 case ACT_MENU_REDRAW
:
420 redraw_terminal_cls(dlg_data
->win
->term
);
423 select_button_by_key(dlg_data
);
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
);
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
);
453 /* TODO: use EVENT_PROCESSED/EVENT_NOT_PROCESSED. */
455 dialog_func(struct window
*win
, struct term_event
*ev
)
457 struct dialog_data
*dlg_data
= win
->data
;
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
)) {
470 dialog_ev_init(dlg_data
);
474 redraw_dialog(dlg_data
, 1);
479 dialog_ev_mouse(dlg_data
);
484 dialog_ev_kbd(dlg_data
);
488 dialog_ev_abort(dlg_data
);
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
))
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);
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
)
530 memcpy(widget_data
->widget
->data
,
532 widget_data
->widget
->datalen
);
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
565 select_widget_by_id(dlg_data
, 0);
567 redraw_dialog(dlg_data
, 0);
568 return EVENT_PROCESSED
;
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
:
584 dlg_format_field(dlg_data
, wdata
, x
, y
, w
, rw
, ALIGN_LEFT
,
589 dlg_format_listbox(dlg_data
, wdata
, x
, y
, w
, h
, rw
,
590 ALIGN_LEFT
, format_only
);
594 dlg_format_text(dlg_data
, wdata
, x
, y
, w
, rw
, h
,
598 case WIDGET_CHECKBOX
:
600 int group
= widget_has_group(wdata
);
602 if (group
> 0 && dlg_data
->dlg
->layout
.float_groups
) {
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
))
613 dlg_format_group(dlg_data
, wdata
, size
, x
, y
, w
, rw
,
619 /* No horizontal space between checkboxes belonging to
621 dlg_format_checkbox(dlg_data
, wdata
, x
, y
, w
, rw
,
622 ALIGN_LEFT
, format_only
);
624 && group
== widget_has_group(&wdata
[1]))
630 /* We assume that the buttons are all stuffed at the very end
633 dlg_format_buttons(dlg_data
, wdata
, widgets
,
634 x
, y
, w
, rw
, ALIGN_CENTER
, format_only
);
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
);
650 rw
= int_min(w
, utf8_ptr2cells(dlg_data
->dlg
->title
, NULL
));
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
) {
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);
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
)) {
691 draw_shadow(term
, &dlg_data
->box
,
692 get_bfu_color(term
, "dialog.shadow"), 2, 1);
695 fix_dwchar_around_box(term
, &dlg_data
->box
, 0, 2, 1);
696 #endif /* 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. */
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
);
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. */
736 refresh_dialog(struct dialog_data
*dlg_data
, dialog_refresh_handler_T handler
, void *data
)
738 struct dialog_refresh
*refresh
= dlg_data
->dlg
->refresh
;
741 refresh
= mem_calloc(1, sizeof(*refresh
));
742 if (!refresh
) return;
744 dlg_data
->dlg
->refresh
= refresh
;
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
);