[GTK3] Add full text completion and simple history navigation
This commit is contained in:
parent
55a258ad0f
commit
e6aef1ce82
@ -17,7 +17,9 @@ struct _ConsoleWindow {
|
||||
bool log_to_sidebar;
|
||||
bool clear_sidebar;
|
||||
|
||||
GList *command_history;
|
||||
GtkEntryCompletion *command_completion;
|
||||
guint command_history_len;
|
||||
gint command_history_index;
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE(ConsoleWindow, console_window, GTK_TYPE_WINDOW);
|
||||
@ -28,6 +30,74 @@ typedef struct {
|
||||
bool sidebar;
|
||||
} AttributedMessage;
|
||||
|
||||
static gboolean on_input_key_press(GtkEntry *input, GdkEventKey *event, ConsoleWindow *self) {
|
||||
switch (event->keyval) {
|
||||
case GDK_KEY_Up:
|
||||
if (event->type == GDK_KEY_PRESS) {
|
||||
if (self->command_history_index + 1 == self->command_history_len) {
|
||||
self->command_history_index = self->command_history_len - 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
self->command_history_index += 1;
|
||||
|
||||
GtkTreeIter iter;
|
||||
GtkTreeModel *model = gtk_entry_completion_get_model(self->command_completion);
|
||||
|
||||
if (!gtk_tree_model_iter_nth_child(model, &iter, NULL, self->command_history_index)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const char *entry = NULL;
|
||||
gtk_tree_model_get(model, &iter, 0, &entry, -1);
|
||||
gtk_entry_set_text(self->input, entry);
|
||||
gtk_editable_set_position(GTK_EDITABLE(input), -1);
|
||||
}
|
||||
|
||||
return true;
|
||||
break;
|
||||
|
||||
case GDK_KEY_Down:
|
||||
if (event->type == GDK_KEY_PRESS) {
|
||||
if (self->command_history_index <= 0) {
|
||||
gtk_entry_set_text(self->input, "");
|
||||
self->command_history_index = -1;
|
||||
}
|
||||
|
||||
if (self->command_history_index == -1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
self->command_history_index -= 1;
|
||||
|
||||
GtkTreeIter iter;
|
||||
GtkTreeModel *model = gtk_entry_completion_get_model(self->command_completion);
|
||||
|
||||
if (!gtk_tree_model_iter_nth_child(model, &iter, NULL, self->command_history_index)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const char *entry = NULL;
|
||||
gtk_tree_model_get(model, &iter, 0, &entry, -1);
|
||||
gtk_entry_set_text(self->input, entry);
|
||||
gtk_editable_set_position(GTK_EDITABLE(input), -1);
|
||||
}
|
||||
|
||||
return true;
|
||||
break;
|
||||
|
||||
case GDK_KEY_Tab:
|
||||
if (event->type == GDK_KEY_PRESS) {
|
||||
gtk_editable_set_position(GTK_EDITABLE(input), -1);
|
||||
}
|
||||
|
||||
return true;
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void console_window_init(ConsoleWindow *self) {
|
||||
gtk_widget_init_template(GTK_WIDGET(self));
|
||||
|
||||
@ -49,7 +119,22 @@ static void console_window_init(ConsoleWindow *self) {
|
||||
self->output_queue = g_async_queue_new();
|
||||
self->log_to_sidebar = false;
|
||||
self->clear_sidebar = false;
|
||||
self->command_history = NULL;
|
||||
self->command_history_index = -1;
|
||||
|
||||
GtkTreeIter iter;
|
||||
GtkListStore *command_list_store = gtk_list_store_new(1, G_TYPE_STRING);
|
||||
self->command_completion = gtk_entry_completion_new();
|
||||
gtk_entry_completion_set_model(self->command_completion, GTK_TREE_MODEL(command_list_store));
|
||||
gtk_entry_completion_set_text_column(self->command_completion, 0);
|
||||
gtk_entry_completion_set_popup_completion(self->command_completion, false);
|
||||
gtk_entry_completion_set_inline_completion(self->command_completion, true);
|
||||
gtk_entry_set_completion(self->input, self->command_completion);
|
||||
gtk_entry_set_input_hints(self->input, GTK_INPUT_HINT_NO_SPELLCHECK | GTK_INPUT_HINT_NO_EMOJI);
|
||||
|
||||
gtk_widget_add_events(GTK_WIDGET(self), GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK);
|
||||
|
||||
g_signal_connect(self->input, "key-press-event", G_CALLBACK(on_input_key_press), self);
|
||||
g_signal_connect(self->input, "key-release-event", G_CALLBACK(on_input_key_press), self);
|
||||
}
|
||||
|
||||
static void console_window_realize(GtkWidget *widget) {
|
||||
@ -69,28 +154,44 @@ static void log_simple(ConsoleWindow *self, const char *message) {
|
||||
}
|
||||
|
||||
// TODO: Use command history (arrow key (↑, ↓) events)
|
||||
static void on_input(GtkEntry *input, ConsoleWindow *self) {
|
||||
static void on_input_enter(GtkEntry *input, ConsoleWindow *self) {
|
||||
const gchar *text_ptr = gtk_entry_get_text(input);
|
||||
const gchar *text = NULL;
|
||||
|
||||
GtkTreeModel *model = gtk_entry_completion_get_model(self->command_completion);
|
||||
|
||||
if (g_strcmp0("", text_ptr) == 0) {
|
||||
GList *last = g_list_last(self->command_history);
|
||||
if (last) text = last->data;
|
||||
const char *last = NULL;
|
||||
GtkTreeIter iter;
|
||||
if (gtk_tree_model_get_iter_first(model, &iter)) {
|
||||
gtk_tree_model_get(model, &iter, 0, &last, -1);
|
||||
}
|
||||
|
||||
if (last) text = last;
|
||||
}
|
||||
else {
|
||||
text = text_ptr;
|
||||
}
|
||||
|
||||
if (text) {
|
||||
GList *last = g_list_last(self->command_history);
|
||||
const char *last = NULL;
|
||||
|
||||
GtkTreeIter iter;
|
||||
if (gtk_tree_model_get_iter_first(model, &iter)) {
|
||||
gtk_tree_model_get(model, &iter, 0, &last, -1);
|
||||
}
|
||||
|
||||
// Add command to queue unless it was the last command issued
|
||||
if (!last || g_strcmp0(last->data, text) != 0) {
|
||||
self->command_history = g_list_append(self->command_history, g_strdup(text));
|
||||
if (!last || g_strcmp0(last, text) != 0) {
|
||||
gtk_list_store_prepend(GTK_LIST_STORE(model), &iter);
|
||||
gtk_list_store_set(GTK_LIST_STORE(model), &iter, 0, text, -1);
|
||||
self->command_history_len += 1;
|
||||
}
|
||||
|
||||
g_async_queue_push(self->input_queue, (gpointer) g_strdup(text));
|
||||
gtk_entry_set_text(self->input, "");
|
||||
|
||||
self->command_history_index = -1;
|
||||
}
|
||||
}
|
||||
|
||||
@ -225,7 +326,7 @@ static void console_window_class_init(ConsoleWindowClass *class) {
|
||||
gtk_widget_class_bind_template_child(GTK_WIDGET_CLASS(class), ConsoleWindow, sidebar_input);
|
||||
gtk_widget_class_bind_template_child(GTK_WIDGET_CLASS(class), ConsoleWindow, sidebar_output);
|
||||
|
||||
gtk_widget_class_bind_template_callback(GTK_WIDGET_CLASS(class), on_input);
|
||||
gtk_widget_class_bind_template_callback(GTK_WIDGET_CLASS(class), on_input_enter);
|
||||
|
||||
GTK_WIDGET_CLASS(class)->realize = console_window_realize;
|
||||
GTK_WIDGET_CLASS(class)->draw = console_window_draw;
|
||||
|
@ -1126,8 +1126,8 @@ static void connect_signal_handlers(GApplication *app) {
|
||||
gtk_widget_add_events(GTK_WIDGET(gui_data.main_window), GDK_KEY_RELEASE_MASK);
|
||||
|
||||
g_signal_connect(gui_data.main_window, "destroy", G_CALLBACK(on_quit_activate), app);
|
||||
g_signal_connect(gui_data.main_window, "key_press_event", G_CALLBACK(on_key_press), NULL);
|
||||
g_signal_connect(gui_data.main_window, "key_release_event", G_CALLBACK(on_key_press), NULL);
|
||||
g_signal_connect(gui_data.main_window, "key-press-event", G_CALLBACK(on_key_press), NULL);
|
||||
g_signal_connect(gui_data.main_window, "key-release-event", G_CALLBACK(on_key_press), NULL);
|
||||
g_signal_connect(gui_data.main_window, "window-state-event", G_CALLBACK(on_window_state_change), NULL);
|
||||
|
||||
// Just hide our sub-windows when closing them
|
||||
|
@ -182,7 +182,7 @@ Author: Maximilian Mader
|
||||
<property name="margin_top">3</property>
|
||||
<property name="margin_bottom">3</property>
|
||||
<property name="placeholder_text" translatable="yes">Console input</property>
|
||||
<signal name="activate" handler="on_input" swapped="no"/>
|
||||
<signal name="activate" handler="on_input_enter" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
|
Loading…
Reference in New Issue
Block a user