1 /*
2 Widgets for the Midnight Commander
3
4 Copyright (C) 1994-2021
5 Free Software Foundation, Inc.
6
7 Authors:
8 Radek Doulik, 1994, 1995
9 Miguel de Icaza, 1994, 1995
10 Jakub Jelinek, 1995
11 Andrej Borsenkow, 1996
12 Norbert Warmuth, 1997
13 Andrew Borodin <aborodin@vmail.ru>, 2009-2016
14
15 This file is part of the Midnight Commander.
16
17 The Midnight Commander is free software: you can redistribute it
18 and/or modify it under the terms of the GNU General Public License as
19 published by the Free Software Foundation, either version 3 of the License,
20 or (at your option) any later version.
21
22 The Midnight Commander is distributed in the hope that it will be useful,
23 but WITHOUT ANY WARRANTY; without even the implied warranty of
24 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 GNU General Public License for more details.
26
27 You should have received a copy of the GNU General Public License
28 along with this program. If not, see <http://www.gnu.org/licenses/>.
29 */
30
31 /** \file input.c
32 * \brief Source: WInput widget
33 */
34
35 #include <config.h>
36
37 #include <stdlib.h>
38 #include <sys/types.h>
39 #include <sys/stat.h>
40
41 #include "lib/global.h"
42
43 #include "lib/tty/tty.h"
44 #include "lib/tty/key.h" /* XCTRL and ALT macros */
45 #include "lib/fileloc.h"
46 #include "lib/skin.h"
47 #include "lib/strutil.h"
48 #include "lib/util.h"
49 #include "lib/widget.h"
50 #include "lib/event.h" /* mc_event_raise() */
51 #include "lib/mcconfig.h" /* mc_config_history_*() */
52
53 /*** global variables ****************************************************************************/
54
55 gboolean quote = FALSE;
56
57 const global_keymap_t *input_map = NULL;
58
59 /* Color styles for input widgets */
60 input_colors_t input_colors;
61
62 /*** file scope macro definitions ****************************************************************/
63
64 #define LARGE_HISTORY_BUTTON 1
65
66 #ifdef LARGE_HISTORY_BUTTON
67 #define HISTORY_BUTTON_WIDTH 3
68 #else
69 #define HISTORY_BUTTON_WIDTH 1
70 #endif
71
72 #define should_show_history_button(in) \
73 (in->history.list != NULL && WIDGET (in)->cols > HISTORY_BUTTON_WIDTH * 2 + 1 \
74 && WIDGET (in)->owner != NULL)
75
76 /*** file scope type declarations ****************************************************************/
77
78 /*** file scope variables ************************************************************************/
79
80 /* Input widgets have a global kill ring */
81 /* Pointer to killed data */
82 static char *kill_buffer = NULL;
83
84 /*** file scope functions ************************************************************************/
85 /* --------------------------------------------------------------------------------------------- */
86
87 static size_t
get_history_length(GList * history)88 get_history_length (GList * history)
89 {
90 size_t len = 0;
91
92 for (; history != NULL; history = g_list_previous (history))
93 len++;
94
95 return len;
96 }
97
98 /* --------------------------------------------------------------------------------------------- */
99
100 static void
draw_history_button(WInput * in)101 draw_history_button (WInput * in)
102 {
103 char c;
104 gboolean disabled;
105
106 if (g_list_next (in->history.current) == NULL)
107 c = '^';
108 else if (g_list_previous (in->history.current) == NULL)
109 c = 'v';
110 else
111 c = '|';
112
113 widget_gotoyx (in, 0, WIDGET (in)->cols - HISTORY_BUTTON_WIDTH);
114 disabled = widget_get_state (WIDGET (in), WST_DISABLED);
115 tty_setcolor (disabled ? DISABLED_COLOR : in->color[WINPUTC_HISTORY]);
116
117 #ifdef LARGE_HISTORY_BUTTON
118 tty_print_string ("[ ]");
119 widget_gotoyx (in, 0, WIDGET (in)->cols - HISTORY_BUTTON_WIDTH + 1);
120 #endif
121
122 tty_print_char (c);
123 }
124
125 /* --------------------------------------------------------------------------------------------- */
126
127 static void
input_mark_cmd(WInput * in,gboolean mark)128 input_mark_cmd (WInput * in, gboolean mark)
129 {
130 in->mark = mark ? in->point : -1;
131 }
132
133 /* --------------------------------------------------------------------------------------------- */
134
135 static gboolean
input_eval_marks(WInput * in,long * start_mark,long * end_mark)136 input_eval_marks (WInput * in, long *start_mark, long *end_mark)
137 {
138 if (in->mark >= 0)
139 {
140 *start_mark = MIN (in->mark, in->point);
141 *end_mark = MAX (in->mark, in->point);
142 return TRUE;
143 }
144
145 *start_mark = *end_mark = -1;
146 return FALSE;
147 }
148
149 /* --------------------------------------------------------------------------------------------- */
150
151 static void
delete_region(WInput * in,int x_first,int x_last)152 delete_region (WInput * in, int x_first, int x_last)
153 {
154 int first = MIN (x_first, x_last);
155 int last = MAX (x_first, x_last);
156
157 input_mark_cmd (in, FALSE);
158 in->point = first;
159 last = str_offset_to_pos (in->buffer, last);
160 first = str_offset_to_pos (in->buffer, first);
161 str_move (in->buffer + first, in->buffer + last);
162 in->charpoint = 0;
163 in->need_push = TRUE;
164 }
165
166 /* --------------------------------------------------------------------------------------------- */
167
168 static void
do_show_hist(WInput * in)169 do_show_hist (WInput * in)
170 {
171 size_t len;
172 history_descriptor_t hd;
173
174 len = get_history_length (in->history.list);
175
176 history_descriptor_init (&hd, WIDGET (in)->y, WIDGET (in)->x, in->history.list,
177 g_list_position (in->history.list, in->history.list));
178 history_show (&hd);
179
180 /* in->history.list was destroyed in history_show().
181 * Apply new history and current postition to avoid use-after-free. */
182 in->history.list = hd.list;
183 in->history.current = in->history.list;
184 if (hd.text != NULL)
185 {
186 input_assign_text (in, hd.text);
187 g_free (hd.text);
188 }
189
190 /* Has history cleaned up or not? */
191 if (len != get_history_length (in->history.list))
192 in->history.changed = TRUE;
193 }
194
195 /* --------------------------------------------------------------------------------------------- */
196 /**
197 * Strip password from incomplete url (just user:pass@host without VFS prefix).
198 *
199 * @param url partial URL
200 * @return newly allocated string without password
201 */
202
203 static char *
input_history_strip_password(char * url)204 input_history_strip_password (char *url)
205 {
206 char *at, *delim, *colon;
207
208 at = strrchr (url, '@');
209 if (at == NULL)
210 return g_strdup (url);
211
212 /* TODO: handle ':' and '@' in password */
213
214 delim = strstr (url, VFS_PATH_URL_DELIMITER);
215 if (delim != NULL)
216 colon = strchr (delim + strlen (VFS_PATH_URL_DELIMITER), ':');
217 else
218 colon = strchr (url, ':');
219
220 /* if 'colon' before 'at', 'colon' delimits user and password: user:password@host */
221 /* if 'colon' after 'at', 'colon' delimits host and port: user@host:port */
222 if (colon != NULL && colon > at)
223 colon = NULL;
224
225 if (colon == NULL)
226 return g_strdup (url);
227 *colon = '\0';
228
229 return g_strconcat (url, at, (char *) NULL);
230 }
231
232 /* --------------------------------------------------------------------------------------------- */
233
234 static void
push_history(WInput * in,const char * text)235 push_history (WInput * in, const char *text)
236 {
237 char *t;
238 gboolean empty;
239
240 if (text == NULL)
241 return;
242
243 t = g_strstrip (g_strdup (text));
244 empty = *t == '\0';
245 g_free (t);
246 t = g_strdup (empty ? "" : text);
247
248 if (!empty && in->history.name != NULL && in->strip_password)
249 {
250 /*
251 We got string user:pass@host without any VFS prefixes
252 and vfs_path_to_str_flags (t, VPF_STRIP_PASSWORD) doesn't work.
253 Therefore we want to strip password in separate algorithm
254 */
255 char *url_with_stripped_password;
256
257 url_with_stripped_password = input_history_strip_password (t);
258 g_free (t);
259 t = url_with_stripped_password;
260 }
261
262 if (in->history.list == NULL || in->history.list->data == NULL
263 || strcmp (in->history.list->data, t) != 0 || in->history.changed)
264 {
265 in->history.list = list_append_unique (in->history.list, t);
266 in->history.current = in->history.list;
267 in->history.changed = TRUE;
268 }
269 else
270 g_free (t);
271
272 in->need_push = FALSE;
273 }
274
275 /* --------------------------------------------------------------------------------------------- */
276
277 static void
move_buffer_backward(WInput * in,int start,int end)278 move_buffer_backward (WInput * in, int start, int end)
279 {
280 int i, pos, len;
281 int str_len;
282
283 str_len = str_length (in->buffer);
284 if (start >= str_len || end > str_len + 1)
285 return;
286
287 pos = str_offset_to_pos (in->buffer, start);
288 len = str_offset_to_pos (in->buffer, end) - pos;
289
290 for (i = pos; in->buffer[i + len - 1]; i++)
291 in->buffer[i] = in->buffer[i + len];
292 }
293
294 /* --------------------------------------------------------------------------------------------- */
295
296 static cb_ret_t
insert_char(WInput * in,int c_code)297 insert_char (WInput * in, int c_code)
298 {
299 int res;
300 long m1, m2;
301
302 if (input_eval_marks (in, &m1, &m2))
303 delete_region (in, m1, m2);
304
305 if (c_code == -1)
306 return MSG_NOT_HANDLED;
307
308 if (in->charpoint >= MB_LEN_MAX)
309 return MSG_HANDLED;
310
311 in->charbuf[in->charpoint] = c_code;
312 in->charpoint++;
313
314 res = str_is_valid_char (in->charbuf, in->charpoint);
315 if (res < 0)
316 {
317 if (res != -2)
318 in->charpoint = 0; /* broken multibyte char, skip */
319 return MSG_HANDLED;
320 }
321
322 in->need_push = TRUE;
323 if (strlen (in->buffer) + 1 + in->charpoint >= in->current_max_size)
324 {
325 /* Expand the buffer */
326 size_t new_length;
327 char *narea;
328
329 new_length = in->current_max_size + WIDGET (in)->cols + in->charpoint;
330 narea = g_try_renew (char, in->buffer, new_length);
331 if (narea != NULL)
332 {
333 in->buffer = narea;
334 in->current_max_size = new_length;
335 }
336 }
337
338 if (strlen (in->buffer) + in->charpoint < in->current_max_size)
339 {
340 size_t i;
341 /* bytes from begin */
342 size_t ins_point = str_offset_to_pos (in->buffer, in->point);
343 /* move chars */
344 size_t rest_bytes = strlen (in->buffer + ins_point);
345
346 for (i = rest_bytes + 1; i > 0; i--)
347 in->buffer[ins_point + i + in->charpoint - 1] = in->buffer[ins_point + i - 1];
348
349 memcpy (in->buffer + ins_point, in->charbuf, in->charpoint);
350 in->point++;
351 }
352
353 in->charpoint = 0;
354 return MSG_HANDLED;
355 }
356
357 /* --------------------------------------------------------------------------------------------- */
358
359 static void
beginning_of_line(WInput * in)360 beginning_of_line (WInput * in)
361 {
362 in->point = 0;
363 in->charpoint = 0;
364 }
365
366 /* --------------------------------------------------------------------------------------------- */
367
368 static void
end_of_line(WInput * in)369 end_of_line (WInput * in)
370 {
371 in->point = str_length (in->buffer);
372 in->charpoint = 0;
373 }
374
375 /* --------------------------------------------------------------------------------------------- */
376
377 static void
backward_char(WInput * in)378 backward_char (WInput * in)
379 {
380 const char *act;
381
382 act = in->buffer + str_offset_to_pos (in->buffer, in->point);
383 if (in->point > 0)
384 in->point -= str_cprev_noncomb_char (&act, in->buffer);
385 in->charpoint = 0;
386 }
387
388 /* --------------------------------------------------------------------------------------------- */
389
390 static void
forward_char(WInput * in)391 forward_char (WInput * in)
392 {
393 const char *act;
394
395 act = in->buffer + str_offset_to_pos (in->buffer, in->point);
396 if (act[0] != '\0')
397 in->point += str_cnext_noncomb_char (&act);
398 in->charpoint = 0;
399 }
400
401 /* --------------------------------------------------------------------------------------------- */
402
403 static void
forward_word(WInput * in)404 forward_word (WInput * in)
405 {
406 const char *p;
407
408 p = in->buffer + str_offset_to_pos (in->buffer, in->point);
409 while (p[0] != '\0' && (str_isspace (p) || str_ispunct (p)))
410 {
411 str_cnext_char (&p);
412 in->point++;
413 }
414 while (p[0] != '\0' && !str_isspace (p) && !str_ispunct (p))
415 {
416 str_cnext_char (&p);
417 in->point++;
418 }
419 }
420
421 /* --------------------------------------------------------------------------------------------- */
422
423 static void
backward_word(WInput * in)424 backward_word (WInput * in)
425 {
426 const char *p;
427
428 p = in->buffer + str_offset_to_pos (in->buffer, in->point);
429
430 while (p != in->buffer)
431 {
432 const char *p_tmp;
433
434 p_tmp = p;
435 str_cprev_char (&p);
436 if (!str_isspace (p) && !str_ispunct (p))
437 {
438 p = p_tmp;
439 break;
440 }
441 in->point--;
442 }
443 while (p != in->buffer)
444 {
445 str_cprev_char (&p);
446 if (str_isspace (p) || str_ispunct (p))
447 break;
448
449 in->point--;
450 }
451 }
452
453 /* --------------------------------------------------------------------------------------------- */
454
455 static void
backward_delete(WInput * in)456 backward_delete (WInput * in)
457 {
458 const char *act = in->buffer + str_offset_to_pos (in->buffer, in->point);
459 int start;
460
461 if (in->point == 0)
462 return;
463
464 start = in->point - str_cprev_noncomb_char (&act, in->buffer);
465 move_buffer_backward (in, start, in->point);
466 in->charpoint = 0;
467 in->need_push = TRUE;
468 in->point = start;
469 }
470
471 /* --------------------------------------------------------------------------------------------- */
472
473 static void
delete_char(WInput * in)474 delete_char (WInput * in)
475 {
476 const char *act;
477 int end = in->point;
478
479 act = in->buffer + str_offset_to_pos (in->buffer, in->point);
480 end += str_cnext_noncomb_char (&act);
481
482 move_buffer_backward (in, in->point, end);
483 in->charpoint = 0;
484 in->need_push = TRUE;
485 }
486
487 /* --------------------------------------------------------------------------------------------- */
488
489 static void
copy_region(WInput * in,int x_first,int x_last)490 copy_region (WInput * in, int x_first, int x_last)
491 {
492 int first = MIN (x_first, x_last);
493 int last = MAX (x_first, x_last);
494
495 if (last == first)
496 {
497 /* Copy selected files to clipboard */
498 mc_event_raise (MCEVENT_GROUP_FILEMANAGER, "panel_save_current_file_to_clip_file", NULL);
499 /* try use external clipboard utility */
500 mc_event_raise (MCEVENT_GROUP_CORE, "clipboard_file_to_ext_clip", NULL);
501 return;
502 }
503
504 g_free (kill_buffer);
505
506 first = str_offset_to_pos (in->buffer, first);
507 last = str_offset_to_pos (in->buffer, last);
508
509 kill_buffer = g_strndup (in->buffer + first, last - first);
510
511 mc_event_raise (MCEVENT_GROUP_CORE, "clipboard_text_to_file", kill_buffer);
512 /* try use external clipboard utility */
513 mc_event_raise (MCEVENT_GROUP_CORE, "clipboard_file_to_ext_clip", NULL);
514 }
515
516 /* --------------------------------------------------------------------------------------------- */
517
518 static void
kill_word(WInput * in)519 kill_word (WInput * in)
520 {
521 int old_point = in->point;
522 int new_point;
523
524 forward_word (in);
525 new_point = in->point;
526 in->point = old_point;
527
528 delete_region (in, old_point, new_point);
529 in->need_push = TRUE;
530 in->charpoint = 0;
531 }
532
533 /* --------------------------------------------------------------------------------------------- */
534
535 static void
back_kill_word(WInput * in)536 back_kill_word (WInput * in)
537 {
538 int old_point = in->point;
539 int new_point;
540
541 backward_word (in);
542 new_point = in->point;
543 in->point = old_point;
544
545 delete_region (in, old_point, new_point);
546 in->need_push = TRUE;
547 }
548
549 /* --------------------------------------------------------------------------------------------- */
550
551 static void
yank(WInput * in)552 yank (WInput * in)
553 {
554 if (kill_buffer != NULL)
555 {
556 char *p;
557
558 in->charpoint = 0;
559 for (p = kill_buffer; *p != '\0'; p++)
560 insert_char (in, *p);
561 in->charpoint = 0;
562 }
563 }
564
565 /* --------------------------------------------------------------------------------------------- */
566
567 static void
kill_line(WInput * in)568 kill_line (WInput * in)
569 {
570 int chp;
571
572 chp = str_offset_to_pos (in->buffer, in->point);
573 g_free (kill_buffer);
574 kill_buffer = g_strdup (&in->buffer[chp]);
575 in->buffer[chp] = '\0';
576 in->charpoint = 0;
577 }
578
579 /* --------------------------------------------------------------------------------------------- */
580
581 static void
clear_line(WInput * in)582 clear_line (WInput * in)
583 {
584 in->need_push = TRUE;
585 in->buffer[0] = '\0';
586 in->point = 0;
587 in->mark = -1;
588 in->charpoint = 0;
589 }
590
591 /* --------------------------------------------------------------------------------------------- */
592
593 static void
ins_from_clip(WInput * in)594 ins_from_clip (WInput * in)
595 {
596 char *p = NULL;
597 ev_clipboard_text_from_file_t event_data;
598
599 /* try use external clipboard utility */
600 mc_event_raise (MCEVENT_GROUP_CORE, "clipboard_file_from_ext_clip", NULL);
601
602 event_data.text = &p;
603 mc_event_raise (MCEVENT_GROUP_CORE, "clipboard_text_from_file", &event_data);
604 if (event_data.ret)
605 {
606 char *pp;
607
608 for (pp = p; *pp != '\0'; pp++)
609 insert_char (in, *pp);
610
611 g_free (p);
612 }
613 }
614
615 /* --------------------------------------------------------------------------------------------- */
616
617 static void
hist_prev(WInput * in)618 hist_prev (WInput * in)
619 {
620 GList *prev;
621
622 if (in->history.list == NULL)
623 return;
624
625 if (in->need_push)
626 push_history (in, in->buffer);
627
628 prev = g_list_previous (in->history.current);
629 if (prev != NULL)
630 {
631 input_assign_text (in, (char *) prev->data);
632 in->history.current = prev;
633 in->history.changed = TRUE;
634 in->need_push = FALSE;
635 }
636 }
637
638 /* --------------------------------------------------------------------------------------------- */
639
640 static void
hist_next(WInput * in)641 hist_next (WInput * in)
642 {
643 GList *next;
644
645 if (in->need_push)
646 {
647 push_history (in, in->buffer);
648 input_assign_text (in, "");
649 return;
650 }
651
652 if (in->history.list == NULL)
653 return;
654
655 next = g_list_next (in->history.current);
656 if (next == NULL)
657 {
658 input_assign_text (in, "");
659 in->history.current = in->history.list;
660 }
661 else
662 {
663 input_assign_text (in, (char *) next->data);
664 in->history.current = next;
665 in->history.changed = TRUE;
666 in->need_push = FALSE;
667 }
668 }
669
670 /* --------------------------------------------------------------------------------------------- */
671
672 static void
port_region_marked_for_delete(WInput * in)673 port_region_marked_for_delete (WInput * in)
674 {
675 in->buffer[0] = '\0';
676 in->point = 0;
677 in->first = FALSE;
678 in->charpoint = 0;
679 }
680
681 /* --------------------------------------------------------------------------------------------- */
682
683 static cb_ret_t
input_execute_cmd(WInput * in,long command)684 input_execute_cmd (WInput * in, long command)
685 {
686 cb_ret_t res = MSG_HANDLED;
687
688 switch (command)
689 {
690 case CK_MarkLeft:
691 case CK_MarkRight:
692 case CK_MarkToWordBegin:
693 case CK_MarkToWordEnd:
694 case CK_MarkToHome:
695 case CK_MarkToEnd:
696 /* a highlight command like shift-arrow */
697 if (in->mark < 0)
698 {
699 input_mark_cmd (in, FALSE); /* clear */
700 input_mark_cmd (in, TRUE); /* marking on */
701 }
702 break;
703 case CK_WordRight:
704 case CK_WordLeft:
705 case CK_Right:
706 case CK_Left:
707 if (in->mark >= 0)
708 input_mark_cmd (in, FALSE);
709 break;
710 default:
711 break;
712 }
713
714 switch (command)
715 {
716 case CK_Home:
717 case CK_MarkToHome:
718 beginning_of_line (in);
719 break;
720 case CK_End:
721 case CK_MarkToEnd:
722 end_of_line (in);
723 break;
724 case CK_Left:
725 case CK_MarkLeft:
726 backward_char (in);
727 break;
728 case CK_WordLeft:
729 case CK_MarkToWordBegin:
730 backward_word (in);
731 break;
732 case CK_Right:
733 case CK_MarkRight:
734 forward_char (in);
735 break;
736 case CK_WordRight:
737 case CK_MarkToWordEnd:
738 forward_word (in);
739 break;
740 case CK_BackSpace:
741 {
742 long m1, m2;
743
744 if (input_eval_marks (in, &m1, &m2))
745 delete_region (in, m1, m2);
746 else
747 backward_delete (in);
748 }
749 break;
750 case CK_Delete:
751 if (in->first)
752 port_region_marked_for_delete (in);
753 else
754 {
755 long m1, m2;
756
757 if (input_eval_marks (in, &m1, &m2))
758 delete_region (in, m1, m2);
759 else
760 delete_char (in);
761 }
762 break;
763 case CK_DeleteToWordEnd:
764 kill_word (in);
765 break;
766 case CK_DeleteToWordBegin:
767 back_kill_word (in);
768 break;
769 case CK_Mark:
770 input_mark_cmd (in, TRUE);
771 break;
772 case CK_Remove:
773 delete_region (in, in->point, MAX (in->mark, 0));
774 break;
775 case CK_DeleteToEnd:
776 kill_line (in);
777 break;
778 case CK_Clear:
779 clear_line (in);
780 break;
781 case CK_Store:
782 copy_region (in, MAX (in->mark, 0), in->point);
783 break;
784 case CK_Cut:
785 {
786 long m;
787
788 m = MAX (in->mark, 0);
789 copy_region (in, m, in->point);
790 delete_region (in, in->point, m);
791 }
792 break;
793 case CK_Yank:
794 yank (in);
795 break;
796 case CK_Paste:
797 ins_from_clip (in);
798 break;
799 case CK_HistoryPrev:
800 hist_prev (in);
801 break;
802 case CK_HistoryNext:
803 hist_next (in);
804 break;
805 case CK_History:
806 do_show_hist (in);
807 break;
808 case CK_Complete:
809 input_complete (in);
810 break;
811 default:
812 res = MSG_NOT_HANDLED;
813 }
814
815 switch (command)
816 {
817 case CK_MarkLeft:
818 case CK_MarkRight:
819 case CK_MarkToWordBegin:
820 case CK_MarkToWordEnd:
821 case CK_MarkToHome:
822 case CK_MarkToEnd:
823 /* do nothing */
824 break;
825 default:
826 in->mark = -1;
827 break;
828 }
829
830 return res;
831 }
832
833 /* --------------------------------------------------------------------------------------------- */
834
835 /* "history_load" event handler */
836 static gboolean
input_load_history(const gchar * event_group_name,const gchar * event_name,gpointer init_data,gpointer data)837 input_load_history (const gchar * event_group_name, const gchar * event_name,
838 gpointer init_data, gpointer data)
839 {
840 WInput *in = INPUT (init_data);
841 ev_history_load_save_t *ev = (ev_history_load_save_t *) data;
842
843 (void) event_group_name;
844 (void) event_name;
845
846 in->history.list = mc_config_history_load (ev->cfg, in->history.name);
847 in->history.current = in->history.list;
848
849 if (in->init_from_history)
850 {
851 const char *def_text = "";
852
853 if (in->history.list != NULL && in->history.list->data != NULL)
854 def_text = (const char *) in->history.list->data;
855
856 input_assign_text (in, def_text);
857 }
858
859 return TRUE;
860 }
861
862 /* --------------------------------------------------------------------------------------------- */
863
864 /* "history_save" event handler */
865 static gboolean
input_save_history(const gchar * event_group_name,const gchar * event_name,gpointer init_data,gpointer data)866 input_save_history (const gchar * event_group_name, const gchar * event_name,
867 gpointer init_data, gpointer data)
868 {
869 WInput *in = INPUT (init_data);
870
871 (void) event_group_name;
872 (void) event_name;
873
874 if (!in->is_password && (DIALOG (WIDGET (in)->owner)->ret_value != B_CANCEL))
875 {
876 ev_history_load_save_t *ev = (ev_history_load_save_t *) data;
877
878 push_history (in, in->buffer);
879 if (in->history.changed)
880 mc_config_history_save (ev->cfg, in->history.name, in->history.list);
881 in->history.changed = FALSE;
882 }
883
884 return TRUE;
885 }
886
887 /* --------------------------------------------------------------------------------------------- */
888
889 static void
input_destroy(WInput * in)890 input_destroy (WInput * in)
891 {
892 if (in == NULL)
893 {
894 fprintf (stderr, "Internal error: null Input *\n");
895 exit (EXIT_FAILURE);
896 }
897
898 input_complete_free (in);
899
900 /* clean history */
901 if (in->history.list != NULL)
902 {
903 /* history is already saved before this moment */
904 in->history.list = g_list_first (in->history.list);
905 g_list_free_full (in->history.list, g_free);
906 }
907 g_free (in->history.name);
908 g_free (in->buffer);
909 MC_PTR_FREE (kill_buffer);
910 }
911
912 /* --------------------------------------------------------------------------------------------- */
913
914 /**
915 * Calculates the buffer index (aka "point") corresponding to some screen coordinate.
916 */
917 static int
input_screen_to_point(const WInput * in,int x)918 input_screen_to_point (const WInput * in, int x)
919 {
920 x += in->term_first_shown;
921
922 if (x < 0)
923 return 0;
924
925 if (x < str_term_width1 (in->buffer))
926 return str_column_to_pos (in->buffer, x);
927
928 return str_length (in->buffer);
929 }
930
931 /* --------------------------------------------------------------------------------------------- */
932
933 static void
input_mouse_callback(Widget * w,mouse_msg_t msg,mouse_event_t * event)934 input_mouse_callback (Widget * w, mouse_msg_t msg, mouse_event_t * event)
935 {
936 /* save point between MSG_MOUSE_DOWN and MSG_MOUSE_DRAG */
937 static int prev_point = 0;
938 WInput *in = INPUT (w);
939
940 switch (msg)
941 {
942 case MSG_MOUSE_DOWN:
943 widget_select (w);
944
945 if (event->x >= w->cols - HISTORY_BUTTON_WIDTH && should_show_history_button (in))
946 do_show_hist (in);
947 else
948 {
949 in->first = FALSE;
950 input_mark_cmd (in, FALSE);
951 input_set_point (in, input_screen_to_point (in, event->x));
952 /* save point for the possible following MSG_MOUSE_DRAG action */
953 prev_point = in->point;
954 }
955 break;
956
957 case MSG_MOUSE_DRAG:
958 /* start point: set marker using point before first MSG_MOUSE_DRAG action */
959 if (in->mark < 0)
960 in->mark = prev_point;
961
962 input_set_point (in, input_screen_to_point (in, event->x));
963 break;
964
965 default:
966 /* don't create highlight region of 0 length */
967 if (in->mark == in->point)
968 input_mark_cmd (in, FALSE);
969 break;
970 }
971 }
972
973 /* --------------------------------------------------------------------------------------------- */
974 /*** public functions ****************************************************************************/
975 /* --------------------------------------------------------------------------------------------- */
976
977 /** Create new instance of WInput object.
978 * @param y Y coordinate
979 * @param x X coordinate
980 * @param input_colors Array of used colors
981 * @param width Widget width
982 * @param def_text Default text filled in widget
983 * @param histname Name of history
984 * @param completion_flags Flags for specify type of completions
985 * @return WInput object
986 */
987 WInput *
input_new(int y,int x,const int * colors,int width,const char * def_text,const char * histname,input_complete_t completion_flags)988 input_new (int y, int x, const int *colors, int width, const char *def_text,
989 const char *histname, input_complete_t completion_flags)
990 {
991 WInput *in;
992 Widget *w;
993
994 in = g_new (WInput, 1);
995 w = WIDGET (in);
996 widget_init (w, y, x, 1, width, input_callback, input_mouse_callback);
997 w->options |= WOP_SELECTABLE | WOP_IS_INPUT | WOP_WANT_CURSOR;
998 w->keymap = input_map;
999
1000 in->color = colors;
1001 in->first = TRUE;
1002 in->mark = -1;
1003 in->term_first_shown = 0;
1004 in->disable_update = 0;
1005 in->is_password = FALSE;
1006 in->strip_password = FALSE;
1007
1008 /* in->buffer will be corrected in "history_load" event handler */
1009 in->current_max_size = width + 1;
1010 in->buffer = g_new0 (char, in->current_max_size);
1011
1012 /* init completions before input_assign_text() call */
1013 in->completions = NULL;
1014 in->completion_flags = completion_flags;
1015
1016 in->init_from_history = (def_text == INPUT_LAST_TEXT);
1017
1018 if (in->init_from_history || def_text == NULL)
1019 def_text = "";
1020
1021 input_assign_text (in, def_text);
1022
1023 /* prepare to history setup */
1024 in->history.list = NULL;
1025 in->history.current = NULL;
1026 in->history.changed = FALSE;
1027 in->history.name = NULL;
1028 if ((histname != NULL) && (*histname != '\0'))
1029 in->history.name = g_strdup (histname);
1030 /* history will be loaded later */
1031
1032 in->label = NULL;
1033
1034 return in;
1035 }
1036
1037 /* --------------------------------------------------------------------------------------------- */
1038
1039 cb_ret_t
input_callback(Widget * w,Widget * sender,widget_msg_t msg,int parm,void * data)1040 input_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
1041 {
1042 WInput *in = INPUT (w);
1043 WDialog *h = DIALOG (w->owner);
1044 cb_ret_t v;
1045
1046 switch (msg)
1047 {
1048 case MSG_INIT:
1049 /* subscribe to "history_load" event */
1050 mc_event_add (h->event_group, MCEVENT_HISTORY_LOAD, input_load_history, w, NULL);
1051 /* subscribe to "history_save" event */
1052 mc_event_add (h->event_group, MCEVENT_HISTORY_SAVE, input_save_history, w, NULL);
1053 if (in->label != NULL)
1054 widget_set_state (WIDGET (in->label), WST_DISABLED, widget_get_state (w, WST_DISABLED));
1055 return MSG_HANDLED;
1056
1057 case MSG_KEY:
1058 if (parm == XCTRL ('q'))
1059 {
1060 quote = TRUE;
1061 v = input_handle_char (in, ascii_alpha_to_cntrl (tty_getch ()));
1062 quote = FALSE;
1063 return v;
1064 }
1065
1066 /* Keys we want others to handle */
1067 if (parm == KEY_UP || parm == KEY_DOWN || parm == ESC_CHAR
1068 || parm == KEY_F (10) || parm == '\n')
1069 return MSG_NOT_HANDLED;
1070
1071 /* When pasting multiline text, insert literal Enter */
1072 if ((parm & ~KEY_M_MASK) == '\n')
1073 {
1074 quote = TRUE;
1075 v = input_handle_char (in, '\n');
1076 quote = FALSE;
1077 return v;
1078 }
1079
1080 return input_handle_char (in, parm);
1081
1082 case MSG_ACTION:
1083 return input_execute_cmd (in, parm);
1084
1085 case MSG_DRAW:
1086 input_update (in, FALSE);
1087 return MSG_HANDLED;
1088
1089 case MSG_ENABLE:
1090 case MSG_DISABLE:
1091 if (in->label != NULL)
1092 widget_set_state (WIDGET (in->label), WST_DISABLED, msg == MSG_DISABLE);
1093 return MSG_HANDLED;
1094
1095 case MSG_CURSOR:
1096 widget_gotoyx (in, 0, str_term_width2 (in->buffer, in->point) - in->term_first_shown);
1097 return MSG_HANDLED;
1098
1099 case MSG_DESTROY:
1100 /* unsubscribe from "history_load" event */
1101 mc_event_del (h->event_group, MCEVENT_HISTORY_LOAD, input_load_history, w);
1102 /* unsubscribe from "history_save" event */
1103 mc_event_del (h->event_group, MCEVENT_HISTORY_SAVE, input_save_history, w);
1104 input_destroy (in);
1105 return MSG_HANDLED;
1106
1107 default:
1108 return widget_default_callback (w, sender, msg, parm, data);
1109 }
1110 }
1111
1112 /* --------------------------------------------------------------------------------------------- */
1113
1114 void
input_set_default_colors(void)1115 input_set_default_colors (void)
1116 {
1117 input_colors[WINPUTC_MAIN] = INPUT_COLOR;
1118 input_colors[WINPUTC_MARK] = INPUT_MARK_COLOR;
1119 input_colors[WINPUTC_UNCHANGED] = INPUT_UNCHANGED_COLOR;
1120 input_colors[WINPUTC_HISTORY] = INPUT_HISTORY_COLOR;
1121 }
1122
1123 /* --------------------------------------------------------------------------------------------- */
1124
1125 cb_ret_t
input_handle_char(WInput * in,int key)1126 input_handle_char (WInput * in, int key)
1127 {
1128 cb_ret_t v;
1129 long command;
1130
1131 if (quote)
1132 {
1133 input_complete_free (in);
1134 v = insert_char (in, key);
1135 input_update (in, TRUE);
1136 quote = FALSE;
1137 return v;
1138 }
1139
1140 command = widget_lookup_key (WIDGET (in), key);
1141 if (command == CK_IgnoreKey)
1142 {
1143 if (key > 255)
1144 return MSG_NOT_HANDLED;
1145 if (in->first)
1146 port_region_marked_for_delete (in);
1147 input_complete_free (in);
1148 v = insert_char (in, key);
1149 input_update (in, TRUE);
1150 }
1151 else
1152 {
1153 gboolean keep_first;
1154
1155 if (command != CK_Complete)
1156 input_complete_free (in);
1157 input_execute_cmd (in, command);
1158 v = MSG_HANDLED;
1159 /* if in->first == TRUE and history or completion window was cancelled,
1160 keep "first" state */
1161 keep_first = in->first && (command == CK_History || command == CK_Complete);
1162 input_update (in, !keep_first);
1163 }
1164
1165 return v;
1166 }
1167
1168 /* --------------------------------------------------------------------------------------------- */
1169
1170 void
input_assign_text(WInput * in,const char * text)1171 input_assign_text (WInput * in, const char *text)
1172 {
1173 Widget *w = WIDGET (in);
1174 size_t text_len, buffer_len;
1175
1176 if (text == NULL)
1177 text = "";
1178
1179 input_complete_free (in);
1180 in->mark = -1;
1181 in->need_push = TRUE;
1182 in->charpoint = 0;
1183
1184 text_len = strlen (text);
1185 buffer_len = 1 + MAX ((size_t) w->cols, text_len);
1186 in->current_max_size = buffer_len;
1187 if (buffer_len > (size_t) w->cols)
1188 in->buffer = g_realloc (in->buffer, buffer_len);
1189 memmove (in->buffer, text, text_len + 1);
1190 in->point = str_length (in->buffer);
1191 input_update (in, TRUE);
1192 }
1193
1194 /* --------------------------------------------------------------------------------------------- */
1195
1196 gboolean
input_is_empty(const WInput * in)1197 input_is_empty (const WInput * in)
1198 {
1199 return (in == NULL || in->buffer == NULL || in->buffer[0] == '\0');
1200 }
1201
1202 /* --------------------------------------------------------------------------------------------- */
1203
1204 /* Inserts text in input line */
1205 void
input_insert(WInput * in,const char * text,gboolean insert_extra_space)1206 input_insert (WInput * in, const char *text, gboolean insert_extra_space)
1207 {
1208 input_disable_update (in);
1209 while (*text != '\0')
1210 input_handle_char (in, (unsigned char) *text++); /* unsigned extension char->int */
1211 if (insert_extra_space)
1212 input_handle_char (in, ' ');
1213 input_enable_update (in);
1214 input_update (in, TRUE);
1215 }
1216
1217 /* --------------------------------------------------------------------------------------------- */
1218
1219 void
input_set_point(WInput * in,int pos)1220 input_set_point (WInput * in, int pos)
1221 {
1222 int max_pos;
1223
1224 max_pos = str_length (in->buffer);
1225 pos = MIN (pos, max_pos);
1226 if (pos != in->point)
1227 input_complete_free (in);
1228 in->point = pos;
1229 in->charpoint = 0;
1230 input_update (in, TRUE);
1231 }
1232
1233 /* --------------------------------------------------------------------------------------------- */
1234
1235 void
input_update(WInput * in,gboolean clear_first)1236 input_update (WInput * in, gboolean clear_first)
1237 {
1238 Widget *w = WIDGET (in);
1239 int has_history = 0;
1240 int buf_len;
1241 const char *cp;
1242 int pw;
1243
1244 if (in->disable_update != 0)
1245 return;
1246
1247 /* don't draw widget not put into dialog */
1248 if (w->owner == NULL || !widget_get_state (WIDGET (w->owner), WST_ACTIVE))
1249 return;
1250
1251 if (clear_first)
1252 in->first = FALSE;
1253
1254 if (should_show_history_button (in))
1255 has_history = HISTORY_BUTTON_WIDTH;
1256
1257 buf_len = str_length (in->buffer);
1258
1259 /* Adjust the mark */
1260 in->mark = MIN (in->mark, buf_len);
1261
1262 pw = str_term_width2 (in->buffer, in->point);
1263
1264 /* Make the point visible */
1265 if ((pw < in->term_first_shown) || (pw >= in->term_first_shown + w->cols - has_history))
1266 {
1267 in->term_first_shown = pw - (w->cols / 3);
1268 if (in->term_first_shown < 0)
1269 in->term_first_shown = 0;
1270 }
1271
1272 if (has_history != 0)
1273 draw_history_button (in);
1274
1275 if (widget_get_state (w, WST_DISABLED))
1276 tty_setcolor (DISABLED_COLOR);
1277 else if (in->first)
1278 tty_setcolor (in->color[WINPUTC_UNCHANGED]);
1279 else
1280 tty_setcolor (in->color[WINPUTC_MAIN]);
1281
1282 widget_gotoyx (in, 0, 0);
1283
1284 if (!in->is_password)
1285 {
1286 if (in->mark < 0)
1287 tty_print_string (str_term_substring (in->buffer, in->term_first_shown,
1288 w->cols - has_history));
1289 else
1290 {
1291 long m1, m2;
1292
1293 if (input_eval_marks (in, &m1, &m2))
1294 {
1295 tty_setcolor (in->color[WINPUTC_MAIN]);
1296 cp = str_term_substring (in->buffer, in->term_first_shown, w->cols - has_history);
1297 tty_print_string (cp);
1298 tty_setcolor (in->color[WINPUTC_MARK]);
1299 if (m1 < in->term_first_shown)
1300 {
1301 widget_gotoyx (in, 0, 0);
1302 tty_print_string (str_term_substring
1303 (in->buffer, in->term_first_shown,
1304 m2 - in->term_first_shown));
1305 }
1306 else
1307 {
1308 int sel_width, buf_width;
1309
1310 widget_gotoyx (in, 0, m1 - in->term_first_shown);
1311 buf_width = str_term_width2 (in->buffer, m1);
1312 sel_width =
1313 MIN (m2 - m1, (w->cols - has_history) - (buf_width - in->term_first_shown));
1314 tty_print_string (str_term_substring (in->buffer, m1, sel_width));
1315 }
1316 }
1317 }
1318 }
1319 else
1320 {
1321 int i;
1322
1323 cp = str_term_substring (in->buffer, in->term_first_shown, w->cols - has_history);
1324 tty_setcolor (in->color[WINPUTC_MAIN]);
1325 for (i = 0; i < w->cols - has_history; i++)
1326 {
1327 if (i < (buf_len - in->term_first_shown) && cp[0] != '\0')
1328 tty_print_char ('*');
1329 else
1330 tty_print_char (' ');
1331 if (cp[0] != '\0')
1332 str_cnext_char (&cp);
1333 }
1334 }
1335 }
1336
1337 /* --------------------------------------------------------------------------------------------- */
1338
1339 void
input_enable_update(WInput * in)1340 input_enable_update (WInput * in)
1341 {
1342 in->disable_update--;
1343 input_update (in, FALSE);
1344 }
1345
1346 /* --------------------------------------------------------------------------------------------- */
1347
1348 void
input_disable_update(WInput * in)1349 input_disable_update (WInput * in)
1350 {
1351 in->disable_update++;
1352 }
1353
1354 /* --------------------------------------------------------------------------------------------- */
1355
1356 /**
1357 * Cleans the input line and adds the current text to the history
1358 *
1359 * @param in the input line
1360 */
1361 void
input_clean(WInput * in)1362 input_clean (WInput * in)
1363 {
1364 push_history (in, in->buffer);
1365 in->need_push = TRUE;
1366 in->buffer[0] = '\0';
1367 in->point = 0;
1368 in->charpoint = 0;
1369 in->mark = -1;
1370 input_complete_free (in);
1371 input_update (in, FALSE);
1372 }
1373
1374 /* --------------------------------------------------------------------------------------------- */
1375