grafthistory: comment about downloading the pack index
[elinks/elinks-j605.git] / src / bfu / dialog.c
blob042a172ccdd918e24ea806833b3a15eb154c21fb
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 /* 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);
67 void
68 redraw_dialog(struct dialog_data *dlg_data, int layout)
70 struct terminal *term = dlg_data->win->term;
71 struct color_pair *title_color;
73 if (layout) {
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) {
84 struct box box;
86 set_box(&box,
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;
101 int y = box.y - 1;
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);
114 static void
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[] = {
124 &checkbox_ops,
125 &field_ops,
126 &field_pass_ops,
127 &button_ops,
128 &listbox_ops,
129 &text_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) {
143 return NULL;
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);
161 return widget_data;
164 void
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);
178 struct 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)
184 return NULL;
186 widget_data = &dlg_data->widgets_data[i];
187 select_widget(dlg_data, widget_data);
189 return widget_data;
192 static void
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);
200 do {
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);
216 static void
217 dialog_ev_init(struct dialog_data *dlg_data)
219 int i;
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 */
228 if (widget_data
229 && widget_is_focusable(widget_data))
230 dlg_data->selected_widget_id = i;
234 #ifdef CONFIG_MOUSE
235 static void
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)
243 == EVENT_PROCESSED)
244 break;
247 #endif /* CONFIG_MOUSE */
249 /* Look up for a button with matching flag. */
250 static void
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);
259 break;
264 /* Look up for a button with matching starting letter. */
265 static void
266 select_button_by_key(struct dialog_data *dlg_data)
268 unsigned char key;
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) {
277 int hk_pos;
279 if (widget_data->widget->type != WIDGET_BUTTON)
280 continue;
282 /* We first try to match marked hotkey if there is
283 * one else we fallback to first character in button
284 * name. */
285 hk_pos = widget_data->widget->info.button.hotkey_pos;
286 if (hk_pos >= 0) {
287 if (toupper(widget_data->widget->text[hk_pos + 1]) != key)
288 continue;
289 } else {
290 if (toupper(widget_data->widget->text[0]) != key)
291 continue;
294 select_dlg_item(dlg_data, widget_data);
295 break;
299 static void
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)
310 return;
312 action_id = kbd_action(KEYMAP_MENU, ev, NULL);
313 switch (action_id) {
314 case ACT_MENU_SELECT:
315 /* Can we select? */
316 if (ops->select) {
317 ops->select(dlg_data, widget_data);
319 break;
320 case ACT_MENU_ENTER:
321 /* Submit button. */
322 if (ops->select) {
323 ops->select(dlg_data, widget_data);
324 break;
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);
332 break;
333 case ACT_MENU_CANCEL:
334 /* Cancel button. */
335 select_button_by_flag(dlg_data, B_ESC);
336 break;
337 case ACT_MENU_NEXT_ITEM:
338 case ACT_MENU_DOWN:
339 case ACT_MENU_RIGHT:
340 /* Cycle focus. */
341 cycle_widget_focus(dlg_data, 1);
342 break;
343 case ACT_MENU_PREVIOUS_ITEM:
344 case ACT_MENU_UP:
345 case ACT_MENU_LEFT:
346 /* Cycle focus (reverse). */
347 cycle_widget_focus(dlg_data, -1);
348 break;
349 case ACT_MENU_REDRAW:
350 redraw_terminal_cls(dlg_data->win->term);
351 break;
352 default:
353 select_button_by_key(dlg_data);
354 break;
358 static void
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);
367 mem_free(refresh);
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. */
383 static void
384 dialog_func(struct window *win, struct term_event *ev)
386 struct dialog_data *dlg_data = win->data;
388 dlg_data->win = win;
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)) {
394 return;
397 switch (ev->ev) {
398 case EVENT_INIT:
399 dialog_ev_init(dlg_data);
400 /* fallback */
401 case EVENT_RESIZE:
402 case EVENT_REDRAW:
403 redraw_dialog(dlg_data, 1);
404 break;
406 case EVENT_MOUSE:
407 #ifdef CONFIG_MOUSE
408 dialog_ev_mouse(dlg_data);
409 #endif
410 break;
412 case EVENT_KBD:
413 dialog_ev_kbd(dlg_data);
414 break;
416 case EVENT_ABORT:
417 dialog_ev_abort(dlg_data);
418 break;
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))
430 continue;
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);
436 return 1;
440 return 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)
457 continue;
458 memcpy(widget_data->widget->data,
459 widget_data->cdata,
460 widget_data->widget->datalen);
463 return 0;
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
492 * (and others). */
493 select_widget_by_id(dlg_data, 0);
495 redraw_dialog(dlg_data, 0);
496 return EVENT_PROCESSED;
500 static void
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:
511 case WIDGET_FIELD:
512 dlg_format_field(term, wdata, x, y, w, rw, ALIGN_LEFT);
513 break;
515 case WIDGET_LISTBOX:
516 dlg_format_listbox(term, wdata, x, y, w, h, rw, ALIGN_LEFT);
517 break;
519 case WIDGET_TEXT:
520 dlg_format_text(term, wdata, x, y, w, rw, h);
521 break;
523 case WIDGET_CHECKBOX:
525 int group = widget_has_group(wdata);
527 if (group > 0 && dlg_data->dlg->layout.float_groups) {
528 int size;
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))
535 break;
538 dlg_format_group(term, wdata, size, x, y, w, rw);
539 wdata += size - 1;
541 } else {
543 /* No horizontal space between checkboxes belonging to
544 * the same group. */
545 dlg_format_checkbox(term, wdata, x, y, w, rw, ALIGN_LEFT);
546 if (widgets > 1
547 && group == widget_has_group(&wdata[1]))
548 (*y)--;
551 break;
553 /* We assume that the buttons are all stuffed at the very end
554 * of the dialog. */
555 case WIDGET_BUTTON:
556 dlg_format_buttons(term, wdata, widgets,
557 x, y, w, rw, ALIGN_CENTER);
558 return;
563 void
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;
571 int x = 0;
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) {
580 w = rw;
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);
592 void
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")) {
607 /* Draw shadow */
608 draw_shadow(term, &dlg_data->box,
609 get_bfu_color(term, "dialog.shadow"), 2, 1);
613 static void
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);
628 return;
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);
641 void
642 refresh_dialog(struct dialog_data *dlg_data, dialog_refresh_handler_T handler, void *data)
644 struct dialog_refresh *refresh = dlg_data->dlg->refresh;
646 if (!refresh) {
647 refresh = mem_calloc(1, sizeof(*refresh));
648 if (!refresh) return;
650 dlg_data->dlg->refresh = refresh;
652 } else {
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);