mc/lib/widget/input.c

1324 lines
35 KiB
C
Raw Normal View History

/*
Widgets for the Midnight Commander
Copyright (C) 1994-2024
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);
}
/* --------------------------------------------------------------------------------------------- */