1 /* Dialog box implementation. */
12 #include "bfu/dialog.h"
13 #include "config/kbdbind.h"
14 #include "config/options.h"
15 #include "intl/gettext/libintl.h"
16 #include "terminal/draw.h"
17 #include "main/timer.h"
18 #include "terminal/kbd.h"
19 #include "terminal/terminal.h"
20 #include "terminal/window.h"
21 #include "util/color.h"
22 #include "util/conv.h"
23 #include "util/error.h"
24 #include "util/memlist.h"
25 #include "util/memory.h"
26 #include "util/string.h"
29 static window_handler_T dialog_func
;
32 do_dialog(struct terminal
*term
, struct dialog
*dlg
,
33 struct memory_list
*ml
)
35 struct dialog_data
*dlg_data
;
37 dlg_data
= mem_calloc(1, sizeof(*dlg_data
) +
38 sizeof(struct widget_data
) * dlg
->number_of_widgets
);
40 /* Worry not: freeml() checks whether its argument is NULL. */
46 dlg_data
->number_of_widgets
= dlg
->number_of_widgets
;
48 add_window(term
, dialog_func
, dlg_data
);
53 static void cycle_widget_focus(struct dialog_data
*dlg_data
, int direction
);
56 update_all_widgets(struct dialog_data
*dlg_data
)
58 struct widget_data
*widget_data
;
60 /* Iterate backwards rather than forwards so that listboxes are drawn
61 * last, which means that they can grab the cursor. Yes, 'tis hacky. */
62 foreach_widget_back(dlg_data
, widget_data
) {
63 display_widget(dlg_data
, widget_data
);
68 redraw_dialog(struct dialog_data
*dlg_data
, int layout
)
70 struct terminal
*term
= dlg_data
->win
->term
;
71 struct color_pair
*title_color
;
74 dlg_data
->dlg
->layouter(dlg_data
);
75 /* This might not be the best place. We need to be able
76 * to make focusability of widgets dynamic so widgets
77 * like scrollable text don't receive focus when there
78 * is nothing to scroll. */
79 if (!widget_is_focusable(selected_widget(dlg_data
)))
80 cycle_widget_focus(dlg_data
, 1);
83 if (!dlg_data
->dlg
->layout
.only_widgets
) {
87 dlg_data
->box
.x
+ (DIALOG_LEFT_BORDER
+ 1),
88 dlg_data
->box
.y
+ (DIALOG_TOP_BORDER
+ 1),
89 dlg_data
->box
.width
- 2 * (DIALOG_LEFT_BORDER
+ 1),
90 dlg_data
->box
.height
- 2 * (DIALOG_TOP_BORDER
+ 1));
92 draw_border(term
, &box
, get_bfu_color(term
, "dialog.frame"), DIALOG_FRAME
);
94 assert(dlg_data
->dlg
->title
);
96 title_color
= get_bfu_color(term
, "dialog.title");
97 if (title_color
&& box
.width
> 2) {
98 unsigned char *title
= dlg_data
->dlg
->title
;
99 int titlelen
= int_min(box
.width
- 2, strlen(title
));
100 int x
= (box
.width
- titlelen
) / 2 + box
.x
;
103 draw_text(term
, x
- 1, y
, " ", 1, 0, title_color
);
104 draw_text(term
, x
, y
, title
, titlelen
, 0, title_color
);
105 draw_text(term
, x
+ titlelen
, y
, " ", 1, 0, title_color
);
109 update_all_widgets(dlg_data
);
111 redraw_from_window(dlg_data
->win
);
115 select_dlg_item(struct dialog_data
*dlg_data
, struct widget_data
*widget_data
)
117 select_widget(dlg_data
, widget_data
);
119 if (widget_data
->widget
->ops
->select
)
120 widget_data
->widget
->ops
->select(dlg_data
, widget_data
);
123 static struct widget_ops
*widget_type_to_ops
[] = {
132 static struct widget_data
*
133 init_widget(struct dialog_data
*dlg_data
, int i
)
135 struct widget_data
*widget_data
= &dlg_data
->widgets_data
[i
];
137 memset(widget_data
, 0, sizeof(*widget_data
));
138 widget_data
->widget
= &dlg_data
->dlg
->widgets
[i
];
140 if (widget_data
->widget
->datalen
) {
141 widget_data
->cdata
= mem_alloc(widget_data
->widget
->datalen
);
142 if (!widget_data
->cdata
) {
145 memcpy(widget_data
->cdata
,
146 widget_data
->widget
->data
,
147 widget_data
->widget
->datalen
);
150 widget_data
->widget
->ops
= widget_type_to_ops
[widget_data
->widget
->type
];
152 if (widget_has_history(widget_data
)) {
153 init_list(widget_data
->info
.field
.history
);
154 widget_data
->info
.field
.cur_hist
=
155 (struct input_history_entry
*) &widget_data
->info
.field
.history
;
158 if (widget_data
->widget
->ops
->init
)
159 widget_data
->widget
->ops
->init(dlg_data
, widget_data
);
165 select_widget(struct dialog_data
*dlg_data
, struct widget_data
*widget_data
)
167 struct widget_data
*previously_selected_widget
;
169 previously_selected_widget
= selected_widget(dlg_data
);
171 dlg_data
->selected_widget_id
= widget_data
- dlg_data
->widgets_data
;
173 display_widget(dlg_data
, previously_selected_widget
);
174 display_widget(dlg_data
, widget_data
);
179 select_widget_by_id(struct dialog_data
*dlg_data
, int i
)
181 struct widget_data
*widget_data
;
183 if (i
>= dlg_data
->number_of_widgets
)
186 widget_data
= &dlg_data
->widgets_data
[i
];
187 select_widget(dlg_data
, widget_data
);
193 cycle_widget_focus(struct dialog_data
*dlg_data
, int direction
)
195 int prev_selected
= dlg_data
->selected_widget_id
;
196 struct widget_data
*previously_selected_widget
;
198 previously_selected_widget
= selected_widget(dlg_data
);
201 dlg_data
->selected_widget_id
+= direction
;
203 if (dlg_data
->selected_widget_id
>= dlg_data
->number_of_widgets
)
204 dlg_data
->selected_widget_id
= 0;
205 else if (dlg_data
->selected_widget_id
< 0)
206 dlg_data
->selected_widget_id
= dlg_data
->number_of_widgets
- 1;
208 } while (!widget_is_focusable(selected_widget(dlg_data
))
209 && dlg_data
->selected_widget_id
!= prev_selected
);
211 display_widget(dlg_data
, previously_selected_widget
);
212 display_widget(dlg_data
, selected_widget(dlg_data
));
213 redraw_from_window(dlg_data
->win
);
217 dialog_ev_init(struct dialog_data
*dlg_data
)
221 /* TODO: foreachback_widget() */
222 for (i
= dlg_data
->number_of_widgets
- 1; i
>= 0; i
--) {
223 struct widget_data
*widget_data
;
225 widget_data
= init_widget(dlg_data
, i
);
227 /* Make sure the selected widget is focusable */
229 && widget_is_focusable(widget_data
))
230 dlg_data
->selected_widget_id
= i
;
236 dialog_ev_mouse(struct dialog_data
*dlg_data
)
238 struct widget_data
*widget_data
;
240 foreach_widget(dlg_data
, widget_data
) {
241 if (widget_data
->widget
->ops
->mouse
242 && widget_data
->widget
->ops
->mouse(dlg_data
, widget_data
)
247 #endif /* CONFIG_MOUSE */
249 /* Look up for a button with matching flag. */
251 select_button_by_flag(struct dialog_data
*dlg_data
, int flag
)
253 struct widget_data
*widget_data
;
255 foreach_widget(dlg_data
, widget_data
) {
256 if (widget_data
->widget
->type
== WIDGET_BUTTON
257 && widget_data
->widget
->info
.button
.flags
& flag
) {
258 select_dlg_item(dlg_data
, widget_data
);
264 /* Look up for a button with matching starting letter. */
266 select_button_by_key(struct dialog_data
*dlg_data
)
269 struct widget_data
*widget_data
;
270 struct term_event
*ev
= dlg_data
->term_event
;
272 if (!check_kbd_label_key(ev
)) return;
274 key
= toupper(get_kbd_key(ev
));
276 foreach_widget(dlg_data
, widget_data
) {
279 if (widget_data
->widget
->type
!= WIDGET_BUTTON
)
282 /* We first try to match marked hotkey if there is
283 * one else we fallback to first character in button
285 hk_pos
= widget_data
->widget
->info
.button
.hotkey_pos
;
287 if (toupper(widget_data
->widget
->text
[hk_pos
+ 1]) != key
)
290 if (toupper(widget_data
->widget
->text
[0]) != key
)
294 select_dlg_item(dlg_data
, widget_data
);
300 dialog_ev_kbd(struct dialog_data
*dlg_data
)
302 struct widget_data
*widget_data
= selected_widget(dlg_data
);
303 struct widget_ops
*ops
= widget_data
->widget
->ops
;
304 /* XXX: KEYMAP_EDIT ? --pasky */
305 enum menu_action action_id
;
306 struct term_event
*ev
= dlg_data
->term_event
;
308 /* First let the widget try out. */
309 if (ops
->kbd
&& ops
->kbd(dlg_data
, widget_data
) == EVENT_PROCESSED
)
312 action_id
= kbd_action(KEYMAP_MENU
, ev
, NULL
);
314 case ACT_MENU_SELECT
:
317 ops
->select(dlg_data
, widget_data
);
323 ops
->select(dlg_data
, widget_data
);
327 if (widget_is_textfield(widget_data
)
328 || check_kbd_modifier(ev
, KBD_MOD_CTRL
)
329 || check_kbd_modifier(ev
, KBD_MOD_ALT
)) {
330 select_button_by_flag(dlg_data
, B_ENTER
);
333 case ACT_MENU_CANCEL
:
335 select_button_by_flag(dlg_data
, B_ESC
);
337 case ACT_MENU_NEXT_ITEM
:
341 cycle_widget_focus(dlg_data
, 1);
343 case ACT_MENU_PREVIOUS_ITEM
:
346 /* Cycle focus (reverse). */
347 cycle_widget_focus(dlg_data
, -1);
349 case ACT_MENU_REDRAW
:
350 redraw_terminal_cls(dlg_data
->win
->term
);
353 select_button_by_key(dlg_data
);
359 dialog_ev_abort(struct dialog_data
*dlg_data
)
361 struct widget_data
*widget_data
;
363 if (dlg_data
->dlg
->refresh
) {
364 struct dialog_refresh
*refresh
= dlg_data
->dlg
->refresh
;
366 kill_timer(&refresh
->timer
);
370 if (dlg_data
->dlg
->abort
)
371 dlg_data
->dlg
->abort(dlg_data
);
373 foreach_widget(dlg_data
, widget_data
) {
374 mem_free_if(widget_data
->cdata
);
375 if (widget_has_history(widget_data
))
376 free_list(widget_data
->info
.field
.history
);
379 freeml(dlg_data
->ml
);
382 /* TODO: use EVENT_PROCESSED/EVENT_NOT_PROCESSED. */
384 dialog_func(struct window
*win
, struct term_event
*ev
)
386 struct dialog_data
*dlg_data
= win
->data
;
389 dlg_data
->term_event
= ev
;
391 /* Look whether user event handlers can help us.. */
392 if (dlg_data
->dlg
->handle_event
&&
393 (dlg_data
->dlg
->handle_event(dlg_data
) == EVENT_PROCESSED
)) {
399 dialog_ev_init(dlg_data
);
403 redraw_dialog(dlg_data
, 1);
408 dialog_ev_mouse(dlg_data
);
413 dialog_ev_kbd(dlg_data
);
417 dialog_ev_abort(dlg_data
);
423 check_dialog(struct dialog_data
*dlg_data
)
425 struct widget_data
*widget_data
;
427 foreach_widget(dlg_data
, widget_data
) {
428 if (widget_data
->widget
->type
!= WIDGET_CHECKBOX
&&
429 !widget_is_textfield(widget_data
))
432 if (widget_data
->widget
->handler
&&
433 widget_data
->widget
->handler(dlg_data
, widget_data
)) {
434 select_widget(dlg_data
, widget_data
);
435 redraw_dialog(dlg_data
, 0);
443 widget_handler_status_T
444 cancel_dialog(struct dialog_data
*dlg_data
, struct widget_data
*xxx
)
446 delete_window(dlg_data
->win
);
447 return EVENT_PROCESSED
;
451 update_dialog_data(struct dialog_data
*dlg_data
)
453 struct widget_data
*widget_data
;
455 foreach_widget(dlg_data
, widget_data
) {
456 if (!widget_data
->widget
->datalen
)
458 memcpy(widget_data
->widget
->data
,
460 widget_data
->widget
->datalen
);
466 widget_handler_status_T
467 ok_dialog(struct dialog_data
*dlg_data
, struct widget_data
*widget_data
)
469 done_handler_T
*done
= widget_data
->widget
->info
.button
.done
;
470 void *done_data
= widget_data
->widget
->info
.button
.done_data
;
472 if (check_dialog(dlg_data
)) return EVENT_NOT_PROCESSED
;
474 update_dialog_data(dlg_data
);
476 if (done
) done(done_data
);
477 return cancel_dialog(dlg_data
, widget_data
);
480 /* Clear dialog fields (if widget has clear callback). */
481 widget_handler_status_T
482 clear_dialog(struct dialog_data
*dlg_data
, struct widget_data
*xxx
)
484 struct widget_data
*widget_data
;
486 foreach_widget(dlg_data
, widget_data
) {
487 if (widget_data
->widget
->ops
->clear
)
488 widget_data
->widget
->ops
->clear(dlg_data
, widget_data
);
491 /* Move focus to the first widget. It helps with bookmark search dialog
493 select_widget_by_id(dlg_data
, 0);
495 redraw_dialog(dlg_data
, 0);
496 return EVENT_PROCESSED
;
501 format_widgets(struct terminal
*term
, struct dialog_data
*dlg_data
,
502 int x
, int *y
, int w
, int h
, int *rw
)
504 struct widget_data
*wdata
= dlg_data
->widgets_data
;
505 int widgets
= dlg_data
->number_of_widgets
;
507 /* TODO: Do something if (*y) gets > height. */
508 for (; widgets
> 0; widgets
--, wdata
++, (*y
)++) {
509 switch (wdata
->widget
->type
) {
510 case WIDGET_FIELD_PASS
:
512 dlg_format_field(term
, wdata
, x
, y
, w
, rw
, ALIGN_LEFT
);
516 dlg_format_listbox(term
, wdata
, x
, y
, w
, h
, rw
, ALIGN_LEFT
);
520 dlg_format_text(term
, wdata
, x
, y
, w
, rw
, h
);
523 case WIDGET_CHECKBOX
:
525 int group
= widget_has_group(wdata
);
527 if (group
> 0 && dlg_data
->dlg
->layout
.float_groups
) {
530 /* Find group size */
531 for (size
= 1; widgets
> 0; size
++, widgets
--) {
532 struct widget_data
*next
= &wdata
[size
];
534 if (group
!= widget_has_group(next
))
538 dlg_format_group(term
, wdata
, size
, x
, y
, w
, rw
);
543 /* No horizontal space between checkboxes belonging to
545 dlg_format_checkbox(term
, wdata
, x
, y
, w
, rw
, ALIGN_LEFT
);
547 && group
== widget_has_group(&wdata
[1]))
553 /* We assume that the buttons are all stuffed at the very end
556 dlg_format_buttons(term
, wdata
, widgets
,
557 x
, y
, w
, rw
, ALIGN_CENTER
);
564 generic_dialog_layouter(struct dialog_data
*dlg_data
)
566 struct terminal
*term
= dlg_data
->win
->term
;
567 int w
= dialog_max_width(term
);
568 int height
= dialog_max_height(term
);
569 int rw
= int_min(w
, strlen(dlg_data
->dlg
->title
));
570 int y
= dlg_data
->dlg
->layout
.padding_top
? 0 : -1;
573 format_widgets(NULL
, dlg_data
, x
, &y
, w
, height
, &rw
);
575 /* Update the width to respond to the required minimum width */
576 if (dlg_data
->dlg
->layout
.fit_datalen
) {
577 int_lower_bound(&rw
, dlg_data
->dlg
->widgets
->datalen
);
578 int_upper_bound(&w
, rw
);
579 } else if (!dlg_data
->dlg
->layout
.maximize_width
) {
583 draw_dialog(dlg_data
, w
, y
);
585 y
= dlg_data
->box
.y
+ DIALOG_TB
+ dlg_data
->dlg
->layout
.padding_top
;
586 x
= dlg_data
->box
.x
+ DIALOG_LB
;
588 format_widgets(term
, dlg_data
, x
, &y
, w
, height
, NULL
);
593 draw_dialog(struct dialog_data
*dlg_data
, int width
, int height
)
595 struct terminal
*term
= dlg_data
->win
->term
;
596 int dlg_width
= int_min(term
->width
, width
+ 2 * DIALOG_LB
);
597 int dlg_height
= int_min(term
->height
, height
+ 2 * DIALOG_TB
);
599 set_box(&dlg_data
->box
,
600 (term
->width
- dlg_width
) / 2, (term
->height
- dlg_height
) / 2,
601 dlg_width
, dlg_height
);
603 draw_box(term
, &dlg_data
->box
, ' ', 0,
604 get_bfu_color(term
, "dialog.generic"));
606 if (get_opt_bool("ui.dialogs.shadows")) {
608 draw_shadow(term
, &dlg_data
->box
,
609 get_bfu_color(term
, "dialog.shadow"), 2, 1);
614 do_refresh_dialog(struct dialog_data
*dlg_data
)
616 struct dialog_refresh
*refresh
= dlg_data
->dlg
->refresh
;
617 enum dlg_refresh_code refresh_code
;
619 assert(refresh
&& refresh
->handler
);
621 refresh_code
= refresh
->handler(dlg_data
, refresh
->data
);
623 if (refresh_code
== REFRESH_CANCEL
624 || refresh_code
== REFRESH_STOP
) {
625 refresh
->timer
= TIMER_ID_UNDEF
;
626 if (refresh_code
== REFRESH_CANCEL
)
627 cancel_dialog(dlg_data
, NULL
);
631 /* We want dialog_has_refresh() to be true while drawing
632 * so we can not set the timer to -1. */
633 if (refresh_code
== REFRESH_DIALOG
) {
634 redraw_dialog(dlg_data
, 1);
637 install_timer(&refresh
->timer
, RESOURCE_INFO_REFRESH
,
638 (void (*)(void *)) do_refresh_dialog
, dlg_data
);
642 refresh_dialog(struct dialog_data
*dlg_data
, dialog_refresh_handler_T handler
, void *data
)
644 struct dialog_refresh
*refresh
= dlg_data
->dlg
->refresh
;
647 refresh
= mem_calloc(1, sizeof(*refresh
));
648 if (!refresh
) return;
650 dlg_data
->dlg
->refresh
= refresh
;
653 kill_timer(&refresh
->timer
);
656 refresh
->handler
= handler
;
657 refresh
->data
= data
;
658 install_timer(&refresh
->timer
, RESOURCE_INFO_REFRESH
,
659 (void (*)(void *)) do_refresh_dialog
, dlg_data
);