#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 "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); void on_gm_world_input_view_changed(GtkTextBuffer *buffer, GmWorldInputView *view); struct _GmWorldInputViewPrivate { GmColorTable *color_table; GList **history; GList *position; gchar *prefix; gulong changed_id; guint idle_scroll; gboolean is_scrolled; }; /* Signals */ enum { TEXT_ACTIVATE, NUM_SIGNALS }; static guint world_input_view_signals[NUM_SIGNALS] = {0}; static GtkWidgetClass *widget_parent_class; 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); if (view->priv->idle_scroll) { g_source_remove(view->priv->idle_scroll); } G_OBJECT_CLASS(gm_world_input_view_parent_class)->finalize(object); } static 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)); g_signal_handlers_block_by_func(buffer, on_gm_world_input_view_changed, view); gtk_text_buffer_set_text(buffer, text, len); gtk_text_buffer_get_end_iter(buffer, &end); gtk_text_buffer_place_cursor(buffer, &end); g_signal_handlers_unblock_by_func(buffer, on_gm_world_input_view_changed, view); } static void gm_world_input_view_reset_prefix(GmWorldInputView *view) { view->priv->prefix = gm_world_input_view_str_new_value( view->priv->prefix, NULL); } gboolean gm_world_input_view_key_press_event(GtkWidget *widget, GdkEventKey *event) { GmWorldInputView *view = GM_WORLD_INPUT_VIEW(widget); gchar *text; gboolean result = FALSE, isUp; GtkTextBuffer *buf; GtkTextIter start, end, cursor; GtkTextMark *insert; GList *item, *found = NULL; gint line, len; buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(view)); gtk_text_buffer_get_bounds(buf, &start, &end); 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)) { break; } text = gtk_text_buffer_get_text(buf, &start, &end, 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)) { result = TRUE; 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') { 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)) { gm_world_input_view_reset_prefix(view); } } g_free(text); result = TRUE; break; case GDK_Return: // Emit the text_activate signal if (!(event->state & GDK_CONTROL_MASK) && !(event->state & GDK_SHIFT_MASK)) { text = gtk_text_buffer_get_text(buf, &start, &end, FALSE); 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)); } } len = g_list_length(*view->priv->history); for (line = 0; line < len - 500; ++line) { *view->priv->history = g_list_remove(*view->priv->history, (*view->priv->history)->data); } // 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); } gm_world_input_view_reset_prefix(view); // Set textview text to an empty string gm_world_input_view_set_text(view, "", 0); g_free(text); result = TRUE; } break; default: break; } if (!result) { if (widget_parent_class->key_press_event) { return widget_parent_class->key_press_event(widget, event); } } return result; } static void gm_world_input_view_class_init(GmWorldInputViewClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass); object_class->finalize = gm_world_input_view_finalize; widget_class->key_press_event = gm_world_input_view_key_press_event; widget_parent_class = GTK_WIDGET_CLASS(gm_world_input_view_parent_class); 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(gtk_text_view_get_buffer(GTK_TEXT_VIEW(view)), "changed", G_CALLBACK(on_gm_world_input_view_changed), view); } static void modify_cursor_color(GtkWidget *textview, GdkColor *color) { static const char cursor_color_rc[] = "style \"svs-cc\"\n" "{\n" "GtkTextView::cursor-color=\"#%04x%04x%04x\"\n" "}\n" "widget \"*.%s\" style : application \"svs-cc\"\n"; const gchar *name; gchar *rc_temp; name = gtk_widget_get_name(textview); if (!name) { gtk_widget_set_name(textview, "GmWorldInputView"); name = gtk_widget_get_name(textview); } g_return_if_fail(name != NULL); if (color != NULL) { rc_temp = g_strdup_printf(cursor_color_rc, color->red, color->green, color->blue, name); } else { GtkRcStyle *rc_style; rc_style = gtk_widget_get_modifier_style(textview); rc_temp = g_strdup_printf(cursor_color_rc, rc_style->text[GTK_STATE_NORMAL].red, rc_style->text[GTK_STATE_NORMAL].green, rc_style->text[GTK_STATE_NORMAL].blue, name); } gtk_rc_parse_string(rc_temp); gtk_widget_reset_rc_styles(textview); g_free(rc_temp); } 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); modify_cursor_color(GTK_WIDGET(view), &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; gm_world_input_view_reset_prefix(view); } 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 idle_scroll(gpointer user_data) { GmWorldInputView *view = GM_WORLD_INPUT_VIEW(user_data); GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(view)); view->priv->idle_scroll = 0; gtk_text_view_scroll_to_mark(GTK_TEXT_VIEW(view), gtk_text_buffer_get_insert(buffer), 0.0, TRUE, 0.5, 1.0); return FALSE; } void on_gm_world_input_view_changed(GtkTextBuffer *buffer, GmWorldInputView *view) { GtkTextIter start, end; GtkWidget *parent = gtk_widget_get_parent(GTK_WIDGET(view)); GtkScrolledWindow *sw; gint lineheight, y; if (view->priv->prefix) { gtk_text_buffer_get_bounds(buffer, &start, &end); if (gtk_text_iter_equal(&start, &end)) { // Reset position to the last item in the history view->priv->position = g_list_last(*view->priv->history); gm_world_input_view_reset_prefix(view); } } if (!GTK_IS_SCROLLED_WINDOW(parent)) { return; } if (!view->priv->is_scrolled && gtk_text_buffer_get_line_count(buffer) > 6) { sw = GTK_SCROLLED_WINDOW(parent); view->priv->is_scrolled = TRUE; gtk_text_buffer_get_start_iter(buffer, &start); gtk_text_view_get_line_yrange(GTK_TEXT_VIEW(view), &start, &y, &lineheight); gtk_widget_set_size_request(parent, -1, lineheight * 6 + gtk_container_get_border_width(GTK_CONTAINER(sw)) + 2); gtk_scrolled_window_set_policy(sw, GTK_POLICY_NEVER, GTK_POLICY_ALWAYS); view->priv->idle_scroll = g_idle_add(idle_scroll, view); } else if (view->priv->is_scrolled && gtk_text_buffer_get_line_count(buffer) <= 6) { sw = GTK_SCROLLED_WINDOW(parent); view->priv->is_scrolled = FALSE; gtk_scrolled_window_set_policy(sw, GTK_POLICY_NEVER, GTK_POLICY_NEVER); gtk_widget_set_size_request(parent, -1, -1); } }