This repository has been archived on 2020-04-11. You can view files and clone it, but cannot push or open issues or pull requests.
gnoemoe/gnoemoe/widgets/gm-world-input-view.c

393 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;
}