1 /* Dialog box implementation. */
2 
3 #ifdef HAVE_CONFIG_H
4 #include "config.h"
5 #endif
6 
7 #include <stdlib.h>
8 #include <string.h>
9 
10 #include "elinks.h"
11 
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"
27 
28 
29 static window_handler_T dialog_func;
30 
31 struct dialog_data *
do_dialog(struct terminal * term,struct dialog * dlg,struct memory_list * ml)32 do_dialog(struct terminal *term, struct dialog *dlg,
33 	  struct memory_list *ml)
34 {
35 	struct dialog_data *dlg_data;
36 
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;
43 	}
44 
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);
49 
50 	return dlg_data;
51 }
52 
53 static void cycle_widget_focus(struct dialog_data *dlg_data, int direction);
54 
55 static void
update_all_widgets(struct dialog_data * dlg_data)56 update_all_widgets(struct dialog_data *dlg_data)
57 {
58 	struct widget_data *widget_data;
59 
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);
64 	}
65 }
66 
67 void
redraw_dialog(struct dialog_data * dlg_data,int layout)68 redraw_dialog(struct dialog_data *dlg_data, int layout)
69 {
70 	struct terminal *term = dlg_data->win->term;
71 	struct color_pair *title_color;
72 
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);
81 	}
82 
83 	if (!dlg_data->dlg->layout.only_widgets) {
84 		struct box box;
85 
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));
91 
92 		draw_border(term, &box, get_bfu_color(term, "dialog.frame"), DIALOG_FRAME);
93 
94 		assert(dlg_data->dlg->title);
95 
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;
102 
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);
106 		}
107 	}
108 
109 	update_all_widgets(dlg_data);
110 
111 	redraw_from_window(dlg_data->win);
112 }
113 
114 static void
select_dlg_item(struct dialog_data * dlg_data,struct widget_data * widget_data)115 select_dlg_item(struct dialog_data *dlg_data, struct widget_data *widget_data)
116 {
117 	select_widget(dlg_data, widget_data);
118 
119 	if (widget_data->widget->ops->select)
120 		widget_data->widget->ops->select(dlg_data, widget_data);
121 }
122 
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,
130 };
131 
132 static struct widget_data *
init_widget(struct dialog_data * dlg_data,int i)133 init_widget(struct dialog_data *dlg_data, int i)
134 {
135 	struct widget_data *widget_data = &dlg_data->widgets_data[i];
136 
137 	memset(widget_data, 0, sizeof(*widget_data));
138 	widget_data->widget = &dlg_data->dlg->widgets[i];
139 
140 	if (widget_data->widget->datalen) {
141 		widget_data->cdata = mem_alloc(widget_data->widget->datalen);
142 		if (!widget_data->cdata) {
143 			return NULL;
144 		}
145 		memcpy(widget_data->cdata,
146 		       widget_data->widget->data,
147 		       widget_data->widget->datalen);
148 	}
149 
150 	widget_data->widget->ops = widget_type_to_ops[widget_data->widget->type];
151 
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;
156 	}
157 
158 	if (widget_data->widget->ops->init)
159 		widget_data->widget->ops->init(dlg_data, widget_data);
160 
161 	return widget_data;
162 }
163 
164 void
select_widget(struct dialog_data * dlg_data,struct widget_data * widget_data)165 select_widget(struct dialog_data *dlg_data, struct widget_data *widget_data)
166 {
167 	struct widget_data *previously_selected_widget;
168 
169 	previously_selected_widget = selected_widget(dlg_data);
170 
171 	dlg_data->selected_widget_id = widget_data - dlg_data->widgets_data;
172 
173 	display_widget(dlg_data, previously_selected_widget);
174 	display_widget(dlg_data, widget_data);
175 }
176 
177 
178 struct widget_data *
select_widget_by_id(struct dialog_data * dlg_data,int i)179 select_widget_by_id(struct dialog_data *dlg_data, int i)
180 {
181 	struct widget_data *widget_data;
182 
183 	if (i >= dlg_data->number_of_widgets)
184 		return NULL;
185 
186 	widget_data = &dlg_data->widgets_data[i];
187 	select_widget(dlg_data, widget_data);
188 
189 	return widget_data;
190 }
191 
192 static void
cycle_widget_focus(struct dialog_data * dlg_data,int direction)193 cycle_widget_focus(struct dialog_data *dlg_data, int direction)
194 {
195 	int prev_selected = dlg_data->selected_widget_id;
196 	struct widget_data *previously_selected_widget;
197 
198 	previously_selected_widget = selected_widget(dlg_data);
199 
200 	do {
201 		dlg_data->selected_widget_id += direction;
202 
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;
207 
208 	} while (!widget_is_focusable(selected_widget(dlg_data))
209 		 && dlg_data->selected_widget_id != prev_selected);
210 
211 	display_widget(dlg_data, previously_selected_widget);
212 	display_widget(dlg_data, selected_widget(dlg_data));
213 	redraw_from_window(dlg_data->win);
214 }
215 
216 static void
dialog_ev_init(struct dialog_data * dlg_data)217 dialog_ev_init(struct dialog_data *dlg_data)
218 {
219 	int i;
220 
221 	/* TODO: foreachback_widget() */
222 	for (i = dlg_data->number_of_widgets - 1; i >= 0; i--) {
223 		struct widget_data *widget_data;
224 
225 		widget_data = init_widget(dlg_data, i);
226 
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;
231 	}
232 }
233 
234 #ifdef CONFIG_MOUSE
235 static void
dialog_ev_mouse(struct dialog_data * dlg_data)236 dialog_ev_mouse(struct dialog_data *dlg_data)
237 {
238 	struct widget_data *widget_data;
239 
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;
245 	}
246 }
247 #endif /* CONFIG_MOUSE */
248 
249 /* Look up for a button with matching flag. */
250 static void
select_button_by_flag(struct dialog_data * dlg_data,int flag)251 select_button_by_flag(struct dialog_data *dlg_data, int flag)
252 {
253 	struct widget_data *widget_data;
254 
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;
260 		}
261 	}
262 }
263 
264 /* Look up for a button with matching starting letter. */
265 static void
select_button_by_key(struct dialog_data * dlg_data)266 select_button_by_key(struct dialog_data *dlg_data)
267 {
268 	unsigned char key;
269 	struct widget_data *widget_data;
270 	struct term_event *ev = dlg_data->term_event;
271 
272 	if (!check_kbd_label_key(ev)) return;
273 
274 	key = toupper(get_kbd_key(ev));
275 
276 	foreach_widget(dlg_data, widget_data) {
277 		int hk_pos;
278 
279 		if (widget_data->widget->type != WIDGET_BUTTON)
280 			continue;
281 
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;
292 		}
293 
294 		select_dlg_item(dlg_data, widget_data);
295 		break;
296 	}
297 }
298 
299 static void
dialog_ev_kbd(struct dialog_data * dlg_data)300 dialog_ev_kbd(struct dialog_data *dlg_data)
301 {
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;
307 
308 	/* First let the widget try out. */
309 	if (ops->kbd && ops->kbd(dlg_data, widget_data) == EVENT_PROCESSED)
310 		return;
311 
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);
318 		}
319 		break;
320 	case ACT_MENU_ENTER:
321 		/* Submit button. */
322 		if (ops->select) {
323 			ops->select(dlg_data, widget_data);
324 			break;
325 		}
326 
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);
331 		}
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;
355 	}
356 }
357 
358 static void
dialog_ev_abort(struct dialog_data * dlg_data)359 dialog_ev_abort(struct dialog_data *dlg_data)
360 {
361 	struct widget_data *widget_data;
362 
363 	if (dlg_data->dlg->refresh) {
364 		struct dialog_refresh *refresh = dlg_data->dlg->refresh;
365 
366 		kill_timer(&refresh->timer);
367 		mem_free(refresh);
368 	}
369 
370 	if (dlg_data->dlg->abort)
371 		dlg_data->dlg->abort(dlg_data);
372 
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);
377 	}
378 
379 	freeml(dlg_data->ml);
380 }
381 
382 /* TODO: use EVENT_PROCESSED/EVENT_NOT_PROCESSED. */
383 static void
dialog_func(struct window * win,struct term_event * ev)384 dialog_func(struct window *win, struct term_event *ev)
385 {
386 	struct dialog_data *dlg_data = win->data;
387 
388 	dlg_data->win = win;
389 	dlg_data->term_event = ev;
390 
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;
395 	}
396 
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;
405 
406 		case EVENT_MOUSE:
407 #ifdef CONFIG_MOUSE
408 			dialog_ev_mouse(dlg_data);
409 #endif
410 			break;
411 
412 		case EVENT_KBD:
413 			dialog_ev_kbd(dlg_data);
414 			break;
415 
416 		case EVENT_ABORT:
417 			dialog_ev_abort(dlg_data);
418 			break;
419 	}
420 }
421 
422 int
check_dialog(struct dialog_data * dlg_data)423 check_dialog(struct dialog_data *dlg_data)
424 {
425 	struct widget_data *widget_data;
426 
427 	foreach_widget(dlg_data, widget_data) {
428 		if (widget_data->widget->type != WIDGET_CHECKBOX &&
429 		    !widget_is_textfield(widget_data))
430 			continue;
431 
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;
437 		}
438 	}
439 
440 	return 0;
441 }
442 
443 widget_handler_status_T
cancel_dialog(struct dialog_data * dlg_data,struct widget_data * xxx)444 cancel_dialog(struct dialog_data *dlg_data, struct widget_data *xxx)
445 {
446 	delete_window(dlg_data->win);
447 	return EVENT_PROCESSED;
448 }
449 
450 int
update_dialog_data(struct dialog_data * dlg_data)451 update_dialog_data(struct dialog_data *dlg_data)
452 {
453 	struct widget_data *widget_data;
454 
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);
461 	}
462 
463 	return 0;
464 }
465 
466 widget_handler_status_T
ok_dialog(struct dialog_data * dlg_data,struct widget_data * widget_data)467 ok_dialog(struct dialog_data *dlg_data, struct widget_data *widget_data)
468 {
469 	done_handler_T *done = widget_data->widget->info.button.done;
470 	void *done_data = widget_data->widget->info.button.done_data;
471 
472 	if (check_dialog(dlg_data)) return EVENT_NOT_PROCESSED;
473 
474 	update_dialog_data(dlg_data);
475 
476 	if (done) done(done_data);
477 	return cancel_dialog(dlg_data, widget_data);
478 }
479 
480 /* Clear dialog fields (if widget has clear callback). */
481 widget_handler_status_T
clear_dialog(struct dialog_data * dlg_data,struct widget_data * xxx)482 clear_dialog(struct dialog_data *dlg_data, struct widget_data *xxx)
483 {
484 	struct widget_data *widget_data;
485 
486 	foreach_widget(dlg_data, widget_data) {
487 		if (widget_data->widget->ops->clear)
488 			widget_data->widget->ops->clear(dlg_data, widget_data);
489 	}
490 
491 	/* Move focus to the first widget. It helps with bookmark search dialog
492 	 * (and others). */
493 	select_widget_by_id(dlg_data, 0);
494 
495 	redraw_dialog(dlg_data, 0);
496 	return EVENT_PROCESSED;
497 }
498 
499 
500 static void
format_widgets(struct terminal * term,struct dialog_data * dlg_data,int x,int * y,int w,int h,int * rw)501 format_widgets(struct terminal *term, struct dialog_data *dlg_data,
502 	       int x, int *y, int w, int h, int *rw)
503 {
504 	struct widget_data *wdata = dlg_data->widgets_data;
505 	int widgets = dlg_data->number_of_widgets;
506 
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;
514 
515 		case WIDGET_LISTBOX:
516 			dlg_format_listbox(term, wdata, x, y, w, h, rw, ALIGN_LEFT);
517 			break;
518 
519 		case WIDGET_TEXT:
520 			dlg_format_text(term, wdata, x, y, w, rw, h);
521 			break;
522 
523 		case WIDGET_CHECKBOX:
524 		{
525 			int group = widget_has_group(wdata);
526 
527 			if (group > 0 && dlg_data->dlg->layout.float_groups) {
528 				int size;
529 
530 				/* Find group size */
531 				for (size = 1; widgets > 0; size++, widgets--) {
532 					struct widget_data *next = &wdata[size];
533 
534 					if (group != widget_has_group(next))
535 						break;
536 				}
537 
538 				dlg_format_group(term, wdata, size, x, y, w, rw);
539 				wdata += size - 1;
540 
541 			} else {
542 
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)--;
549 			}
550 		}
551 			break;
552 
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;
559 		}
560 	}
561 }
562 
563 void
generic_dialog_layouter(struct dialog_data * dlg_data)564 generic_dialog_layouter(struct dialog_data *dlg_data)
565 {
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;
572 
573 	format_widgets(NULL, dlg_data, x, &y, w, height, &rw);
574 
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;
581 	}
582 
583 	draw_dialog(dlg_data, w, y);
584 
585 	y = dlg_data->box.y + DIALOG_TB + dlg_data->dlg->layout.padding_top;
586 	x = dlg_data->box.x + DIALOG_LB;
587 
588 	format_widgets(term, dlg_data, x, &y, w, height, NULL);
589 }
590 
591 
592 void
draw_dialog(struct dialog_data * dlg_data,int width,int height)593 draw_dialog(struct dialog_data *dlg_data, int width, int height)
594 {
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);
598 
599 	set_box(&dlg_data->box,
600 		(term->width - dlg_width) / 2, (term->height - dlg_height) / 2,
601 		dlg_width, dlg_height);
602 
603 	draw_box(term, &dlg_data->box, ' ', 0,
604 		 get_bfu_color(term, "dialog.generic"));
605 
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);
610 	}
611 }
612 
613 static void
do_refresh_dialog(struct dialog_data * dlg_data)614 do_refresh_dialog(struct dialog_data *dlg_data)
615 {
616 	struct dialog_refresh *refresh = dlg_data->dlg->refresh;
617 	enum dlg_refresh_code refresh_code;
618 
619 	assert(refresh && refresh->handler);
620 
621 	refresh_code = refresh->handler(dlg_data, refresh->data);
622 
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;
629 	}
630 
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);
635 	}
636 
637 	install_timer(&refresh->timer, RESOURCE_INFO_REFRESH,
638 		      (void (*)(void *)) do_refresh_dialog, dlg_data);
639 }
640 
641 void
refresh_dialog(struct dialog_data * dlg_data,dialog_refresh_handler_T handler,void * data)642 refresh_dialog(struct dialog_data *dlg_data, dialog_refresh_handler_T handler, void *data)
643 {
644 	struct dialog_refresh *refresh = dlg_data->dlg->refresh;
645 
646 	if (!refresh) {
647 		refresh = mem_calloc(1, sizeof(*refresh));
648 		if (!refresh) return;
649 
650 		dlg_data->dlg->refresh = refresh;
651 
652 	} else {
653 		kill_timer(&refresh->timer);
654 	}
655 
656 	refresh->handler = handler;
657 	refresh->data = data;
658 	install_timer(&refresh->timer, RESOURCE_INFO_REFRESH,
659 		      (void (*)(void *)) do_refresh_dialog, dlg_data);
660 }
661