392 lines
11 KiB
C
392 lines
11 KiB
C
#include <string.h>
|
|
#include <gtk/gtk.h>
|
|
#include <gdk/gdk.h>
|
|
#include <gdk/gdkkeysyms.h>
|
|
|
|
#include "gm-world-input-view.h"
|
|
#include "gm-world-view.h"
|
|
#include "../gm-world.h"
|
|
#include "../gm-color-table.h"
|
|
#include "../gm-debug.h"
|
|
|
|
#define GM_WORLD_INPUT_VIEW_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE((object), GM_TYPE_WORLD_INPUT_VIEW, GmWorldInputViewPrivate))
|
|
|
|
void on_gm_world_input_view_color_table_color_changed(GmColorTable *table,
|
|
gchar *name, GmWorldInputView *view);
|
|
void on_gm_world_input_view_color_table_font_changed(GmColorTable *table,
|
|
gchar *font_description, GmWorldInputView *view);
|
|
|
|
gboolean on_gm_world_input_view_key_pressed(GtkWidget *widget,
|
|
GdkEventKey * event, gpointer userdata);
|
|
gboolean on_gm_world_input_view_key_released(GtkWidget *widget,
|
|
GdkEventKey *event, gpointer userdata);
|
|
|
|
struct _GmWorldInputViewPrivate {
|
|
GmColorTable *color_table;
|
|
GList **history;
|
|
GList *position;
|
|
gchar *prefix;
|
|
};
|
|
|
|
/* Signals */
|
|
|
|
enum {
|
|
TEXT_ACTIVATE,
|
|
NUM_SIGNALS
|
|
};
|
|
|
|
static guint world_input_view_signals[NUM_SIGNALS] = {0};
|
|
|
|
G_DEFINE_TYPE(GmWorldInputView, gm_world_input_view, GTK_TYPE_TEXT_VIEW)
|
|
|
|
/* Private functions */
|
|
static void
|
|
gm_world_input_view_finalize(GObject *object) {
|
|
GmWorldInputView *view = GM_WORLD_INPUT_VIEW(object);
|
|
|
|
gm_world_input_view_set_color_table(view, NULL);
|
|
|
|
G_OBJECT_CLASS(gm_world_input_view_parent_class)->finalize(object);
|
|
}
|
|
|
|
static void
|
|
gm_world_input_view_class_init(GmWorldInputViewClass *klass) {
|
|
GObjectClass *object_class = G_OBJECT_CLASS(klass);
|
|
|
|
object_class->finalize = gm_world_input_view_finalize;
|
|
|
|
world_input_view_signals[TEXT_ACTIVATE] =
|
|
g_signal_new("text_activate",
|
|
G_OBJECT_CLASS_TYPE(object_class),
|
|
G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET(GmWorldInputViewClass, text_activate),
|
|
NULL, NULL,
|
|
g_cclosure_marshal_VOID__STRING,
|
|
G_TYPE_NONE,
|
|
1,
|
|
G_TYPE_STRING);
|
|
|
|
g_type_class_add_private(object_class, sizeof(GmWorldInputViewPrivate));
|
|
}
|
|
|
|
static void
|
|
gm_world_input_view_init(GmWorldInputView *view) {
|
|
view->priv = GM_WORLD_INPUT_VIEW_GET_PRIVATE(view);
|
|
|
|
view->priv->color_table = NULL;
|
|
view->priv->history = NULL;
|
|
view->priv->position = NULL;
|
|
view->priv->prefix = NULL;
|
|
|
|
gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(view), GTK_WRAP_WORD_CHAR);
|
|
gtk_text_view_set_accepts_tab(GTK_TEXT_VIEW(view), TRUE);
|
|
gtk_text_view_set_left_margin(GTK_TEXT_VIEW(view), 3);
|
|
gtk_text_view_set_right_margin(GTK_TEXT_VIEW(view), 3);
|
|
gtk_text_view_set_pixels_above_lines(GTK_TEXT_VIEW(view), 1);
|
|
gtk_text_view_set_pixels_below_lines(GTK_TEXT_VIEW(view), 1);
|
|
|
|
g_signal_connect(view, "key_press_event",
|
|
G_CALLBACK(on_gm_world_input_view_key_pressed), NULL);
|
|
g_signal_connect(view, "key_release_event",
|
|
G_CALLBACK(on_gm_world_input_view_key_released), NULL);
|
|
}
|
|
|
|
gchar *
|
|
gm_world_input_view_str_new_value(gchar *old, gchar *new) {
|
|
g_free(old);
|
|
|
|
if (new) {
|
|
return g_strdup(new);
|
|
} else {
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
void
|
|
gm_world_input_view_set_text(GmWorldInputView *view, gchar *text, gint len) {
|
|
GtkTextIter end;
|
|
GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(view));
|
|
|
|
gtk_text_buffer_set_text(buffer, text, len);
|
|
gtk_text_buffer_get_end_iter(buffer, &end);
|
|
gtk_text_buffer_place_cursor(buffer, &end);
|
|
}
|
|
|
|
void
|
|
gm_world_input_view_update_colors(GmWorldInputView *view) {
|
|
/*GdkColor col;
|
|
|
|
if (view->priv->color_table != NULL) {
|
|
if (gm_color_table_get(view->priv->color_table, "bg_default", &col)) {
|
|
gtk_widget_modify_base(GTK_WIDGET(view), GTK_STATE_NORMAL,
|
|
&col);
|
|
}
|
|
if (gm_color_table_get(view->priv->color_table, "fg_default", &col)) {
|
|
gtk_widget_modify_text(GTK_WIDGET(view), GTK_STATE_NORMAL,
|
|
&col);
|
|
}
|
|
}*/
|
|
}
|
|
|
|
void
|
|
gm_world_input_view_update_font(GmWorldInputView *view) {
|
|
PangoFontDescription *f = pango_font_description_from_string(
|
|
gm_color_table_font_description(view->priv->color_table));
|
|
|
|
if (f != NULL) {
|
|
gtk_widget_modify_font(GTK_WIDGET(view), f);
|
|
pango_font_description_free(f);
|
|
}
|
|
}
|
|
|
|
/* Public functions */
|
|
GtkWidget *
|
|
gm_world_input_view_new() {
|
|
GtkWidget *result;
|
|
GmColorTable *table = gm_color_table_new();
|
|
result = gm_world_input_view_new_with_color_table(table);
|
|
g_object_unref(table);
|
|
|
|
return result;
|
|
}
|
|
|
|
GtkWidget *
|
|
gm_world_input_view_new_with_color_table(GmColorTable *color_table) {
|
|
GmWorldInputView *view = GM_WORLD_INPUT_VIEW(
|
|
g_object_new(GM_TYPE_WORLD_INPUT_VIEW, NULL));
|
|
|
|
gm_world_input_view_set_color_table(view, color_table);
|
|
|
|
return GTK_WIDGET(view);
|
|
}
|
|
|
|
void
|
|
gm_world_input_view_set_history(GmWorldInputView *view, GList **history) {
|
|
view->priv->history = history;
|
|
|
|
view->priv->position = NULL;
|
|
view->priv->prefix = NULL;
|
|
}
|
|
|
|
GList **
|
|
gm_world_input_view_history(GmWorldInputView *view) {
|
|
return view->priv->history;
|
|
}
|
|
|
|
void
|
|
gm_world_input_view_set_color_table(GmWorldInputView *view,
|
|
GmColorTable *color_table) {
|
|
if (view->priv->color_table != NULL) {
|
|
g_signal_handlers_disconnect_by_func(view->priv->color_table,
|
|
on_gm_world_input_view_color_table_color_changed, view);
|
|
g_signal_handlers_disconnect_by_func(view->priv->color_table,
|
|
on_gm_world_input_view_color_table_font_changed, view);
|
|
g_object_unref(view->priv->color_table);
|
|
}
|
|
|
|
if (color_table != NULL) {
|
|
view->priv->color_table = g_object_ref(color_table);
|
|
g_signal_connect(view->priv->color_table, "color_changed",
|
|
G_CALLBACK(on_gm_world_input_view_color_table_color_changed), view);
|
|
g_signal_connect(view->priv->color_table, "font_changed",
|
|
G_CALLBACK(on_gm_world_input_view_color_table_font_changed), view);
|
|
|
|
gm_world_input_view_update_colors(view);
|
|
gm_world_input_view_update_font(view);
|
|
} else {
|
|
view->priv->color_table = NULL;
|
|
}
|
|
}
|
|
|
|
GmColorTable *
|
|
gm_world_input_view_color_table(GmWorldInputView *view) {
|
|
return view->priv->color_table;
|
|
}
|
|
|
|
/* Callbacks */
|
|
void
|
|
on_gm_world_input_view_color_table_color_changed(GmColorTable *table,
|
|
gchar *name, GmWorldInputView *view) {
|
|
if (strcmp(name, "fg_default") == 0 || strcmp(name, "bg_default") == 0) {
|
|
gm_world_input_view_update_colors(view);
|
|
}
|
|
}
|
|
|
|
void
|
|
on_gm_world_input_view_color_table_font_changed(GmColorTable *table,
|
|
gchar *font_description, GmWorldInputView *view) {
|
|
gm_world_input_view_update_font(view);
|
|
}
|
|
|
|
gboolean
|
|
on_gm_world_input_view_key_released(GtkWidget *widget, GdkEventKey *event,
|
|
gpointer userdata) {
|
|
GmWorldInputView *view = GM_WORLD_INPUT_VIEW(widget);
|
|
GtkTextBuffer *buffer;
|
|
GtkTextIter start, end;
|
|
gchar *text;
|
|
|
|
buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(view));
|
|
gtk_text_buffer_get_bounds(buffer, &start, &end);
|
|
text = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
|
|
|
|
switch (event->keyval) {
|
|
case GDK_Delete: case GDK_BackSpace:
|
|
if (text[0] == '\0') {
|
|
// Reset position to the last item in the history
|
|
view->priv->position = g_list_last(*view->priv->history);
|
|
|
|
// Reset prefix to NULL
|
|
view->priv->prefix = gm_world_input_view_str_new_value(
|
|
view->priv->prefix, NULL);
|
|
}
|
|
break;
|
|
}
|
|
|
|
g_free(text);
|
|
return FALSE;
|
|
}
|
|
|
|
gboolean
|
|
on_gm_world_input_view_key_pressed(GtkWidget *widget, GdkEventKey *event,
|
|
gpointer userdata) {
|
|
GmWorldInputView *view = GM_WORLD_INPUT_VIEW(widget);
|
|
gchar *text;
|
|
gboolean result = TRUE, isUp;
|
|
GtkTextBuffer *buf;
|
|
GtkTextIter start, end, cursor;
|
|
GtkTextMark *insert;
|
|
GList *item, *found = NULL;
|
|
gint line;
|
|
|
|
buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(view));
|
|
|
|
gtk_text_buffer_get_bounds(buf, &start, &end);
|
|
text = gtk_text_buffer_get_text(buf, &start, &end, FALSE);
|
|
|
|
switch (event->keyval) {
|
|
case GDK_Up: case GDK_Down:
|
|
isUp = event->keyval == GDK_Up;
|
|
|
|
if (!view->priv->history) {
|
|
break;
|
|
}
|
|
|
|
insert = gtk_text_buffer_get_insert(buf);
|
|
gtk_text_buffer_get_iter_at_mark(buf, &cursor, insert);
|
|
line = gtk_text_iter_get_line(&cursor);
|
|
|
|
if ((isUp && line != 0) || (!isUp && line !=
|
|
gtk_text_buffer_get_line_count(buf) - 1)) {
|
|
g_free(text);
|
|
return FALSE;
|
|
}
|
|
|
|
// If the current position is empty then append a new history item
|
|
if (!view->priv->position) {
|
|
*view->priv->history = g_list_append(*view->priv->history,
|
|
g_strdup(text));
|
|
view->priv->position = g_list_last(*view->priv->history);
|
|
}
|
|
|
|
// If there is nowhere to move, don't even bother
|
|
if ((isUp && !view->priv->position->prev)
|
|
|| (!isUp && !view->priv->position->next)) {
|
|
break;
|
|
}
|
|
|
|
// If the current prefix is NULL then we set the new prefix to
|
|
// the current text
|
|
if (view->priv->prefix == NULL) {
|
|
view->priv->prefix = g_strdup(text);
|
|
}
|
|
|
|
// If the prefix is an empty string then simply advance to the
|
|
// next item
|
|
if (view->priv->prefix[0] == '\0') {
|
|
if (isUp) {
|
|
found = view->priv->position->prev;
|
|
} else {
|
|
found = view->priv->position->next;
|
|
}
|
|
} else {
|
|
// Else find the closest matching history line
|
|
item = isUp ? view->priv->position->prev :
|
|
view->priv->position->next;
|
|
while (item) {
|
|
if (strncmp((gchar *)item->data, view->priv->prefix,
|
|
strlen(view->priv->prefix)) == 0) {
|
|
// Change current position to the matched item
|
|
found = item;
|
|
break;
|
|
}
|
|
|
|
item = isUp ? item->prev : item->next;
|
|
}
|
|
}
|
|
|
|
// If a match is found then set this history text
|
|
if (found) {
|
|
// Change the data of the current position to the text
|
|
// now in the buffer.
|
|
view->priv->position->data =
|
|
gm_world_input_view_str_new_value(
|
|
view->priv->position->data, text);
|
|
gm_world_input_view_set_text(view, (gchar *)found->data, -1);
|
|
view->priv->position = found;
|
|
|
|
if (found == g_list_last(*view->priv->history)) {
|
|
view->priv->prefix = gm_world_input_view_str_new_value(
|
|
view->priv->prefix, NULL);
|
|
}
|
|
}
|
|
break;
|
|
case GDK_Return:
|
|
// Emit the text_activate signal
|
|
if (!(event->state & GDK_CONTROL_MASK) &&
|
|
!(event->state & GDK_SHIFT_MASK)) {
|
|
|
|
g_signal_emit(view, world_input_view_signals[TEXT_ACTIVATE], 0,
|
|
text);
|
|
|
|
if (view->priv->history) {
|
|
item = g_list_last(*view->priv->history);
|
|
|
|
if (item) {
|
|
item->data = gm_world_input_view_str_new_value(
|
|
(gchar *)(item->data), text);
|
|
} else {
|
|
*view->priv->history =
|
|
g_list_append(*view->priv->history,
|
|
g_strdup(text));
|
|
}
|
|
}
|
|
|
|
// TODO: manage history length
|
|
|
|
// Append new empty history item which will become our new
|
|
// current item
|
|
if (view->priv->history) {
|
|
*view->priv->history =
|
|
g_list_append(*view->priv->history, g_strdup(""));
|
|
view->priv->position = g_list_last(*view->priv->history);
|
|
}
|
|
|
|
// Reset prefix to NULL
|
|
view->priv->prefix = gm_world_input_view_str_new_value(
|
|
view->priv->prefix, NULL);
|
|
|
|
// Set textview text to an empty string
|
|
gm_world_input_view_set_text(view, "", 0);
|
|
} else {
|
|
result = FALSE;
|
|
}
|
|
break;
|
|
default:
|
|
result = FALSE;
|
|
break;
|
|
}
|
|
|
|
g_free(text);
|
|
return result;
|
|
}
|