grafthistory: support curl
[elinks/elinks-j605.git] / src / bfu / inpfield.c
blobfd14f00291eba375eef5da19cf72886ffc548883
1 /* Input field widget implementation. */
3 #ifdef HAVE_CONFIG_H
4 #include "config.h"
5 #endif
7 #include <errno.h>
8 #include <stdlib.h>
9 #include <string.h>
11 #include "elinks.h"
13 #include "bfu/button.h"
14 #include "bfu/dialog.h"
15 #include "bfu/inpfield.h"
16 #include "bfu/inphist.h"
17 #include "bfu/msgbox.h"
18 #include "bfu/text.h"
19 #include "config/kbdbind.h"
20 #include "intl/gettext/libintl.h"
21 #include "osdep/osdep.h"
22 #include "session/session.h"
23 #include "terminal/draw.h"
24 #include "terminal/kbd.h"
25 #include "terminal/mouse.h"
26 #include "terminal/terminal.h"
27 #include "terminal/window.h"
28 #include "util/color.h"
29 #include "util/memlist.h"
30 #include "util/memory.h"
32 #define INPUTFIELD_HEIGHT 1
34 #define INPUTFIELD_FLOATLABEL_PADDING 1
36 #define INPUTFIELD_FLOAT_SEPARATOR ":"
37 #define INPUTFIELD_FLOAT_SEPARATOR_LEN 1
39 void
40 add_dlg_field_do(struct dialog *dlg, enum widget_type type, unsigned char *label,
41 int min, int max, widget_handler_T *handler,
42 int datalen, void *data,
43 struct input_history *history, enum inpfield_flags flags)
45 struct widget *widget = &dlg->widgets[dlg->number_of_widgets++];
47 widget->type = type;
48 widget->text = label;
49 widget->handler = handler;
50 widget->datalen = datalen;
51 widget->data = data;
53 widget->info.field.history = history;
54 widget->info.field.flags = flags;
55 widget->info.field.min = min;
56 widget->info.field.max = max;
59 widget_handler_status_T
60 check_number(struct dialog_data *dlg_data, struct widget_data *widget_data)
62 struct widget *widget = widget_data->widget;
63 char *end;
64 long l;
66 errno = 0;
67 l = strtol(widget_data->cdata, &end, 10);
69 if (errno || !*widget_data->cdata || *end) {
70 info_box(dlg_data->win->term, 0,
71 N_("Bad number"), ALIGN_CENTER,
72 N_("Number expected in field"));
73 return EVENT_NOT_PROCESSED;
76 if (l < widget->info.field.min || l > widget->info.field.max) {
77 info_box(dlg_data->win->term, MSGBOX_FREE_TEXT,
78 N_("Bad number"), ALIGN_CENTER,
79 msg_text(dlg_data->win->term,
80 N_("Number should be in the range from %d to %d."),
81 widget->info.field.min, widget->info.field.max));
82 return EVENT_NOT_PROCESSED;
85 return EVENT_PROCESSED;
88 widget_handler_status_T
89 check_nonempty(struct dialog_data *dlg_data, struct widget_data *widget_data)
91 unsigned char *p;
93 for (p = widget_data->cdata; *p; p++)
94 if (*p > ' ')
95 return EVENT_PROCESSED;
97 info_box(dlg_data->win->term, 0,
98 N_("Bad string"), ALIGN_CENTER,
99 N_("Empty string not allowed"));
101 return EVENT_NOT_PROCESSED;
104 void
105 dlg_format_field(struct terminal *term,
106 struct widget_data *widget_data,
107 int x, int *y, int w, int *rw, enum format_align align)
109 static int max_label_width;
110 static int *prev_y; /* Assert the uniqueness of y */ /* TODO: get rid of this !! --Zas */
111 unsigned char *label = widget_data->widget->text;
112 struct color_pair *text_color = NULL;
113 int label_width = 0;
114 int float_label = widget_data->widget->info.field.flags & (INPFIELD_FLOAT|INPFIELD_FLOAT2);
116 if (label && *label && float_label) {
117 label_width = strlen(label);
118 if (prev_y == y) {
119 int_lower_bound(&max_label_width, label_width);
120 } else {
121 max_label_width = label_width;
122 prev_y = y;
125 /* Right align the floating label up against the
126 * input field */
127 x += max_label_width - label_width;
128 w -= max_label_width - label_width;
131 if (label && *label) {
132 if (term) text_color = get_bfu_color(term, "dialog.text");
134 dlg_format_text_do(term, label, x, y, w, rw, text_color, ALIGN_LEFT);
137 /* XXX: We want the field and label on the same line if the terminal
138 * width allows it. */
139 if (label && *label && float_label) {
140 if (widget_data->widget->info.field.flags & INPFIELD_FLOAT) {
141 (*y) -= INPUTFIELD_HEIGHT;
142 dlg_format_text_do(term, INPUTFIELD_FLOAT_SEPARATOR,
143 x + label_width, y, w, rw,
144 text_color, ALIGN_LEFT);
145 w -= INPUTFIELD_FLOAT_SEPARATOR_LEN + INPUTFIELD_FLOATLABEL_PADDING;
146 x += INPUTFIELD_FLOAT_SEPARATOR_LEN + INPUTFIELD_FLOATLABEL_PADDING;
149 /* FIXME: Is 5 chars for input field enough? --jonas */
150 if (label_width < w - 5) {
151 (*y) -= INPUTFIELD_HEIGHT;
152 w -= label_width;
153 x += label_width;
157 if (rw) int_lower_bound(rw, int_min(w, DIALOG_MIN_WIDTH));
159 set_box(&widget_data->box, x, *y, w, INPUTFIELD_HEIGHT);
161 (*y) += INPUTFIELD_HEIGHT;
164 static widget_handler_status_T
165 input_field_cancel(struct dialog_data *dlg_data, struct widget_data *widget_data)
167 void (*fn)(void *) = widget_data->widget->data;
168 void *data = dlg_data->dlg->udata2;
170 if (fn) fn(data);
172 return cancel_dialog(dlg_data, widget_data);
175 static widget_handler_status_T
176 input_field_ok(struct dialog_data *dlg_data, struct widget_data *widget_data)
178 void (*fn)(void *, unsigned char *) = widget_data->widget->data;
179 void *data = dlg_data->dlg->udata2;
180 unsigned char *text = dlg_data->widgets_data->cdata;
182 if (check_dialog(dlg_data)) return EVENT_NOT_PROCESSED;
184 if (widget_has_history(dlg_data->widgets_data))
185 add_to_input_history(dlg_data->dlg->widgets->info.field.history,
186 text, 1);
188 if (fn) fn(data, text);
190 return cancel_dialog(dlg_data, widget_data);
193 void
194 input_field(struct terminal *term, struct memory_list *ml, int intl,
195 unsigned char *title,
196 unsigned char *text,
197 unsigned char *okbutton,
198 unsigned char *cancelbutton,
199 void *data, struct input_history *history, int l,
200 unsigned char *def, int min, int max,
201 widget_handler_T *check,
202 void (*fn)(void *, unsigned char *),
203 void (*cancelfn)(void *))
205 struct dialog *dlg;
206 unsigned char *field;
208 if (intl) {
209 title = _(title, term);
210 text = _(text, term);
211 okbutton = _(okbutton, term);
212 cancelbutton = _(cancelbutton, term);
215 #define INPUT_WIDGETS_COUNT 3
216 dlg = calloc_dialog(INPUT_WIDGETS_COUNT, l);
217 if (!dlg) return;
219 /* @field is automatically cleared by calloc() */
220 field = get_dialog_offset(dlg, INPUT_WIDGETS_COUNT);
222 if (def) {
223 int defsize = strlen(def) + 1;
225 memcpy(field, def, (defsize > l) ? l - 1 : defsize);
228 dlg->title = title;
229 dlg->layouter = generic_dialog_layouter;
230 dlg->layout.fit_datalen = 1;
231 dlg->udata2 = data;
233 add_dlg_field(dlg, text, min, max, check, l, field, history);
235 add_dlg_button(dlg, okbutton, B_ENTER, input_field_ok, fn);
236 add_dlg_button(dlg, cancelbutton, B_ESC, input_field_cancel, cancelfn);
238 add_dlg_end(dlg, INPUT_WIDGETS_COUNT);
240 add_to_ml(&ml, dlg, NULL);
241 do_dialog(term, dlg, ml);
244 void
245 input_dialog(struct terminal *term, struct memory_list *ml,
246 unsigned char *title,
247 unsigned char *text,
248 void *data, struct input_history *history, int l,
249 unsigned char *def, int min, int max,
250 widget_handler_T *check,
251 void (*fn)(void *, unsigned char *),
252 void (*cancelfn)(void *))
254 input_field(term, ml, 1, title, text, N_("~OK"), N_("~Cancel"),
255 data, history, l,
256 def, min, max,
257 check, fn, cancelfn);
260 static widget_handler_status_T
261 display_field_do(struct dialog_data *dlg_data, struct widget_data *widget_data,
262 int hide)
264 struct terminal *term = dlg_data->win->term;
265 struct color_pair *color;
266 int sel = is_selected_widget(dlg_data, widget_data);
268 int_bounds(&widget_data->info.field.vpos,
269 widget_data->info.field.cpos - widget_data->box.width + 1,
270 widget_data->info.field.cpos);
271 int_lower_bound(&widget_data->info.field.vpos, 0);
273 color = get_bfu_color(term, "dialog.field");
274 if (color)
275 draw_box(term, &widget_data->box, ' ', 0, color);
277 color = get_bfu_color(term, "dialog.field-text");
278 if (color) {
279 int len = strlen(widget_data->cdata + widget_data->info.field.vpos);
280 int w = int_min(len, widget_data->box.width);
282 if (!hide) {
283 draw_text(term, widget_data->box.x, widget_data->box.y,
284 widget_data->cdata + widget_data->info.field.vpos, w,
285 0, color);
286 } else {
287 struct box box;
289 copy_box(&box, &widget_data->box);
290 box.width = w;
292 draw_box(term, &box, '*', 0, color);
296 if (sel) {
297 int x = widget_data->box.x + widget_data->info.field.cpos - widget_data->info.field.vpos;
299 set_cursor(term, x, widget_data->box.y, 0);
300 set_window_ptr(dlg_data->win, widget_data->box.x, widget_data->box.y);
303 return EVENT_PROCESSED;
306 static widget_handler_status_T
307 display_field(struct dialog_data *dlg_data, struct widget_data *widget_data)
309 return display_field_do(dlg_data, widget_data, 0);
312 static widget_handler_status_T
313 display_field_pass(struct dialog_data *dlg_data, struct widget_data *widget_data)
315 return display_field_do(dlg_data, widget_data, 1);
318 static widget_handler_status_T
319 init_field(struct dialog_data *dlg_data, struct widget_data *widget_data)
321 if (widget_has_history(widget_data)) {
322 struct input_history_entry *entry;
324 foreach (entry, widget_data->widget->info.field.history->entries) {
325 int datalen = strlen(entry->data);
326 struct input_history_entry *new_entry;
328 /* One byte is reserved in struct input_history_entry. */
329 new_entry = mem_alloc(sizeof(*new_entry) + datalen);
330 if (!new_entry) continue;
332 memcpy(new_entry->data, entry->data, datalen + 1);
333 add_to_list(widget_data->info.field.history, new_entry);
337 widget_data->info.field.cpos = strlen(widget_data->cdata);
338 return EVENT_PROCESSED;
341 static int
342 field_prev_history(struct widget_data *widget_data)
344 if (widget_has_history(widget_data)
345 && (void *) widget_data->info.field.cur_hist->prev != &widget_data->info.field.history) {
346 widget_data->info.field.cur_hist = widget_data->info.field.cur_hist->prev;
347 dlg_set_history(widget_data);
348 return 1;
350 return 0;
353 static int
354 field_next_history(struct widget_data *widget_data)
356 if (widget_has_history(widget_data)
357 && (void *) widget_data->info.field.cur_hist != &widget_data->info.field.history) {
358 widget_data->info.field.cur_hist = widget_data->info.field.cur_hist->next;
359 dlg_set_history(widget_data);
360 return 1;
362 return 0;
365 static widget_handler_status_T
366 mouse_field(struct dialog_data *dlg_data, struct widget_data *widget_data)
368 struct term_event *ev = dlg_data->term_event;
370 if (!check_mouse_position(ev, &widget_data->box))
371 return EVENT_NOT_PROCESSED;
373 /* Handle navigation through history (if any) using up/down mouse wheel */
374 switch (get_mouse_button(ev)) {
375 case B_WHEEL_UP:
376 if (check_mouse_action(ev, B_DOWN)) {
377 if (field_prev_history(widget_data)) {
378 select_widget(dlg_data, widget_data);
379 return EVENT_PROCESSED;
382 return EVENT_NOT_PROCESSED;
384 case B_WHEEL_DOWN:
385 if (check_mouse_action(ev, B_DOWN)) {
386 if (field_next_history(widget_data)) {
387 select_widget(dlg_data, widget_data);
388 return EVENT_PROCESSED;
391 return EVENT_NOT_PROCESSED;
394 /* Place text cursor at mouse position and focus the widget. */
395 widget_data->info.field.cpos = widget_data->info.field.vpos
396 + ev->info.mouse.x - widget_data->box.x;
397 int_upper_bound(&widget_data->info.field.cpos, strlen(widget_data->cdata));
399 select_widget(dlg_data, widget_data);
400 return EVENT_PROCESSED;
403 static widget_handler_status_T
404 kbd_field(struct dialog_data *dlg_data, struct widget_data *widget_data)
406 struct window *win = dlg_data->win;
407 struct terminal *term = win->term;
408 struct term_event *ev = dlg_data->term_event;
409 action_id_T action_id;
411 action_id = kbd_action(KEYMAP_EDIT, ev, NULL);
412 if (action_id != -1
413 && !action_is_anonymous_safe(KEYMAP_EDIT, action_id)
414 && get_cmd_opt_bool("anonymous"))
415 return EVENT_NOT_PROCESSED;
417 switch (action_id) {
418 case ACT_EDIT_UP:
419 if (!widget_has_history(widget_data))
420 return EVENT_NOT_PROCESSED;
422 if (field_prev_history(widget_data)) {
423 goto display_field;
425 break;
427 case ACT_EDIT_DOWN:
428 if (!widget_has_history(widget_data))
429 return EVENT_NOT_PROCESSED;
431 if (field_next_history(widget_data)) {
432 goto display_field;
434 break;
436 case ACT_EDIT_RIGHT:
437 if (widget_data->info.field.cpos < strlen(widget_data->cdata))
438 widget_data->info.field.cpos++;
439 goto display_field;
441 case ACT_EDIT_LEFT:
442 if (widget_data->info.field.cpos > 0)
443 widget_data->info.field.cpos--;
444 goto display_field;
446 case ACT_EDIT_HOME:
447 widget_data->info.field.cpos = 0;
448 goto display_field;
450 case ACT_EDIT_END:
451 widget_data->info.field.cpos = strlen(widget_data->cdata);
452 goto display_field;
454 case ACT_EDIT_BACKSPACE:
455 if (widget_data->info.field.cpos) {
456 memmove(widget_data->cdata + widget_data->info.field.cpos - 1,
457 widget_data->cdata + widget_data->info.field.cpos,
458 strlen(widget_data->cdata) - widget_data->info.field.cpos + 1);
459 widget_data->info.field.cpos--;
461 goto display_field;
463 case ACT_EDIT_DELETE:
465 int cdata_len = strlen(widget_data->cdata);
467 if (widget_data->info.field.cpos >= cdata_len) goto display_field;
469 memmove(widget_data->cdata + widget_data->info.field.cpos,
470 widget_data->cdata + widget_data->info.field.cpos + 1,
471 cdata_len - widget_data->info.field.cpos + 1);
472 goto display_field;
475 case ACT_EDIT_KILL_TO_BOL:
476 memmove(widget_data->cdata,
477 widget_data->cdata + widget_data->info.field.cpos,
478 strlen(widget_data->cdata + widget_data->info.field.cpos) + 1);
479 widget_data->info.field.cpos = 0;
480 goto display_field;
482 case ACT_EDIT_KILL_TO_EOL:
483 widget_data->cdata[widget_data->info.field.cpos] = 0;
484 goto display_field;
486 case ACT_EDIT_COPY_CLIPBOARD:
487 /* Copy to clipboard */
488 set_clipboard_text(widget_data->cdata);
489 return EVENT_PROCESSED;
491 case ACT_EDIT_CUT_CLIPBOARD:
492 /* Cut to clipboard */
493 set_clipboard_text(widget_data->cdata);
494 widget_data->cdata[0] = 0;
495 widget_data->info.field.cpos = 0;
496 goto display_field;
498 case ACT_EDIT_PASTE_CLIPBOARD:
500 /* Paste from clipboard */
501 unsigned char *clipboard = get_clipboard_text();
503 if (!clipboard) goto display_field;
505 safe_strncpy(widget_data->cdata, clipboard, widget_data->widget->datalen);
506 widget_data->info.field.cpos = strlen(widget_data->cdata);
507 mem_free(clipboard);
508 goto display_field;
511 case ACT_EDIT_AUTO_COMPLETE:
512 if (!widget_has_history(widget_data))
513 return EVENT_NOT_PROCESSED;
515 do_tab_compl(dlg_data, &widget_data->info.field.history);
516 goto display_field;
518 case ACT_EDIT_AUTO_COMPLETE_FILE:
519 if (!widget_has_history(widget_data))
520 return EVENT_NOT_PROCESSED;
522 do_tab_compl_file(dlg_data, &widget_data->info.field.history);
523 goto display_field;
525 case ACT_EDIT_AUTO_COMPLETE_UNAMBIGUOUS:
526 if (!widget_has_history(widget_data))
527 return EVENT_NOT_PROCESSED;
529 do_tab_compl_unambiguous(dlg_data, &widget_data->info.field.history);
530 goto display_field;
532 case ACT_EDIT_REDRAW:
533 redraw_terminal_cls(term);
534 return EVENT_PROCESSED;
536 default:
537 if (check_kbd_textinput_key(ev)) {
538 unsigned char *text = widget_data->cdata;
539 int textlen = strlen(text);
541 if (textlen >= widget_data->widget->datalen - 1)
542 goto display_field;
544 /* Shift to position of the cursor */
545 textlen -= widget_data->info.field.cpos;
546 text += widget_data->info.field.cpos++;
548 memmove(text + 1, text, textlen + 1);
549 *text = get_kbd_key(ev);
551 goto display_field;
554 return EVENT_NOT_PROCESSED;
556 display_field:
557 display_widget(dlg_data, widget_data);
558 redraw_from_window(dlg_data->win);
559 return EVENT_PROCESSED;
563 static widget_handler_status_T
564 clear_field(struct dialog_data *dlg_data, struct widget_data *widget_data)
566 widget_data->info.field.cpos = 0;
568 if (widget_data->widget->datalen)
569 memset(widget_data->cdata, 0, widget_data->widget->datalen);
571 return EVENT_PROCESSED;
574 struct widget_ops field_ops = {
575 display_field,
576 init_field,
577 mouse_field,
578 kbd_field,
579 NULL,
580 clear_field,
583 struct widget_ops field_pass_ops = {
584 display_field_pass,
585 init_field,
586 mouse_field,
587 kbd_field,
588 NULL,
589 clear_field,
593 /* Input lines */
595 static void
596 input_line_layouter(struct dialog_data *dlg_data)
598 struct input_line *input_line = dlg_data->dlg->udata;
599 struct session *ses = input_line->ses;
600 struct window *win = dlg_data->win;
601 int y = win->term->height - 1
602 - ses->status.show_status_bar
603 - ses->status.show_tabs_bar;
605 dlg_format_field(win->term, dlg_data->widgets_data, 0,
606 &y, win->term->width, NULL, ALIGN_LEFT);
609 static widget_handler_status_T
610 input_line_event_handler(struct dialog_data *dlg_data)
612 struct input_line *input_line = dlg_data->dlg->udata;
613 input_line_handler_T handler = input_line->handler;
614 enum edit_action action_id;
615 struct widget_data *widget_data = dlg_data->widgets_data;
616 struct term_event *ev = dlg_data->term_event;
618 /* Noodle time */
619 switch (ev->ev) {
620 case EVENT_KBD:
621 action_id = kbd_action(KEYMAP_EDIT, ev, NULL);
623 /* Handle some basic actions such as quiting for empty buffers */
624 switch (action_id) {
625 case ACT_EDIT_ENTER:
626 case ACT_EDIT_NEXT_ITEM:
627 case ACT_EDIT_PREVIOUS_ITEM:
628 if (widget_has_history(widget_data))
629 add_to_input_history(widget_data->widget->info.field.history,
630 input_line->buffer, 1);
631 break;
633 case ACT_EDIT_BACKSPACE:
634 if (!*input_line->buffer)
635 goto cancel_input_line;
636 break;
638 case ACT_EDIT_CANCEL:
639 goto cancel_input_line;
641 default:
642 break;
645 /* First let the input field do its business */
646 kbd_field(dlg_data, widget_data);
647 break;
649 case EVENT_MOUSE:
650 #ifdef CONFIG_MOUSE
651 if (ev->info.mouse.y != dlg_data->win->y) {
652 delete_window_ev(dlg_data->win, ev);
653 return EVENT_PROCESSED;
655 #endif /* CONFIG_MOUSE */
656 return EVENT_NOT_PROCESSED;
658 case EVENT_REDRAW:
659 /* Try to catch the redraw event initiated by the history
660 * completion and only respond if something was actually
661 * updated. Meaning we have new data in the line buffer that
662 * should be propagated to the line handler. */
663 if (!widget_has_history(widget_data)
664 || widget_data->info.field.cpos <= 0
665 || widget_data->info.field.cpos <= strlen(input_line->buffer))
666 return EVENT_NOT_PROCESSED;
668 /* Fall thru */
670 case EVENT_RESIZE:
671 action_id = ACT_EDIT_REDRAW;
672 break;
674 default:
675 return EVENT_NOT_PROCESSED;
678 update_dialog_data(dlg_data);
680 send_action_to_handler:
681 /* Then pass it on to the specialized handler */
682 switch (handler(input_line, action_id)) {
683 case INPUT_LINE_CANCEL:
684 cancel_input_line:
685 cancel_dialog(dlg_data, widget_data);
686 break;
688 case INPUT_LINE_REWIND:
689 /* This is stolen kbd_field() handling for ACT_EDIT_BACKSPACE */
690 memmove(widget_data->cdata + widget_data->info.field.cpos - 1,
691 widget_data->cdata + widget_data->info.field.cpos,
692 strlen(widget_data->cdata) - widget_data->info.field.cpos + 1);
693 widget_data->info.field.cpos--;
695 update_dialog_data(dlg_data);
697 goto send_action_to_handler;
699 case INPUT_LINE_PROCEED:
700 break;
703 /* Hack: We want our caller to perform its redrawing routine,
704 * even if we did process the event here. */
705 if (action_id == ACT_EDIT_REDRAW) return EVENT_NOT_PROCESSED;
707 /* Completely bypass any further dialog event handling */
708 return EVENT_PROCESSED;
711 void
712 input_field_line(struct session *ses, unsigned char *prompt, void *data,
713 struct input_history *history, input_line_handler_T handler)
715 struct dialog *dlg;
716 unsigned char *buffer;
717 struct input_line *input_line;
719 assert(ses);
721 dlg = calloc_dialog(INPUT_LINE_WIDGETS, sizeof(*input_line));
722 if (!dlg) return;
724 input_line = (void *) get_dialog_offset(dlg, INPUT_LINE_WIDGETS);
725 input_line->ses = ses;
726 input_line->data = data;
727 input_line->handler = handler;
728 buffer = input_line->buffer;
730 dlg->handle_event = input_line_event_handler;
731 dlg->layouter = input_line_layouter;
732 dlg->layout.only_widgets = 1;
733 dlg->udata = input_line;
735 add_dlg_field_float2(dlg, prompt, 0, 0, NULL, INPUT_LINE_BUFFER_SIZE,
736 buffer, history);
738 do_dialog(ses->tab->term, dlg, getml(dlg, NULL));