elinks-0.11.0
[elinks/images.git] / src / bfu / dialog.c
blob8671c81793030acb6515b11f94353c010bd9ac86
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/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;
31 struct dialog_data *
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);
39 if (!dlg_data) {
40 /* Worry not: freeml() checks whether its argument is NULL. */
41 freeml(ml);
42 return NULL;
45 dlg_data->dlg = dlg;
46 dlg_data->number_of_widgets = dlg->number_of_widgets;
47 dlg_data->ml = ml;
48 add_window(term, dialog_func, dlg_data);
50 return dlg_data;
53 static void cycle_widget_focus(struct dialog_data *dlg_data, int direction);
55 static void
56 update_all_widgets(struct dialog_data *dlg_data)
58 struct widget_data *widget_data;
60 foreach_widget(dlg_data, widget_data) {
61 display_widget(dlg_data, widget_data);
65 void
66 redraw_dialog(struct dialog_data *dlg_data, int layout)
68 struct terminal *term = dlg_data->win->term;
69 struct color_pair *title_color;
71 if (layout) {
72 dlg_data->dlg->layouter(dlg_data);
73 /* This might not be the best place. We need to be able
74 * to make focusability of widgets dynamic so widgets
75 * like scrollable text don't receive focus when there
76 * is nothing to scroll. */
77 if (!widget_is_focusable(selected_widget(dlg_data)))
78 cycle_widget_focus(dlg_data, 1);
81 if (!dlg_data->dlg->layout.only_widgets) {
82 struct box box;
84 set_box(&box,
85 dlg_data->box.x + (DIALOG_LEFT_BORDER + 1),
86 dlg_data->box.y + (DIALOG_TOP_BORDER + 1),
87 dlg_data->box.width - 2 * (DIALOG_LEFT_BORDER + 1),
88 dlg_data->box.height - 2 * (DIALOG_TOP_BORDER + 1));
90 draw_border(term, &box, get_bfu_color(term, "dialog.frame"), DIALOG_FRAME);
92 assert(dlg_data->dlg->title);
94 title_color = get_bfu_color(term, "dialog.title");
95 if (title_color && box.width > 2) {
96 unsigned char *title = dlg_data->dlg->title;
97 int titlelen = int_min(box.width - 2, strlen(title));
98 int x = (box.width - titlelen) / 2 + box.x;
99 int y = box.y - 1;
101 draw_text(term, x - 1, y, " ", 1, 0, title_color);
102 draw_text(term, x, y, title, titlelen, 0, title_color);
103 draw_text(term, x + titlelen, y, " ", 1, 0, title_color);
107 update_all_widgets(dlg_data);
109 redraw_from_window(dlg_data->win);
112 static void
113 select_dlg_item(struct dialog_data *dlg_data, struct widget_data *widget_data)
115 select_widget(dlg_data, widget_data);
117 if (widget_data->widget->ops->select)
118 widget_data->widget->ops->select(dlg_data, widget_data);
121 static struct widget_ops *widget_type_to_ops[] = {
122 &checkbox_ops,
123 &field_ops,
124 &field_pass_ops,
125 &button_ops,
126 &listbox_ops,
127 &text_ops,
130 static struct widget_data *
131 init_widget(struct dialog_data *dlg_data, int i)
133 struct widget_data *widget_data = &dlg_data->widgets_data[i];
135 memset(widget_data, 0, sizeof(*widget_data));
136 widget_data->widget = &dlg_data->dlg->widgets[i];
138 if (widget_data->widget->datalen) {
139 widget_data->cdata = mem_alloc(widget_data->widget->datalen);
140 if (!widget_data->cdata) {
141 return NULL;
143 memcpy(widget_data->cdata,
144 widget_data->widget->data,
145 widget_data->widget->datalen);
148 widget_data->widget->ops = widget_type_to_ops[widget_data->widget->type];
150 if (widget_has_history(widget_data)) {
151 init_list(widget_data->info.field.history);
152 widget_data->info.field.cur_hist =
153 (struct input_history_entry *) &widget_data->info.field.history;
156 if (widget_data->widget->ops->init)
157 widget_data->widget->ops->init(dlg_data, widget_data);
159 return widget_data;
162 void
163 select_widget(struct dialog_data *dlg_data, struct widget_data *widget_data)
165 struct widget_data *previously_selected_widget;
167 previously_selected_widget = selected_widget(dlg_data);
169 dlg_data->selected_widget_id = widget_data - dlg_data->widgets_data;
171 display_widget(dlg_data, previously_selected_widget);
172 display_widget(dlg_data, widget_data);
176 struct widget_data *
177 select_widget_by_id(struct dialog_data *dlg_data, int i)
179 struct widget_data *widget_data;
181 if (i >= dlg_data->number_of_widgets)
182 return NULL;
184 widget_data = &dlg_data->widgets_data[i];
185 select_widget(dlg_data, widget_data);
187 return widget_data;
190 static void
191 cycle_widget_focus(struct dialog_data *dlg_data, int direction)
193 int prev_selected = dlg_data->selected_widget_id;
194 struct widget_data *previously_selected_widget;
196 previously_selected_widget = selected_widget(dlg_data);
198 do {
199 dlg_data->selected_widget_id += direction;
201 if (dlg_data->selected_widget_id >= dlg_data->number_of_widgets)
202 dlg_data->selected_widget_id = 0;
203 else if (dlg_data->selected_widget_id < 0)
204 dlg_data->selected_widget_id = dlg_data->number_of_widgets - 1;
206 } while (!widget_is_focusable(selected_widget(dlg_data))
207 && dlg_data->selected_widget_id != prev_selected);
209 display_widget(dlg_data, previously_selected_widget);
210 display_widget(dlg_data, selected_widget(dlg_data));
211 redraw_from_window(dlg_data->win);
214 static void
215 dialog_ev_init(struct dialog_data *dlg_data)
217 int i;
219 /* TODO: foreachback_widget() */
220 for (i = dlg_data->number_of_widgets - 1; i >= 0; i--) {
221 struct widget_data *widget_data;
223 widget_data = init_widget(dlg_data, i);
225 /* Make sure the selected widget is focusable */
226 if (widget_data
227 && widget_is_focusable(widget_data))
228 dlg_data->selected_widget_id = i;
232 #ifdef CONFIG_MOUSE
233 static void
234 dialog_ev_mouse(struct dialog_data *dlg_data)
236 struct widget_data *widget_data;
238 foreach_widget(dlg_data, widget_data) {
239 if (widget_data->widget->ops->mouse
240 && widget_data->widget->ops->mouse(dlg_data, widget_data)
241 == EVENT_PROCESSED)
242 break;
245 #endif /* CONFIG_MOUSE */
247 /* Look up for a button with matching flag. */
248 static void
249 select_button_by_flag(struct dialog_data *dlg_data, int flag)
251 struct widget_data *widget_data;
253 foreach_widget(dlg_data, widget_data) {
254 if (widget_data->widget->type == WIDGET_BUTTON
255 && widget_data->widget->info.button.flags & flag) {
256 select_dlg_item(dlg_data, widget_data);
257 break;
262 /* Look up for a button with matching starting letter. */
263 static void
264 select_button_by_key(struct dialog_data *dlg_data)
266 unsigned char key;
267 struct widget_data *widget_data;
268 struct term_event *ev = dlg_data->term_event;
270 if (!check_kbd_label_key(ev)) return;
272 key = toupper(get_kbd_key(ev));
274 foreach_widget(dlg_data, widget_data) {
275 int hk_pos;
277 if (widget_data->widget->type != WIDGET_BUTTON)
278 continue;
280 /* We first try to match marked hotkey if there is
281 * one else we fallback to first character in button
282 * name. */
283 hk_pos = widget_data->widget->info.button.hotkey_pos;
284 if (hk_pos >= 0) {
285 if (toupper(widget_data->widget->text[hk_pos + 1]) != key)
286 continue;
287 } else {
288 if (toupper(widget_data->widget->text[0]) != key)
289 continue;
292 select_dlg_item(dlg_data, widget_data);
293 break;
297 static void
298 dialog_ev_kbd(struct dialog_data *dlg_data)
300 struct widget_data *widget_data = selected_widget(dlg_data);
301 struct widget_ops *ops = widget_data->widget->ops;
302 /* XXX: KEYMAP_EDIT ? --pasky */
303 enum menu_action action_id;
304 struct term_event *ev = dlg_data->term_event;
306 /* First let the widget try out. */
307 if (ops->kbd && ops->kbd(dlg_data, widget_data) == EVENT_PROCESSED)
308 return;
310 action_id = kbd_action(KEYMAP_MENU, ev, NULL);
311 switch (action_id) {
312 case ACT_MENU_SELECT:
313 /* Can we select? */
314 if (ops->select) {
315 ops->select(dlg_data, widget_data);
317 break;
318 case ACT_MENU_ENTER:
319 /* Submit button. */
320 if (ops->select) {
321 ops->select(dlg_data, widget_data);
322 break;
325 if (widget_is_textfield(widget_data)
326 || check_kbd_modifier(ev, KBD_MOD_CTRL)
327 || check_kbd_modifier(ev, KBD_MOD_ALT)) {
328 select_button_by_flag(dlg_data, B_ENTER);
330 break;
331 case ACT_MENU_CANCEL:
332 /* Cancel button. */
333 select_button_by_flag(dlg_data, B_ESC);
334 break;
335 case ACT_MENU_NEXT_ITEM:
336 case ACT_MENU_DOWN:
337 case ACT_MENU_RIGHT:
338 /* Cycle focus. */
339 cycle_widget_focus(dlg_data, 1);
340 break;
341 case ACT_MENU_PREVIOUS_ITEM:
342 case ACT_MENU_UP:
343 case ACT_MENU_LEFT:
344 /* Cycle focus (reverse). */
345 cycle_widget_focus(dlg_data, -1);
346 break;
347 case ACT_MENU_REDRAW:
348 redraw_terminal_cls(dlg_data->win->term);
349 break;
350 default:
351 select_button_by_key(dlg_data);
352 break;
356 static void
357 dialog_ev_abort(struct dialog_data *dlg_data)
359 struct widget_data *widget_data;
361 if (dlg_data->dlg->refresh) {
362 struct dialog_refresh *refresh = dlg_data->dlg->refresh;
364 kill_timer(&refresh->timer);
365 mem_free(refresh);
368 if (dlg_data->dlg->abort)
369 dlg_data->dlg->abort(dlg_data);
371 foreach_widget(dlg_data, widget_data) {
372 mem_free_if(widget_data->cdata);
373 if (widget_has_history(widget_data))
374 free_list(widget_data->info.field.history);
377 freeml(dlg_data->ml);
380 /* TODO: use EVENT_PROCESSED/EVENT_NOT_PROCESSED. */
381 static void
382 dialog_func(struct window *win, struct term_event *ev)
384 struct dialog_data *dlg_data = win->data;
386 dlg_data->win = win;
387 dlg_data->term_event = ev;
389 /* Look whether user event handlers can help us.. */
390 if (dlg_data->dlg->handle_event &&
391 (dlg_data->dlg->handle_event(dlg_data) == EVENT_PROCESSED)) {
392 return;
395 switch (ev->ev) {
396 case EVENT_INIT:
397 dialog_ev_init(dlg_data);
398 /* fallback */
399 case EVENT_RESIZE:
400 case EVENT_REDRAW:
401 redraw_dialog(dlg_data, 1);
402 break;
404 case EVENT_MOUSE:
405 #ifdef CONFIG_MOUSE
406 dialog_ev_mouse(dlg_data);
407 #endif
408 break;
410 case EVENT_KBD:
411 dialog_ev_kbd(dlg_data);
412 break;
414 case EVENT_ABORT:
415 dialog_ev_abort(dlg_data);
416 break;
421 check_dialog(struct dialog_data *dlg_data)
423 struct widget_data *widget_data;
425 foreach_widget(dlg_data, widget_data) {
426 if (widget_data->widget->type != WIDGET_CHECKBOX &&
427 !widget_is_textfield(widget_data))
428 continue;
430 if (widget_data->widget->handler &&
431 widget_data->widget->handler(dlg_data, widget_data)) {
432 select_widget(dlg_data, widget_data);
433 redraw_dialog(dlg_data, 0);
434 return 1;
438 return 0;
441 widget_handler_status_T
442 cancel_dialog(struct dialog_data *dlg_data, struct widget_data *xxx)
444 delete_window(dlg_data->win);
445 return EVENT_PROCESSED;
449 update_dialog_data(struct dialog_data *dlg_data)
451 struct widget_data *widget_data;
453 foreach_widget(dlg_data, widget_data) {
454 if (!widget_data->widget->datalen)
455 continue;
456 memcpy(widget_data->widget->data,
457 widget_data->cdata,
458 widget_data->widget->datalen);
461 return 0;
464 widget_handler_status_T
465 ok_dialog(struct dialog_data *dlg_data, struct widget_data *widget_data)
467 done_handler_T *done = widget_data->widget->info.button.done;
468 void *done_data = widget_data->widget->info.button.done_data;
470 if (check_dialog(dlg_data)) return EVENT_NOT_PROCESSED;
472 update_dialog_data(dlg_data);
474 if (done) done(done_data);
475 return cancel_dialog(dlg_data, widget_data);
478 /* Clear dialog fields (if widget has clear callback). */
479 widget_handler_status_T
480 clear_dialog(struct dialog_data *dlg_data, struct widget_data *xxx)
482 struct widget_data *widget_data;
484 foreach_widget(dlg_data, widget_data) {
485 if (widget_data->widget->ops->clear)
486 widget_data->widget->ops->clear(dlg_data, widget_data);
489 /* Move focus to the first widget. It helps with bookmark search dialog
490 * (and others). */
491 select_widget_by_id(dlg_data, 0);
493 redraw_dialog(dlg_data, 0);
494 return EVENT_PROCESSED;
498 static void
499 format_widgets(struct terminal *term, struct dialog_data *dlg_data,
500 int x, int *y, int w, int h, int *rw)
502 struct widget_data *wdata = dlg_data->widgets_data;
503 int widgets = dlg_data->number_of_widgets;
505 /* TODO: Do something if (*y) gets > height. */
506 for (; widgets > 0; widgets--, wdata++, (*y)++) {
507 switch (wdata->widget->type) {
508 case WIDGET_FIELD_PASS:
509 case WIDGET_FIELD:
510 dlg_format_field(term, wdata, x, y, w, rw, ALIGN_LEFT);
511 break;
513 case WIDGET_LISTBOX:
514 dlg_format_listbox(term, wdata, x, y, w, h, rw, ALIGN_LEFT);
515 break;
517 case WIDGET_TEXT:
518 dlg_format_text(term, wdata, x, y, w, rw, h);
519 break;
521 case WIDGET_CHECKBOX:
523 int group = widget_has_group(wdata);
525 if (group > 0 && dlg_data->dlg->layout.float_groups) {
526 int size;
528 /* Find group size */
529 for (size = 1; widgets > 0; size++, widgets--) {
530 struct widget_data *next = &wdata[size];
532 if (group != widget_has_group(next))
533 break;
536 dlg_format_group(term, wdata, size, x, y, w, rw);
537 wdata += size - 1;
539 } else {
541 /* No horizontal space between checkboxes belonging to
542 * the same group. */
543 dlg_format_checkbox(term, wdata, x, y, w, rw, ALIGN_LEFT);
544 if (widgets > 1
545 && group == widget_has_group(&wdata[1]))
546 (*y)--;
549 break;
551 /* We assume that the buttons are all stuffed at the very end
552 * of the dialog. */
553 case WIDGET_BUTTON:
554 dlg_format_buttons(term, wdata, widgets,
555 x, y, w, rw, ALIGN_CENTER);
556 return;
561 void
562 generic_dialog_layouter(struct dialog_data *dlg_data)
564 struct terminal *term = dlg_data->win->term;
565 int w = dialog_max_width(term);
566 int height = dialog_max_height(term);
567 int rw = int_min(w, strlen(dlg_data->dlg->title));
568 int y = dlg_data->dlg->layout.padding_top ? 0 : -1;
569 int x = 0;
571 format_widgets(NULL, dlg_data, x, &y, w, height, &rw);
573 /* Update the width to respond to the required minimum width */
574 if (dlg_data->dlg->layout.fit_datalen) {
575 int_lower_bound(&rw, dlg_data->dlg->widgets->datalen);
576 int_upper_bound(&w, rw);
577 } else if (!dlg_data->dlg->layout.maximize_width) {
578 w = rw;
581 draw_dialog(dlg_data, w, y);
583 y = dlg_data->box.y + DIALOG_TB + dlg_data->dlg->layout.padding_top;
584 x = dlg_data->box.x + DIALOG_LB;
586 format_widgets(term, dlg_data, x, &y, w, height, NULL);
590 void
591 draw_dialog(struct dialog_data *dlg_data, int width, int height)
593 struct terminal *term = dlg_data->win->term;
594 int dlg_width = int_min(term->width, width + 2 * DIALOG_LB);
595 int dlg_height = int_min(term->height, height + 2 * DIALOG_TB);
597 set_box(&dlg_data->box,
598 (term->width - dlg_width) / 2, (term->height - dlg_height) / 2,
599 dlg_width, dlg_height);
601 draw_box(term, &dlg_data->box, ' ', 0,
602 get_bfu_color(term, "dialog.generic"));
604 if (get_opt_bool("ui.dialogs.shadows")) {
605 /* Draw shadow */
606 draw_shadow(term, &dlg_data->box,
607 get_bfu_color(term, "dialog.shadow"), 2, 1);
611 static void
612 do_refresh_dialog(struct dialog_data *dlg_data)
614 struct dialog_refresh *refresh = dlg_data->dlg->refresh;
615 enum dlg_refresh_code refresh_code;
617 assert(refresh && refresh->handler);
619 refresh_code = refresh->handler(dlg_data, refresh->data);
621 if (refresh_code == REFRESH_CANCEL
622 || refresh_code == REFRESH_STOP) {
623 refresh->timer = TIMER_ID_UNDEF;
624 if (refresh_code == REFRESH_CANCEL)
625 cancel_dialog(dlg_data, NULL);
626 return;
629 /* We want dialog_has_refresh() to be true while drawing
630 * so we can not set the timer to -1. */
631 if (refresh_code == REFRESH_DIALOG) {
632 redraw_dialog(dlg_data, 1);
635 install_timer(&refresh->timer, RESOURCE_INFO_REFRESH,
636 (void (*)(void *)) do_refresh_dialog, dlg_data);
639 void
640 refresh_dialog(struct dialog_data *dlg_data, dialog_refresh_handler_T handler, void *data)
642 struct dialog_refresh *refresh = dlg_data->dlg->refresh;
644 if (!refresh) {
645 refresh = mem_calloc(1, sizeof(*refresh));
646 if (!refresh) return;
648 dlg_data->dlg->refresh = refresh;
650 } else {
651 kill_timer(&refresh->timer);
654 refresh->handler = handler;
655 refresh->data = data;
656 install_timer(&refresh->timer, RESOURCE_INFO_REFRESH,
657 (void (*)(void *)) do_refresh_dialog, dlg_data);