#include #include #include #include #include "gm-world-input-view.h" #include "gm-world-view.h" #include "gm-world.h" #include "gm-color-table.h" #include "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; GList *item, *found = NULL; 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; } // 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) { debug_msg(0, "Prefix: %s", text); 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); debug_msg(0, "%d", view->priv->history); 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; }