1 /* Input field widget implementation. */
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"
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
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
++];
49 widget
->handler
= handler
;
50 widget
->datalen
= datalen
;
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
;
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
)
93 for (p
= widget_data
->cdata
; *p
; 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
;
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
;
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
);
119 int_lower_bound(&max_label_width
, label_width
);
121 max_label_width
= label_width
;
125 /* Right align the floating label up against the
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
;
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
;
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
,
188 if (fn
) fn(data
, text
);
190 return cancel_dialog(dlg_data
, widget_data
);
194 input_field(struct terminal
*term
, struct memory_list
*ml
, int intl
,
195 unsigned char *title
,
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 *))
206 unsigned char *field
;
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
);
219 /* @field is automatically cleared by calloc() */
220 field
= get_dialog_offset(dlg
, INPUT_WIDGETS_COUNT
);
223 int defsize
= strlen(def
) + 1;
225 memcpy(field
, def
, (defsize
> l
) ? l
- 1 : defsize
);
229 dlg
->layouter
= generic_dialog_layouter
;
230 dlg
->layout
.fit_datalen
= 1;
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
);
245 input_dialog(struct terminal
*term
, struct memory_list
*ml
,
246 unsigned char *title
,
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"),
257 check
, fn
, cancelfn
);
260 static widget_handler_status_T
261 display_field_do(struct dialog_data
*dlg_data
, struct widget_data
*widget_data
,
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");
275 draw_box(term
, &widget_data
->box
, ' ', 0, color
);
277 color
= get_bfu_color(term
, "dialog.field-text");
279 int len
= strlen(widget_data
->cdata
+ widget_data
->info
.field
.vpos
);
280 int w
= int_min(len
, widget_data
->box
.width
);
283 draw_text(term
, widget_data
->box
.x
, widget_data
->box
.y
,
284 widget_data
->cdata
+ widget_data
->info
.field
.vpos
, w
,
289 copy_box(&box
, &widget_data
->box
);
292 draw_box(term
, &box
, '*', 0, color
);
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
;
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
);
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
);
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
)) {
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
;
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
);
413 && !action_is_anonymous_safe(KEYMAP_EDIT
, action_id
)
414 && get_cmd_opt_bool("anonymous"))
415 return EVENT_NOT_PROCESSED
;
419 if (!widget_has_history(widget_data
))
420 return EVENT_NOT_PROCESSED
;
422 if (field_prev_history(widget_data
)) {
428 if (!widget_has_history(widget_data
))
429 return EVENT_NOT_PROCESSED
;
431 if (field_next_history(widget_data
)) {
437 if (widget_data
->info
.field
.cpos
< strlen(widget_data
->cdata
))
438 widget_data
->info
.field
.cpos
++;
442 if (widget_data
->info
.field
.cpos
> 0)
443 widget_data
->info
.field
.cpos
--;
447 widget_data
->info
.field
.cpos
= 0;
451 widget_data
->info
.field
.cpos
= strlen(widget_data
->cdata
);
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
--;
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);
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;
482 case ACT_EDIT_KILL_TO_EOL
:
483 widget_data
->cdata
[widget_data
->info
.field
.cpos
] = 0;
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;
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
);
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
);
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
);
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
);
532 case ACT_EDIT_REDRAW
:
533 redraw_terminal_cls(term
);
534 return EVENT_PROCESSED
;
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)
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
);
554 return EVENT_NOT_PROCESSED
;
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
= {
583 struct widget_ops field_pass_ops
= {
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
;
621 action_id
= kbd_action(KEYMAP_EDIT
, ev
, NULL
);
623 /* Handle some basic actions such as quiting for empty buffers */
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);
633 case ACT_EDIT_BACKSPACE
:
634 if (!*input_line
->buffer
)
635 goto cancel_input_line
;
638 case ACT_EDIT_CANCEL
:
639 goto cancel_input_line
;
645 /* First let the input field do its business */
646 kbd_field(dlg_data
, widget_data
);
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
;
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
;
671 action_id
= ACT_EDIT_REDRAW
;
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
:
685 cancel_dialog(dlg_data
, widget_data
);
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
:
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
;
712 input_field_line(struct session
*ses
, unsigned char *prompt
, void *data
,
713 struct input_history
*history
, input_line_handler_T handler
)
716 unsigned char *buffer
;
717 struct input_line
*input_line
;
721 dlg
= calloc_dialog(INPUT_LINE_WIDGETS
, sizeof(*input_line
));
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
,
738 do_dialog(ses
->tab
->term
, dlg
, getml(dlg
, NULL
));