diff options
Diffstat (limited to '')
-rw-r--r-- | lib/widget/input.c | 1323 |
1 files changed, 1323 insertions, 0 deletions
diff --git a/lib/widget/input.c b/lib/widget/input.c new file mode 100644 index 0000000..3a67b49 --- /dev/null +++ b/lib/widget/input.c @@ -0,0 +1,1323 @@ +/* + Widgets for the Midnight Commander + + Copyright (C) 1994-2023 + Free Software Foundation, Inc. + + Authors: + Radek Doulik, 1994, 1995 + Miguel de Icaza, 1994, 1995 + Jakub Jelinek, 1995 + Andrej Borsenkow, 1996 + Norbert Warmuth, 1997 + Andrew Borodin <aborodin@vmail.ru>, 2009-2022 + + This file is part of the Midnight Commander. + + The Midnight Commander is free software: you can redistribute it + and/or modify it under the terms of the GNU General Public License as + published by the Free Software Foundation, either version 3 of the License, + or (at your option) any later version. + + The Midnight Commander is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +/** \file input.c + * \brief Source: WInput widget + */ + +#include <config.h> + +#include <stdlib.h> +#include <sys/types.h> +#include <sys/stat.h> + +#include "lib/global.h" + +#include "lib/tty/tty.h" +#include "lib/tty/key.h" /* XCTRL and ALT macros */ +#include "lib/fileloc.h" +#include "lib/skin.h" +#include "lib/strutil.h" +#include "lib/util.h" +#include "lib/widget.h" +#include "lib/event.h" /* mc_event_raise() */ +#include "lib/mcconfig.h" /* mc_config_history_*() */ + +/*** global variables ****************************************************************************/ + +gboolean quote = FALSE; + +const global_keymap_t *input_map = NULL; + +/* Color styles for input widgets */ +input_colors_t input_colors; + +/*** file scope macro definitions ****************************************************************/ + +#define LARGE_HISTORY_BUTTON 1 + +#ifdef LARGE_HISTORY_BUTTON +#define HISTORY_BUTTON_WIDTH 3 +#else +#define HISTORY_BUTTON_WIDTH 1 +#endif + +#define should_show_history_button(in) \ + (in->history.list != NULL && WIDGET (in)->rect.cols > HISTORY_BUTTON_WIDTH * 2 + 1 \ + && WIDGET (in)->owner != NULL) + +/*** file scope type declarations ****************************************************************/ + +/*** forward declarations (file scope functions) *************************************************/ + +/*** file scope variables ************************************************************************/ + +/* Input widgets have a global kill ring */ +/* Pointer to killed data */ +static char *kill_buffer = NULL; + +/* --------------------------------------------------------------------------------------------- */ +/*** file scope functions ************************************************************************/ +/* --------------------------------------------------------------------------------------------- */ + +static size_t +get_history_length (GList * history) +{ + size_t len = 0; + + for (; history != NULL; history = g_list_previous (history)) + len++; + + return len; +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +draw_history_button (WInput * in) +{ + char c; + gboolean disabled; + + if (g_list_next (in->history.current) == NULL) + c = '^'; + else if (g_list_previous (in->history.current) == NULL) + c = 'v'; + else + c = '|'; + + widget_gotoyx (in, 0, WIDGET (in)->rect.cols - HISTORY_BUTTON_WIDTH); + disabled = widget_get_state (WIDGET (in), WST_DISABLED); + tty_setcolor (disabled ? DISABLED_COLOR : in->color[WINPUTC_HISTORY]); + +#ifdef LARGE_HISTORY_BUTTON + tty_print_string ("[ ]"); + widget_gotoyx (in, 0, WIDGET (in)->rect.cols - HISTORY_BUTTON_WIDTH + 1); +#endif + + tty_print_char (c); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +input_mark_cmd (WInput * in, gboolean mark) +{ + in->mark = mark ? in->point : -1; +} + +/* --------------------------------------------------------------------------------------------- */ + +static gboolean +input_eval_marks (WInput * in, long *start_mark, long *end_mark) +{ + if (in->mark >= 0) + { + *start_mark = MIN (in->mark, in->point); + *end_mark = MAX (in->mark, in->point); + return TRUE; + } + + *start_mark = *end_mark = -1; + return FALSE; +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +do_show_hist (WInput * in) +{ + size_t len; + history_descriptor_t hd; + + len = get_history_length (in->history.list); + + history_descriptor_init (&hd, WIDGET (in)->rect.y, WIDGET (in)->rect.x, in->history.list, + g_list_position (in->history.list, in->history.list)); + history_show (&hd); + + /* in->history.list was destroyed in history_show(). + * Apply new history and current position to avoid use-after-free. */ + in->history.list = hd.list; + in->history.current = in->history.list; + if (hd.text != NULL) + { + input_assign_text (in, hd.text); + g_free (hd.text); + } + + /* Has history cleaned up or not? */ + if (len != get_history_length (in->history.list)) + in->history.changed = TRUE; +} + +/* --------------------------------------------------------------------------------------------- */ +/** + * Strip password from incomplete url (just user:pass@host without VFS prefix). + * + * @param url partial URL + * @return newly allocated string without password + */ + +static char * +input_history_strip_password (char *url) +{ + char *at, *delim, *colon; + + at = strrchr (url, '@'); + if (at == NULL) + return g_strdup (url); + + /* TODO: handle ':' and '@' in password */ + + delim = strstr (url, VFS_PATH_URL_DELIMITER); + if (delim != NULL) + colon = strchr (delim + strlen (VFS_PATH_URL_DELIMITER), ':'); + else + colon = strchr (url, ':'); + + /* if 'colon' before 'at', 'colon' delimits user and password: user:password@host */ + /* if 'colon' after 'at', 'colon' delimits host and port: user@host:port */ + if (colon != NULL && colon > at) + colon = NULL; + + if (colon == NULL) + return g_strdup (url); + *colon = '\0'; + + return g_strconcat (url, at, (char *) NULL); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +input_push_history (WInput * in) +{ + char *t; + gboolean empty; + + t = g_strstrip (input_get_text (in)); + empty = *t == '\0'; + if (!empty) + { + g_free (t); + t = input_get_text (in); + + if (in->history.name != NULL && in->strip_password) + { + /* + We got string user:pass@host without any VFS prefixes + and vfs_path_to_str_flags (t, VPF_STRIP_PASSWORD) doesn't work. + Therefore we want to strip password in separate algorithm + */ + char *url_with_stripped_password; + + url_with_stripped_password = input_history_strip_password (t); + g_free (t); + t = url_with_stripped_password; + } + } + + if (in->history.list == NULL || in->history.list->data == NULL + || strcmp (in->history.list->data, t) != 0 || in->history.changed) + { + in->history.list = list_append_unique (in->history.list, t); + in->history.current = in->history.list; + in->history.changed = TRUE; + } + else + g_free (t); + + in->need_push = FALSE; +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +move_buffer_backward (WInput * in, int start, int end) +{ + int str_len; + + str_len = str_length (in->buffer->str); + if (start >= str_len || end > str_len + 1) + return; + + start = str_offset_to_pos (in->buffer->str, start); + end = str_offset_to_pos (in->buffer->str, end); + g_string_erase (in->buffer, start, end - start); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +beginning_of_line (WInput * in) +{ + in->point = 0; + in->charpoint = 0; +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +end_of_line (WInput * in) +{ + in->point = str_length (in->buffer->str); + in->charpoint = 0; +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +backward_char (WInput * in) +{ + if (in->point > 0) + { + const char *act; + + act = in->buffer->str + str_offset_to_pos (in->buffer->str, in->point); + in->point -= str_cprev_noncomb_char (&act, in->buffer->str); + } + + in->charpoint = 0; +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +forward_char (WInput * in) +{ + const char *act; + + act = in->buffer->str + str_offset_to_pos (in->buffer->str, in->point); + if (act[0] != '\0') + in->point += str_cnext_noncomb_char (&act); + in->charpoint = 0; +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +forward_word (WInput * in) +{ + const char *p; + + p = in->buffer->str + str_offset_to_pos (in->buffer->str, in->point); + + for (; p[0] != '\0' && (str_isspace (p) || str_ispunct (p)); in->point++) + str_cnext_char (&p); + + for (; p[0] != '\0' && !str_isspace (p) && !str_ispunct (p); in->point++) + str_cnext_char (&p); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +backward_word (WInput * in) +{ + const char *p; + + p = in->buffer->str + str_offset_to_pos (in->buffer->str, in->point); + + for (; p != in->buffer->str; in->point--) + { + const char *p_tmp; + + p_tmp = p; + str_cprev_char (&p); + if (!str_isspace (p) && !str_ispunct (p)) + { + p = p_tmp; + break; + } + } + + for (; p != in->buffer->str; in->point--) + { + str_cprev_char (&p); + if (str_isspace (p) || str_ispunct (p)) + break; + } +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +backward_delete (WInput * in) +{ + const char *act; + int start; + + if (in->point == 0) + return; + + act = in->buffer->str + str_offset_to_pos (in->buffer->str, in->point); + start = in->point - str_cprev_noncomb_char (&act, in->buffer->str); + move_buffer_backward (in, start, in->point); + in->charpoint = 0; + in->need_push = TRUE; + in->point = start; +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +copy_region (WInput * in, int start, int end) +{ + int first = MIN (start, end); + int last = MAX (start, end); + + if (last == first) + { + /* Copy selected files to clipboard */ + mc_event_raise (MCEVENT_GROUP_FILEMANAGER, "panel_save_current_file_to_clip_file", NULL); + /* try use external clipboard utility */ + mc_event_raise (MCEVENT_GROUP_CORE, "clipboard_file_to_ext_clip", NULL); + return; + } + + g_free (kill_buffer); + + first = str_offset_to_pos (in->buffer->str, first); + last = str_offset_to_pos (in->buffer->str, last); + + kill_buffer = g_strndup (in->buffer->str + first, last - first); + + mc_event_raise (MCEVENT_GROUP_CORE, "clipboard_text_to_file", kill_buffer); + /* try use external clipboard utility */ + mc_event_raise (MCEVENT_GROUP_CORE, "clipboard_file_to_ext_clip", NULL); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +delete_region (WInput * in, int start, int end) +{ + int first = MIN (start, end); + int last = MAX (start, end); + + input_mark_cmd (in, FALSE); + in->point = first; + move_buffer_backward (in, first, last); + in->charpoint = 0; + in->need_push = TRUE; +} + +/* --------------------------------------------------------------------------------------------- */ + +static cb_ret_t +insert_char (WInput * in, int c_code) +{ + int res; + long m1, m2; + size_t ins_point; + + if (input_eval_marks (in, &m1, &m2)) + delete_region (in, m1, m2); + + if (c_code == -1) + return MSG_NOT_HANDLED; + + if (in->charpoint >= MB_LEN_MAX) + return MSG_HANDLED; + + in->charbuf[in->charpoint] = c_code; + in->charpoint++; + + res = str_is_valid_char (in->charbuf, in->charpoint); + if (res < 0) + { + if (res != -2) + in->charpoint = 0; /* broken multibyte char, skip */ + return MSG_HANDLED; + } + + in->need_push = TRUE; + ins_point = str_offset_to_pos (in->buffer->str, in->point); + g_string_insert_len (in->buffer, ins_point, in->charbuf, in->charpoint); + in->point++; + in->charpoint = 0; + + return MSG_HANDLED; +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +delete_char (WInput * in) +{ + const char *act; + int end; + + act = in->buffer->str + str_offset_to_pos (in->buffer->str, in->point); + end = in->point + str_cnext_noncomb_char (&act); + move_buffer_backward (in, in->point, end); + in->charpoint = 0; + in->need_push = TRUE; +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +kill_word (WInput * in) +{ + int old_point = in->point; + int new_point; + + forward_word (in); + new_point = in->point; + in->point = old_point; + + delete_region (in, old_point, new_point); + in->need_push = TRUE; + in->charpoint = 0; +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +back_kill_word (WInput * in) +{ + int old_point = in->point; + int new_point; + + backward_word (in); + new_point = in->point; + in->point = old_point; + + delete_region (in, old_point, new_point); + in->need_push = TRUE; +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +yank (WInput * in) +{ + if (kill_buffer != NULL) + { + char *p; + + in->charpoint = 0; + for (p = kill_buffer; *p != '\0'; p++) + insert_char (in, *p); + in->charpoint = 0; + } +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +kill_line (WInput * in) +{ + int chp; + + chp = str_offset_to_pos (in->buffer->str, in->point); + g_free (kill_buffer); + kill_buffer = g_strndup (in->buffer->str + chp, in->buffer->len - chp); + g_string_set_size (in->buffer, chp); + in->charpoint = 0; +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +clear_line (WInput * in) +{ + in->need_push = TRUE; + g_string_set_size (in->buffer, 0); + in->point = 0; + in->mark = -1; + in->charpoint = 0; +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +ins_from_clip (WInput * in) +{ + char *p = NULL; + ev_clipboard_text_from_file_t event_data = { NULL, FALSE }; + + /* try use external clipboard utility */ + mc_event_raise (MCEVENT_GROUP_CORE, "clipboard_file_from_ext_clip", NULL); + + event_data.text = &p; + mc_event_raise (MCEVENT_GROUP_CORE, "clipboard_text_from_file", &event_data); + if (event_data.ret) + { + char *pp; + + for (pp = p; *pp != '\0'; pp++) + insert_char (in, *pp); + + g_free (p); + } +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +hist_prev (WInput * in) +{ + GList *prev; + + if (in->history.list == NULL) + return; + + if (in->need_push) + input_push_history (in); + + prev = g_list_previous (in->history.current); + if (prev != NULL) + { + input_assign_text (in, (char *) prev->data); + in->history.current = prev; + in->history.changed = TRUE; + in->need_push = FALSE; + } +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +hist_next (WInput * in) +{ + GList *next; + + if (in->need_push) + { + input_push_history (in); + input_assign_text (in, ""); + return; + } + + if (in->history.list == NULL) + return; + + next = g_list_next (in->history.current); + if (next == NULL) + { + input_assign_text (in, ""); + in->history.current = in->history.list; + } + else + { + input_assign_text (in, (char *) next->data); + in->history.current = next; + in->history.changed = TRUE; + in->need_push = FALSE; + } +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +port_region_marked_for_delete (WInput * in) +{ + g_string_set_size (in->buffer, 0); + in->point = 0; + in->first = FALSE; + in->charpoint = 0; +} + +/* --------------------------------------------------------------------------------------------- */ + +static cb_ret_t +input_execute_cmd (WInput * in, long command) +{ + cb_ret_t res = MSG_HANDLED; + + switch (command) + { + case CK_MarkLeft: + case CK_MarkRight: + case CK_MarkToWordBegin: + case CK_MarkToWordEnd: + case CK_MarkToHome: + case CK_MarkToEnd: + /* a highlight command like shift-arrow */ + if (in->mark < 0) + { + input_mark_cmd (in, FALSE); /* clear */ + input_mark_cmd (in, TRUE); /* marking on */ + } + break; + case CK_WordRight: + case CK_WordLeft: + case CK_Right: + case CK_Left: + if (in->mark >= 0) + input_mark_cmd (in, FALSE); + break; + default: + break; + } + + switch (command) + { + case CK_Home: + case CK_MarkToHome: + beginning_of_line (in); + break; + case CK_End: + case CK_MarkToEnd: + end_of_line (in); + break; + case CK_Left: + case CK_MarkLeft: + backward_char (in); + break; + case CK_WordLeft: + case CK_MarkToWordBegin: + backward_word (in); + break; + case CK_Right: + case CK_MarkRight: + forward_char (in); + break; + case CK_WordRight: + case CK_MarkToWordEnd: + forward_word (in); + break; + case CK_BackSpace: + { + long m1, m2; + + if (input_eval_marks (in, &m1, &m2)) + delete_region (in, m1, m2); + else + backward_delete (in); + } + break; + case CK_Delete: + if (in->first) + port_region_marked_for_delete (in); + else + { + long m1, m2; + + if (input_eval_marks (in, &m1, &m2)) + delete_region (in, m1, m2); + else + delete_char (in); + } + break; + case CK_DeleteToWordEnd: + kill_word (in); + break; + case CK_DeleteToWordBegin: + back_kill_word (in); + break; + case CK_Mark: + input_mark_cmd (in, TRUE); + break; + case CK_Remove: + delete_region (in, in->point, MAX (in->mark, 0)); + break; + case CK_DeleteToEnd: + kill_line (in); + break; + case CK_Clear: + clear_line (in); + break; + case CK_Store: + copy_region (in, MAX (in->mark, 0), in->point); + break; + case CK_Cut: + { + long m; + + m = MAX (in->mark, 0); + copy_region (in, m, in->point); + delete_region (in, in->point, m); + } + break; + case CK_Yank: + yank (in); + break; + case CK_Paste: + ins_from_clip (in); + break; + case CK_HistoryPrev: + hist_prev (in); + break; + case CK_HistoryNext: + hist_next (in); + break; + case CK_History: + do_show_hist (in); + break; + case CK_Complete: + input_complete (in); + break; + default: + res = MSG_NOT_HANDLED; + } + + switch (command) + { + case CK_MarkLeft: + case CK_MarkRight: + case CK_MarkToWordBegin: + case CK_MarkToWordEnd: + case CK_MarkToHome: + case CK_MarkToEnd: + /* do nothing */ + break; + default: + in->mark = -1; + break; + } + + return res; +} + +/* --------------------------------------------------------------------------------------------- */ + +/* "history_load" event handler */ +static gboolean +input_load_history (const gchar * event_group_name, const gchar * event_name, + gpointer init_data, gpointer data) +{ + WInput *in = INPUT (init_data); + ev_history_load_save_t *ev = (ev_history_load_save_t *) data; + + (void) event_group_name; + (void) event_name; + + in->history.list = mc_config_history_load (ev->cfg, in->history.name); + in->history.current = in->history.list; + + if (in->init_from_history) + { + const char *def_text = ""; + + if (in->history.list != NULL && in->history.list->data != NULL) + def_text = (const char *) in->history.list->data; + + input_assign_text (in, def_text); + } + + return TRUE; +} + +/* --------------------------------------------------------------------------------------------- */ + +/* "history_save" event handler */ +static gboolean +input_save_history (const gchar * event_group_name, const gchar * event_name, + gpointer init_data, gpointer data) +{ + WInput *in = INPUT (init_data); + + (void) event_group_name; + (void) event_name; + + if (!in->is_password && (DIALOG (WIDGET (in)->owner)->ret_value != B_CANCEL)) + { + ev_history_load_save_t *ev = (ev_history_load_save_t *) data; + + input_push_history (in); + if (in->history.changed) + mc_config_history_save (ev->cfg, in->history.name, in->history.list); + in->history.changed = FALSE; + } + + return TRUE; +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +input_destroy (WInput * in) +{ + input_complete_free (in); + + /* clean history */ + if (in->history.list != NULL) + { + /* history is already saved before this moment */ + in->history.list = g_list_first (in->history.list); + g_list_free_full (in->history.list, g_free); + } + g_free (in->history.name); + g_string_free (in->buffer, TRUE); + MC_PTR_FREE (kill_buffer); +} + +/* --------------------------------------------------------------------------------------------- */ + +/** + * Calculates the buffer index (aka "point") corresponding to some screen coordinate. + */ +static int +input_screen_to_point (const WInput * in, int x) +{ + x += in->term_first_shown; + + if (x < 0) + return 0; + + if (x < str_term_width1 (in->buffer->str)) + return str_column_to_pos (in->buffer->str, x); + + return str_length (in->buffer->str); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +input_mouse_callback (Widget * w, mouse_msg_t msg, mouse_event_t * event) +{ + /* save point between MSG_MOUSE_DOWN and MSG_MOUSE_DRAG */ + static int prev_point = 0; + WInput *in = INPUT (w); + + switch (msg) + { + case MSG_MOUSE_DOWN: + widget_select (w); + + if (event->x >= w->rect.cols - HISTORY_BUTTON_WIDTH && should_show_history_button (in)) + do_show_hist (in); + else + { + in->first = FALSE; + input_mark_cmd (in, FALSE); + input_set_point (in, input_screen_to_point (in, event->x)); + /* save point for the possible following MSG_MOUSE_DRAG action */ + prev_point = in->point; + } + break; + + case MSG_MOUSE_DRAG: + /* start point: set marker using point before first MSG_MOUSE_DRAG action */ + if (in->mark < 0) + in->mark = prev_point; + + input_set_point (in, input_screen_to_point (in, event->x)); + break; + + default: + /* don't create highlight region of 0 length */ + if (in->mark == in->point) + input_mark_cmd (in, FALSE); + break; + } +} + +/* --------------------------------------------------------------------------------------------- */ +/*** public functions ****************************************************************************/ +/* --------------------------------------------------------------------------------------------- */ + +/** Create new instance of WInput object. + * @param y Y coordinate + * @param x X coordinate + * @param input_colors Array of used colors + * @param width Widget width + * @param def_text Default text filled in widget + * @param histname Name of history + * @param completion_flags Flags for specify type of completions + * @return WInput object + */ +WInput * +input_new (int y, int x, const int *colors, int width, const char *def_text, + const char *histname, input_complete_t completion_flags) +{ + WRect r = { y, x, 1, width }; + WInput *in; + Widget *w; + + in = g_new (WInput, 1); + w = WIDGET (in); + widget_init (w, &r, input_callback, input_mouse_callback); + w->options |= WOP_SELECTABLE | WOP_IS_INPUT | WOP_WANT_CURSOR; + w->keymap = input_map; + + in->color = colors; + in->first = TRUE; + in->mark = -1; + in->term_first_shown = 0; + in->disable_update = 0; + in->is_password = FALSE; + in->strip_password = FALSE; + + /* in->buffer will be corrected in "history_load" event handler */ + in->buffer = g_string_sized_new (width); + + /* init completions before input_assign_text() call */ + in->completions = NULL; + in->completion_flags = completion_flags; + + in->init_from_history = (def_text == INPUT_LAST_TEXT); + + if (in->init_from_history || def_text == NULL) + def_text = ""; + + input_assign_text (in, def_text); + + /* prepare to history setup */ + in->history.list = NULL; + in->history.current = NULL; + in->history.changed = FALSE; + in->history.name = NULL; + if ((histname != NULL) && (*histname != '\0')) + in->history.name = g_strdup (histname); + /* history will be loaded later */ + + in->label = NULL; + + return in; +} + +/* --------------------------------------------------------------------------------------------- */ + +cb_ret_t +input_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data) +{ + WInput *in = INPUT (w); + WDialog *h = DIALOG (w->owner); + cb_ret_t v; + + switch (msg) + { + case MSG_INIT: + /* subscribe to "history_load" event */ + mc_event_add (h->event_group, MCEVENT_HISTORY_LOAD, input_load_history, w, NULL); + /* subscribe to "history_save" event */ + mc_event_add (h->event_group, MCEVENT_HISTORY_SAVE, input_save_history, w, NULL); + if (in->label != NULL) + widget_set_state (WIDGET (in->label), WST_DISABLED, widget_get_state (w, WST_DISABLED)); + return MSG_HANDLED; + + case MSG_KEY: + if (parm == XCTRL ('q')) + { + quote = TRUE; + v = input_handle_char (in, ascii_alpha_to_cntrl (tty_getch ())); + quote = FALSE; + return v; + } + + /* Keys we want others to handle */ + if (parm == KEY_UP || parm == KEY_DOWN || parm == ESC_CHAR + || parm == KEY_F (10) || parm == '\n') + return MSG_NOT_HANDLED; + + /* When pasting multiline text, insert literal Enter */ + if ((parm & ~KEY_M_MASK) == '\n') + { + quote = TRUE; + v = input_handle_char (in, '\n'); + quote = FALSE; + return v; + } + + return input_handle_char (in, parm); + + case MSG_ACTION: + return input_execute_cmd (in, parm); + + case MSG_DRAW: + input_update (in, FALSE); + return MSG_HANDLED; + + case MSG_ENABLE: + case MSG_DISABLE: + if (in->label != NULL) + widget_set_state (WIDGET (in->label), WST_DISABLED, msg == MSG_DISABLE); + return MSG_HANDLED; + + case MSG_CURSOR: + widget_gotoyx (in, 0, str_term_width2 (in->buffer->str, in->point) - in->term_first_shown); + return MSG_HANDLED; + + case MSG_DESTROY: + /* unsubscribe from "history_load" event */ + mc_event_del (h->event_group, MCEVENT_HISTORY_LOAD, input_load_history, w); + /* unsubscribe from "history_save" event */ + mc_event_del (h->event_group, MCEVENT_HISTORY_SAVE, input_save_history, w); + input_destroy (in); + return MSG_HANDLED; + + default: + return widget_default_callback (w, sender, msg, parm, data); + } +} + +/* --------------------------------------------------------------------------------------------- */ + +void +input_set_default_colors (void) +{ + input_colors[WINPUTC_MAIN] = INPUT_COLOR; + input_colors[WINPUTC_MARK] = INPUT_MARK_COLOR; + input_colors[WINPUTC_UNCHANGED] = INPUT_UNCHANGED_COLOR; + input_colors[WINPUTC_HISTORY] = INPUT_HISTORY_COLOR; +} + +/* --------------------------------------------------------------------------------------------- */ + +cb_ret_t +input_handle_char (WInput * in, int key) +{ + cb_ret_t v; + long command; + + if (quote) + { + input_complete_free (in); + v = insert_char (in, key); + input_update (in, TRUE); + quote = FALSE; + return v; + } + + command = widget_lookup_key (WIDGET (in), key); + if (command == CK_IgnoreKey) + { + if (key > 255) + return MSG_NOT_HANDLED; + if (in->first) + port_region_marked_for_delete (in); + input_complete_free (in); + v = insert_char (in, key); + input_update (in, TRUE); + } + else + { + gboolean keep_first; + + if (command != CK_Complete) + input_complete_free (in); + input_execute_cmd (in, command); + v = MSG_HANDLED; + /* if in->first == TRUE and history or completion window was cancelled, + keep "first" state */ + keep_first = in->first && (command == CK_History || command == CK_Complete); + input_update (in, !keep_first); + } + + return v; +} + +/* --------------------------------------------------------------------------------------------- */ + +void +input_assign_text (WInput * in, const char *text) +{ + if (text == NULL) + text = ""; + + input_complete_free (in); + in->mark = -1; + in->need_push = TRUE; + in->charpoint = 0; + g_string_assign (in->buffer, text); + in->point = str_length (in->buffer->str); + input_update (in, TRUE); +} + +/* --------------------------------------------------------------------------------------------- */ + +/* Inserts text in input line */ +void +input_insert (WInput * in, const char *text, gboolean insert_extra_space) +{ + input_disable_update (in); + while (*text != '\0') + input_handle_char (in, (unsigned char) *text++); /* unsigned extension char->int */ + if (insert_extra_space) + input_handle_char (in, ' '); + input_enable_update (in); + input_update (in, TRUE); +} + +/* --------------------------------------------------------------------------------------------- */ + +void +input_set_point (WInput * in, int pos) +{ + int max_pos; + + max_pos = str_length (in->buffer->str); + pos = MIN (pos, max_pos); + if (pos != in->point) + input_complete_free (in); + in->point = pos; + in->charpoint = 0; + input_update (in, TRUE); +} + +/* --------------------------------------------------------------------------------------------- */ + +void +input_update (WInput * in, gboolean clear_first) +{ + Widget *wi = WIDGET (in); + const WRect *w = &wi->rect; + int has_history = 0; + int buf_len; + const char *cp; + int pw; + + if (in->disable_update != 0) + return; + + /* don't draw widget not put into dialog */ + if (wi->owner == NULL || !widget_get_state (WIDGET (wi->owner), WST_ACTIVE)) + return; + + if (clear_first) + in->first = FALSE; + + if (should_show_history_button (in)) + has_history = HISTORY_BUTTON_WIDTH; + + buf_len = str_length (in->buffer->str); + + /* Adjust the mark */ + in->mark = MIN (in->mark, buf_len); + + pw = str_term_width2 (in->buffer->str, in->point); + + /* Make the point visible */ + if ((pw < in->term_first_shown) || (pw >= in->term_first_shown + w->cols - has_history)) + { + in->term_first_shown = pw - (w->cols / 3); + if (in->term_first_shown < 0) + in->term_first_shown = 0; + } + + if (has_history != 0) + draw_history_button (in); + + if (widget_get_state (wi, WST_DISABLED)) + tty_setcolor (DISABLED_COLOR); + else if (in->first) + tty_setcolor (in->color[WINPUTC_UNCHANGED]); + else + tty_setcolor (in->color[WINPUTC_MAIN]); + + widget_gotoyx (in, 0, 0); + + if (!in->is_password) + { + if (in->mark < 0) + tty_print_string (str_term_substring (in->buffer->str, in->term_first_shown, + w->cols - has_history)); + else + { + long m1, m2; + + if (input_eval_marks (in, &m1, &m2)) + { + tty_setcolor (in->color[WINPUTC_MAIN]); + cp = str_term_substring (in->buffer->str, in->term_first_shown, + w->cols - has_history); + tty_print_string (cp); + tty_setcolor (in->color[WINPUTC_MARK]); + if (m1 < in->term_first_shown) + { + widget_gotoyx (in, 0, 0); + m1 = in->term_first_shown; + m2 -= m1; + } + else + { + int buf_width; + + widget_gotoyx (in, 0, m1 - in->term_first_shown); + buf_width = str_term_width2 (in->buffer->str, m1); + m2 = MIN (m2 - m1, + (w->cols - has_history) - (buf_width - in->term_first_shown)); + } + + tty_print_string (str_term_substring (in->buffer->str, m1, m2)); + } + } + } + else + { + int i; + + cp = str_term_substring (in->buffer->str, in->term_first_shown, w->cols - has_history); + tty_setcolor (in->color[WINPUTC_MAIN]); + for (i = 0; i < w->cols - has_history; i++) + { + if (i < (buf_len - in->term_first_shown) && cp[0] != '\0') + tty_print_char ('*'); + else + tty_print_char (' '); + if (cp[0] != '\0') + str_cnext_char (&cp); + } + } +} + +/* --------------------------------------------------------------------------------------------- */ + +void +input_enable_update (WInput * in) +{ + in->disable_update--; + input_update (in, FALSE); +} + +/* --------------------------------------------------------------------------------------------- */ + +void +input_disable_update (WInput * in) +{ + in->disable_update++; +} + +/* --------------------------------------------------------------------------------------------- */ + +/** + * Cleans the input line and adds the current text to the history + * + * @param in the input line + */ +void +input_clean (WInput * in) +{ + input_push_history (in); + in->need_push = TRUE; + g_string_set_size (in->buffer, 0); + in->point = 0; + in->charpoint = 0; + in->mark = -1; + input_complete_free (in); + input_update (in, FALSE); +} + +/* --------------------------------------------------------------------------------------------- */ |