summaryrefslogtreecommitdiffstats
path: root/src/widgets/ink-comboboxentry-action.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/widgets/ink-comboboxentry-action.cpp')
-rw-r--r--src/widgets/ink-comboboxentry-action.cpp958
1 files changed, 958 insertions, 0 deletions
diff --git a/src/widgets/ink-comboboxentry-action.cpp b/src/widgets/ink-comboboxentry-action.cpp
new file mode 100644
index 000000000..5c59f6961
--- /dev/null
+++ b/src/widgets/ink-comboboxentry-action.cpp
@@ -0,0 +1,958 @@
+/*
+ * A subclass of GtkAction that wraps a GtkComboBoxEntry.
+ * Features:
+ * Setting GtkEntryBox width in characters.
+ * Passing a function for formatting cells.
+ * Displaying a warning if entry text isn't in list.
+ * Check comma separated values in text against list. (Useful for font-family fallbacks.)
+ * Setting names for GtkComboBoxEntry and GtkEntry (actionName_combobox, actionName_entry)
+ * to allow setting resources.
+ *
+ * Author(s):
+ * Tavmjong Bah
+ * Jon A. Cruz <jon@joncruz.org>
+ *
+ * Copyright (C) 2010 Authors
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+/*
+ * We must provide for both a toolbar item and a menu item.
+ * As we don't know which widgets are used (or even constructed),
+ * we must keep track of things like active entry ourselves.
+ */
+
+#include <iostream>
+#include <string.h>
+#include <glibmm/ustring.h>
+
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+
+#include "widgets/ink-comboboxentry-action.h"
+#include "ui/icon-names.h"
+
+// Must handle both tool and menu items!
+static GtkWidget* create_tool_item( GtkAction* action );
+static GtkWidget* create_menu_item( GtkAction* action );
+
+// Internal
+static gint get_active_row_from_text( Ink_ComboBoxEntry_Action* action, const gchar* target_text, gboolean exclude = false, gboolean ignore_case = false );
+static Glib::ustring check_comma_separated_text( Ink_ComboBoxEntry_Action* action );
+
+// Callbacks
+static void combo_box_changed_cb( GtkComboBox* widget, gpointer data );
+static void entry_activate_cb( GtkEntry* widget, gpointer data );
+static gboolean match_selected_cb( GtkEntryCompletion* widget, GtkTreeModel* model, GtkTreeIter* iter, gpointer data );
+static gboolean keypress_cb( GtkWidget *widget, GdkEventKey *event, gpointer data );
+
+enum {
+ PROP_MODEL = 1,
+ PROP_COMBOBOX,
+ PROP_ENTRY,
+ PROP_ENTRY_WIDTH,
+ PROP_EXTRA_WIDTH,
+ PROP_CELL_DATA_FUNC,
+ PROP_SEPARATOR_FUNC,
+ PROP_POPUP,
+ PROP_FOCUS_WIDGET
+};
+
+enum {
+ CHANGED = 0,
+ ACTIVATED,
+ N_SIGNALS
+};
+static guint signals[N_SIGNALS] = {0};
+
+static GQuark gDataName = 0;
+
+static void ink_comboboxentry_action_init (Ink_ComboBoxEntry_Action *action);
+static void ink_comboboxentry_action_class_init (Ink_ComboBoxEntry_ActionClass *klass);
+
+G_DEFINE_TYPE(Ink_ComboBoxEntry_Action, ink_comboboxentry_action, GTK_TYPE_ACTION);
+
+static void ink_comboboxentry_action_finalize (GObject *object)
+{
+ // Free any allocated resources.
+
+ G_OBJECT_CLASS (ink_comboboxentry_action_parent_class)->finalize (object);
+}
+
+
+static void ink_comboboxentry_action_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec)
+{
+ Ink_ComboBoxEntry_Action *action = INK_COMBOBOXENTRY_ACTION (object);
+
+ switch(property_id) {
+
+ case PROP_MODEL:
+ action->model = GTK_TREE_MODEL( g_value_get_object( value ));
+ break;
+
+ case PROP_COMBOBOX:
+ action->combobox = GTK_COMBO_BOX (g_value_get_object (value));
+ break;
+
+ case PROP_ENTRY:
+ action->entry = GTK_ENTRY( g_value_get_object( value ));
+ break;
+
+ case PROP_ENTRY_WIDTH:
+ action->entry_width = g_value_get_int( value );
+ break;
+
+ case PROP_EXTRA_WIDTH:
+ action->extra_width = g_value_get_int( value );
+ break;
+
+ case PROP_CELL_DATA_FUNC:
+ action->cell_data_func = g_value_get_pointer( value );
+ break;
+
+ case PROP_SEPARATOR_FUNC:
+ action->separator_func = g_value_get_pointer( value );
+ break;
+
+ case PROP_POPUP:
+ action->popup = g_value_get_boolean( value );
+ break;
+
+ case PROP_FOCUS_WIDGET:
+ action->focusWidget = (GtkWidget*)g_value_get_pointer( value );
+ break;
+
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+
+static void ink_comboboxentry_action_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec)
+{
+ Ink_ComboBoxEntry_Action *action = INK_COMBOBOXENTRY_ACTION (object);
+
+ switch(property_id) {
+
+ case PROP_MODEL:
+ g_value_set_object (value, action->model);
+ break;
+
+ case PROP_COMBOBOX:
+ g_value_set_object (value, action->combobox);
+ break;
+
+ case PROP_ENTRY:
+ g_value_set_object (value, action->entry);
+ break;
+
+ case PROP_ENTRY_WIDTH:
+ g_value_set_int (value, action->entry_width);
+ break;
+
+ case PROP_EXTRA_WIDTH:
+ g_value_set_int (value, action->extra_width);
+ break;
+
+ case PROP_CELL_DATA_FUNC:
+ g_value_set_pointer (value, action->cell_data_func);
+ break;
+
+ case PROP_SEPARATOR_FUNC:
+ g_value_set_pointer (value, action->separator_func);
+ break;
+
+ case PROP_POPUP:
+ g_value_set_boolean (value, action->popup);
+ break;
+
+ case PROP_FOCUS_WIDGET:
+ g_value_set_pointer (value, action->focusWidget);
+ break;
+
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+static void
+ink_comboboxentry_action_connect_proxy (GtkAction *action,
+ GtkWidget *proxy)
+{
+ /* Override any proxy properties. */
+ // if (GTK_IS_MENU_ITEM (proxy)) {
+ // }
+
+ GTK_ACTION_CLASS (ink_comboboxentry_action_parent_class)->connect_proxy (action, proxy);
+}
+
+static void
+ink_comboboxentry_action_class_init (Ink_ComboBoxEntry_ActionClass *klass)
+{
+
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ GtkActionClass *gtkaction_class = GTK_ACTION_CLASS (klass);
+
+ gtkaction_class->connect_proxy = ink_comboboxentry_action_connect_proxy;
+
+ gobject_class->finalize = ink_comboboxentry_action_finalize;
+ gobject_class->set_property = ink_comboboxentry_action_set_property;
+ gobject_class->get_property = ink_comboboxentry_action_get_property;
+
+ gDataName = g_quark_from_string("ink_comboboxentry-action");
+
+ klass->parent_class.create_tool_item = create_tool_item;
+ klass->parent_class.create_menu_item = create_menu_item;
+
+ g_object_class_install_property (
+ gobject_class,
+ PROP_MODEL,
+ g_param_spec_object ("model",
+ "Tree Model",
+ "Tree Model",
+ GTK_TYPE_TREE_MODEL,
+ (GParamFlags)G_PARAM_READWRITE));
+ g_object_class_install_property (
+ gobject_class,
+ PROP_COMBOBOX,
+ g_param_spec_object ("combobox",
+ "GtkComboBoxEntry",
+ "GtkComboBoxEntry",
+ GTK_TYPE_WIDGET,
+ (GParamFlags)G_PARAM_READABLE));
+ g_object_class_install_property (
+ gobject_class,
+ PROP_ENTRY,
+ g_param_spec_object ("entry",
+ "GtkEntry",
+ "GtkEntry",
+ GTK_TYPE_WIDGET,
+ (GParamFlags)G_PARAM_READABLE));
+ g_object_class_install_property (
+ gobject_class,
+ PROP_ENTRY_WIDTH,
+ g_param_spec_int ("entry_width",
+ "EntryBox width",
+ "EntryBox width (characters)",
+ -1.0, 100, -1.0,
+ (GParamFlags)G_PARAM_READWRITE));
+
+ g_object_class_install_property (
+ gobject_class,
+ PROP_EXTRA_WIDTH,
+ g_param_spec_int ("extra_width",
+ "Extra width",
+ "Extra width (px)",
+ -1.0, 500, -1.0,
+ (GParamFlags)G_PARAM_READWRITE));
+
+ g_object_class_install_property (
+ gobject_class,
+ PROP_CELL_DATA_FUNC,
+ g_param_spec_pointer ("cell_data_func",
+ "Cell Data Func",
+ "Cell Deta Function",
+ (GParamFlags)G_PARAM_READWRITE));
+
+ g_object_class_install_property (
+ gobject_class,
+ PROP_SEPARATOR_FUNC,
+ g_param_spec_pointer ("separator_func",
+ "Separator Func",
+ "Separator Function",
+ (GParamFlags)G_PARAM_READWRITE));
+
+ g_object_class_install_property (
+ gobject_class,
+ PROP_POPUP,
+ g_param_spec_boolean ("popup",
+ "Entry Popup",
+ "Entry Popup",
+ false,
+ (GParamFlags)G_PARAM_READWRITE));
+
+ g_object_class_install_property( gobject_class,
+ PROP_FOCUS_WIDGET,
+ g_param_spec_pointer( "focus-widget",
+ "Focus Widget",
+ "The widget to return focus to",
+ (GParamFlags)(G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT) ) );
+
+ // We need to know when GtkComboBoxEvent or Menu ready for reading
+ signals[CHANGED] = g_signal_new( "changed",
+ G_TYPE_FROM_CLASS(klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET(Ink_ComboBoxEntry_ActionClass, changed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ // Probably not needed... originally to keep track of key-presses.
+ signals[ACTIVATED] = g_signal_new( "activated",
+ G_TYPE_FROM_CLASS(klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET(Ink_ComboBoxEntry_ActionClass, activated),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+}
+
+static void ink_comboboxentry_action_init (Ink_ComboBoxEntry_Action *action)
+{
+ action->active = -1;
+ action->text = strdup("");
+ action->entry_completion = NULL;
+ action->indicator = NULL;
+ action->popup = false;
+ action->info = NULL;
+ action->info_cb = NULL;
+ action->info_cb_id = 0;
+ action->info_cb_blocked = false;
+ action->warning = NULL;
+ action->warning_cb = NULL;
+ action->warning_cb_id = 0;
+ action->warning_cb_blocked = false;
+ action->altx_name = NULL;
+ action->focusWidget = NULL;
+}
+
+Ink_ComboBoxEntry_Action *ink_comboboxentry_action_new (const gchar *name,
+ const gchar *label,
+ const gchar *tooltip,
+ const gchar *stock_id,
+ GtkTreeModel *model,
+ gint entry_width,
+ gint extra_width,
+ void *cell_data_func,
+ void *separator_func,
+ GtkWidget *focusWidget)
+{
+ g_return_val_if_fail (name != NULL, NULL);
+
+ return (Ink_ComboBoxEntry_Action*)g_object_new (INK_COMBOBOXENTRY_TYPE_ACTION,
+ "name", name,
+ "label", label,
+ "tooltip", tooltip,
+ "stock-id", stock_id,
+ "model", model,
+ "entry_width", entry_width,
+ "extra_width", extra_width,
+ "cell_data_func", cell_data_func,
+ "separator_func", separator_func,
+ "focus-widget", focusWidget,
+ NULL);
+}
+
+// Create a widget for a toolbar.
+GtkWidget* create_tool_item( GtkAction* action )
+{
+ GtkWidget* item = 0;
+
+ if ( INK_COMBOBOXENTRY_IS_ACTION( action ) && INK_COMBOBOXENTRY_ACTION(action)->model ) {
+
+ Ink_ComboBoxEntry_Action* ink_comboboxentry_action = INK_COMBOBOXENTRY_ACTION( action );
+
+ gchar *action_name = g_strdup( gtk_action_get_name( action ) );
+ gchar *combobox_name = g_strjoin( NULL, action_name, "_combobox", NULL );
+ gchar *entry_name = g_strjoin( NULL, action_name, "_entry", NULL );
+ g_free( action_name );
+
+ item = GTK_WIDGET( gtk_tool_item_new() );
+
+ GtkWidget* comboBoxEntry = gtk_combo_box_new_with_model_and_entry (ink_comboboxentry_action->model);
+ gtk_combo_box_set_entry_text_column (GTK_COMBO_BOX (comboBoxEntry), 0);
+
+ // Name it so we can muck with it using an RC file
+ gtk_widget_set_name( comboBoxEntry, combobox_name );
+ g_free( combobox_name );
+
+ {
+ GtkWidget *align = gtk_alignment_new(0, 0.5, 0, 0);
+ gtk_container_add( GTK_CONTAINER(align), comboBoxEntry );
+ gtk_container_add( GTK_CONTAINER(item), align );
+ }
+
+ ink_comboboxentry_action->combobox = GTK_COMBO_BOX (comboBoxEntry);
+
+ //gtk_combo_box_set_active( GTK_COMBO_BOX( comboBoxEntry ), ink_comboboxentry_action->active );
+ gtk_combo_box_set_active( GTK_COMBO_BOX( comboBoxEntry ), 0 );
+
+ g_signal_connect( G_OBJECT(comboBoxEntry), "changed", G_CALLBACK(combo_box_changed_cb), action );
+
+ // Optionally add separator function...
+ if( ink_comboboxentry_action->separator_func != NULL ) {
+ gtk_combo_box_set_row_separator_func( ink_comboboxentry_action->combobox,
+ GtkTreeViewRowSeparatorFunc (ink_comboboxentry_action->separator_func),
+ NULL, NULL );
+ }
+
+ // FIXME: once gtk3 migration is done this can be removed
+ // https://bugzilla.gnome.org/show_bug.cgi?id=734915
+ gtk_widget_show_all (comboBoxEntry);
+
+ // Optionally add formatting...
+ if( ink_comboboxentry_action->cell_data_func != NULL ) {
+ GtkCellRenderer *cell = gtk_cell_renderer_text_new();
+ gtk_cell_layout_clear( GTK_CELL_LAYOUT( comboBoxEntry ) );
+ gtk_cell_layout_pack_start( GTK_CELL_LAYOUT( comboBoxEntry ), cell, true );
+ gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT( comboBoxEntry ), cell,
+ GtkCellLayoutDataFunc (ink_comboboxentry_action->cell_data_func),
+ NULL, NULL );
+ }
+
+ // Optionally widen the combobox width... which widens the drop-down list in list mode.
+ if( ink_comboboxentry_action->extra_width > 0 ) {
+ GtkRequisition req;
+#if GTK_CHECK_VERSION(3,0,0)
+ gtk_widget_get_preferred_size(GTK_WIDGET(ink_comboboxentry_action->combobox), &req, NULL);
+#else
+ gtk_widget_size_request( GTK_WIDGET( ink_comboboxentry_action->combobox ), &req );
+#endif
+ gtk_widget_set_size_request( GTK_WIDGET( ink_comboboxentry_action->combobox ),
+ req.width + ink_comboboxentry_action->extra_width, -1 );
+ }
+
+ // Get reference to GtkEntry and fiddle a bit with it.
+ GtkWidget *child = gtk_bin_get_child( GTK_BIN(comboBoxEntry) );
+
+ // Name it so we can muck with it using an RC file
+ gtk_widget_set_name( child, entry_name );
+ g_free( entry_name );
+
+ if( child && GTK_IS_ENTRY( child ) ) {
+
+ ink_comboboxentry_action->entry = GTK_ENTRY(child);
+
+ // Change width
+ if( ink_comboboxentry_action->entry_width > 0 ) {
+ gtk_entry_set_width_chars (GTK_ENTRY (child), ink_comboboxentry_action->entry_width );
+ }
+
+ // Add pop-up entry completion if required
+ if( ink_comboboxentry_action->popup ) {
+ ink_comboboxentry_action_popup_enable( ink_comboboxentry_action );
+ }
+
+ // Add altx_name if required
+ if( ink_comboboxentry_action->altx_name ) {
+ g_object_set_data( G_OBJECT( child ), ink_comboboxentry_action->altx_name, ink_comboboxentry_action->entry );
+ }
+
+ // Add signal for GtkEntry to check if finished typing.
+ g_signal_connect( G_OBJECT(child), "activate", G_CALLBACK(entry_activate_cb), action );
+ g_signal_connect( G_OBJECT(child), "key-press-event", G_CALLBACK(keypress_cb), action );
+ }
+
+ gtk_activatable_set_related_action( GTK_ACTIVATABLE (item), GTK_ACTION( action ) );
+ gtk_widget_show_all( item );
+
+ } else {
+
+ item = GTK_ACTION_CLASS(ink_comboboxentry_action_parent_class)->create_tool_item( action );
+
+ }
+
+ return item;
+}
+
+// Create a drop-down menu.
+GtkWidget* create_menu_item( GtkAction* action )
+{
+ GtkWidget* item = 0;
+
+ item = GTK_ACTION_CLASS(ink_comboboxentry_action_parent_class)->create_menu_item( action );
+ g_warning( "ink_comboboxentry_action: create_menu_item not implemented" );
+ // One can easily modify ege-select-one-action routine to implement this.
+ return item;
+}
+
+// Setters/Getters ---------------------------------------------------
+
+GtkTreeModel *ink_comboboxentry_action_get_model( Ink_ComboBoxEntry_Action* action ) {
+
+ return action->model;
+}
+
+GtkComboBox *ink_comboboxentry_action_get_comboboxentry( Ink_ComboBoxEntry_Action* action ) {
+
+ return action->combobox;
+}
+
+gchar* ink_comboboxentry_action_get_active_text( Ink_ComboBoxEntry_Action* action ) {
+
+ gchar* text = g_strdup( action->text );
+ return text;
+}
+
+/*
+ * For the font-family list we need to handle two cases:
+ * Text is in list store:
+ * In this case we use row number as the font-family list can have duplicate
+ * entries, one in the document font part and one in the system font part. In
+ * order that scrolling through the list works properly we must distinguish
+ * between the two.
+ * Text is not in the list store (i.e. default font-family is not on system):
+ * In this case we have a row number of -1, and the text must be set by hand.
+ */
+gboolean ink_comboboxentry_action_set_active_text( Ink_ComboBoxEntry_Action* action, const gchar* text, int row ) {
+
+ if( strcmp( action->text, text ) != 0 ) {
+ g_free( action->text );
+ action->text = g_strdup( text );
+ }
+
+ // Get active row or -1 if none
+ if( row < 0 ) {
+ row = get_active_row_from_text( action, action->text );
+ }
+ action->active = row;
+
+ // Set active row, check that combobox has been created.
+ if( action->combobox ) {
+ gtk_combo_box_set_active( GTK_COMBO_BOX( action->combobox ), action->active );
+ }
+
+ // Fiddle with entry
+ if( action->entry ) {
+
+ // Explicitly set text in GtkEntry box (won't be set if text not in list).
+ gtk_entry_set_text( action->entry, text );
+
+ // Show or hide warning -- this might be better moved to text-toolbox.cpp
+ if( action->info_cb_id != 0 &&
+ !action->info_cb_blocked ) {
+ g_signal_handler_block (G_OBJECT(action->entry),
+ action->info_cb_id );
+ action->info_cb_blocked = true;
+ }
+ if( action->warning_cb_id != 0 &&
+ !action->warning_cb_blocked ) {
+ g_signal_handler_block (G_OBJECT(action->entry),
+ action->warning_cb_id );
+ action->warning_cb_blocked = true;
+ }
+
+ bool set = false;
+ if( action->warning != NULL ) {
+ Glib::ustring missing = check_comma_separated_text( action );
+ if( !missing.empty() ) {
+ gtk_entry_set_icon_from_icon_name( action->entry,
+ GTK_ENTRY_ICON_SECONDARY,
+ INKSCAPE_ICON("dialog-warning") );
+ // Can't add tooltip until icon set
+ Glib::ustring warning = action->warning;
+ warning += ": ";
+ warning += missing;
+ gtk_entry_set_icon_tooltip_text( action->entry,
+ GTK_ENTRY_ICON_SECONDARY,
+ warning.c_str() );
+
+ if( action->warning_cb ) {
+
+ // Add callback if we haven't already
+ if( action->warning_cb_id == 0 ) {
+ action->warning_cb_id =
+ g_signal_connect( G_OBJECT(action->entry),
+ "icon-press",
+ G_CALLBACK(action->warning_cb),
+ action);
+ }
+ // Unblock signal
+ if( action->warning_cb_blocked ) {
+ g_signal_handler_unblock (G_OBJECT(action->entry),
+ action->warning_cb_id );
+ action->warning_cb_blocked = false;
+ }
+ }
+ set = true;
+ }
+ }
+
+ if( !set && action->info != NULL ) {
+ gtk_entry_set_icon_from_icon_name( GTK_ENTRY(action->entry),
+ GTK_ENTRY_ICON_SECONDARY,
+ INKSCAPE_ICON("edit-select-all") );
+ gtk_entry_set_icon_tooltip_text( action->entry,
+ GTK_ENTRY_ICON_SECONDARY,
+ action->info );
+
+ if( action->info_cb ) {
+ // Add callback if we haven't already
+ if( action->info_cb_id == 0 ) {
+ action->info_cb_id =
+ g_signal_connect( G_OBJECT(action->entry),
+ "icon-press",
+ G_CALLBACK(action->info_cb),
+ action);
+ }
+ // Unblock signal
+ if( action->info_cb_blocked ) {
+ g_signal_handler_unblock (G_OBJECT(action->entry),
+ action->info_cb_id );
+ action->info_cb_blocked = false;
+ }
+ }
+ set = true;
+ }
+
+ if( !set ) {
+ gtk_entry_set_icon_from_icon_name( GTK_ENTRY(action->entry),
+ GTK_ENTRY_ICON_SECONDARY,
+ NULL );
+ }
+ }
+
+ // Return if active text in list
+ gboolean found = ( action->active != -1 );
+ return found;
+}
+
+void ink_comboboxentry_action_set_entry_width( Ink_ComboBoxEntry_Action* action, gint entry_width ) {
+
+ action->entry_width = entry_width;
+
+ // Widget may not have been created....
+ if( action->entry ) {
+ gtk_entry_set_width_chars( GTK_ENTRY(action->entry), entry_width );
+ }
+}
+
+void ink_comboboxentry_action_set_extra_width( Ink_ComboBoxEntry_Action* action, gint extra_width ) {
+
+ action->extra_width = extra_width;
+
+ // Widget may not have been created....
+ if( action->combobox ) {
+ GtkRequisition req;
+#if GTK_CHECK_VERSION(3,0,0)
+ gtk_widget_get_preferred_size(GTK_WIDGET(action->combobox), &req, NULL);
+#else
+ gtk_widget_size_request( GTK_WIDGET( action->combobox ), &req );
+#endif
+ gtk_widget_set_size_request( GTK_WIDGET( action->combobox ), req.width + action->extra_width, -1 );
+ }
+}
+
+void ink_comboboxentry_action_popup_enable( Ink_ComboBoxEntry_Action* action ) {
+
+ action->popup = true;
+
+ // Widget may not have been created....
+ if( action->entry ) {
+
+ // Check we don't already have a GtkEntryCompletion
+ if( action->entry_completion ) return;
+
+ action->entry_completion = gtk_entry_completion_new();
+
+ gtk_entry_set_completion( action->entry, action->entry_completion );
+ gtk_entry_completion_set_model( action->entry_completion, action->model );
+ gtk_entry_completion_set_text_column( action->entry_completion, 0 );
+ gtk_entry_completion_set_popup_completion( action->entry_completion, true );
+ gtk_entry_completion_set_inline_completion( action->entry_completion, false );
+ gtk_entry_completion_set_inline_selection( action->entry_completion, true );
+
+ g_signal_connect (G_OBJECT (action->entry_completion), "match-selected", G_CALLBACK (match_selected_cb), action );
+
+
+ }
+}
+
+void ink_comboboxentry_action_popup_disable( Ink_ComboBoxEntry_Action* action ) {
+
+ action->popup = false;
+
+ if( action->entry_completion ) {
+ gtk_widget_destroy(GTK_WIDGET(action->entry_completion));
+ action->entry_completion = 0;
+ }
+}
+void ink_comboboxentry_action_set_tooltip( Ink_ComboBoxEntry_Action* action, const gchar* tooltip ) {
+
+ // Widget may not have been created....
+ if( action->entry ) {
+ gtk_widget_set_tooltip_text ( GTK_WIDGET(action->entry), tooltip);
+ }
+ if( action->combobox ) {
+ gtk_widget_set_tooltip_text ( GTK_WIDGET(action->combobox), tooltip);
+ }
+
+}
+
+void ink_comboboxentry_action_set_info( Ink_ComboBoxEntry_Action* action, const gchar* info ) {
+
+ g_free( action->info );
+ action->info = g_strdup( info );
+
+ // Widget may not have been created....
+ if( action->entry ) {
+ gtk_entry_set_icon_tooltip_text( GTK_ENTRY(action->entry),
+ GTK_ENTRY_ICON_SECONDARY,
+ action->info );
+ }
+}
+
+void ink_comboboxentry_action_set_info_cb( Ink_ComboBoxEntry_Action* action, gpointer info_cb ) {
+
+ action->info_cb = info_cb;
+}
+
+void ink_comboboxentry_action_set_warning( Ink_ComboBoxEntry_Action* action, const gchar* warning ) {
+
+ g_free( action->warning );
+ action->warning = g_strdup( warning );
+
+ // Widget may not have been created....
+ if( action->entry ) {
+ gtk_entry_set_icon_tooltip_text( GTK_ENTRY(action->entry),
+ GTK_ENTRY_ICON_SECONDARY,
+ action->warning );
+ }
+}
+
+void ink_comboboxentry_action_set_warning_cb( Ink_ComboBoxEntry_Action* action, gpointer warning_cb ) {
+
+ action->warning_cb = warning_cb;
+}
+
+void ink_comboboxentry_action_set_altx_name( Ink_ComboBoxEntry_Action* action, const gchar* altx_name ) {
+
+ g_free( action->altx_name );
+ action->altx_name = g_strdup( altx_name );
+
+ // Widget may not have been created....
+ if( action->entry ) {
+ g_object_set_data( G_OBJECT(action->entry), action->altx_name, action->entry );
+ }
+}
+
+// Internal ---------------------------------------------------
+
+// Return row of active text or -1 if not found. If exclude is true,
+// use 3d colunm if available to exclude row from checking (useful to
+// skip rows added for font-families included in doc and not on
+// system)
+gint get_active_row_from_text( Ink_ComboBoxEntry_Action* action, const gchar* target_text,
+ gboolean exclude, gboolean ignore_case ) {
+
+ // Check if text in list
+ gint row = 0;
+ gboolean found = false;
+ GtkTreeIter iter;
+ gboolean valid = gtk_tree_model_get_iter_first( action->model, &iter );
+ while ( valid ) {
+
+ // See if we should exclude a row
+ gboolean check = true; // If true, font-family is on system.
+ if( exclude && gtk_tree_model_get_n_columns( action->model ) > 2 ) {
+ gtk_tree_model_get( action->model, &iter, 2, &check, -1 );
+ }
+
+ if( check ) {
+ // Get text from list entry
+ gchar* text = 0;
+ gtk_tree_model_get( action->model, &iter, 0, &text, -1 ); // Column 0
+
+ if( !ignore_case ) {
+ // Case sensitive compare
+ if( strcmp( target_text, text ) == 0 ){
+ found = true;
+ break;
+ }
+ } else {
+ // Case insensitive compare
+ gchar* target_text_casefolded = g_utf8_casefold( target_text, -1 );
+ gchar* text_casefolded = g_utf8_casefold( text, -1 );
+ gboolean equal = (strcmp( target_text_casefolded, text_casefolded ) == 0 );
+ g_free( text_casefolded );
+ g_free( target_text_casefolded );
+ if( equal ) {
+ found = true;
+ break;
+ }
+ }
+ }
+
+ ++row;
+ valid = gtk_tree_model_iter_next( action->model, &iter );
+ }
+
+ if( !found ) row = -1;
+
+ return row;
+
+}
+
+// Checks if all comma separated text fragments are in the list and
+// returns a ustring with a list of missing fragments.
+// This is useful for checking if all fonts in a font-family fallback
+// list are available on the system.
+//
+// This routine could also create a Pango Markup string to show which
+// fragments are invalid in the entry box itself. See:
+// http://developer.gnome.org/pango/stable/PangoMarkupFormat.html
+// However... it appears that while one can retrieve the PangoLayout
+// for a GtkEntry box, it is only a copy and changing it has no effect.
+// PangoLayout * pl = gtk_entry_get_layout( entry );
+// pango_layout_set_markup( pl, "NEW STRING", -1 ); // DOESN'T WORK
+static Glib::ustring check_comma_separated_text( Ink_ComboBoxEntry_Action* action ) {
+
+ Glib::ustring missing;
+
+ // Parse fallback_list using a comma as deliminator
+ gchar** tokens = g_strsplit( action->text, ",", 0 );
+
+ gint i = 0;
+ while( tokens[i] != NULL ) {
+
+ // Remove any surrounding white space.
+ g_strstrip( tokens[i] );
+
+ if( get_active_row_from_text( action, tokens[i], true, true ) == -1 ) {
+ missing += tokens[i];
+ missing += ", ";
+ }
+ ++i;
+ }
+ g_strfreev( tokens );
+
+ // Remove extra comma and space from end.
+ if( missing.size() >= 2 ) {
+ missing.resize( missing.size()-2 );
+ }
+ return missing;
+}
+
+// Callbacks ---------------------------------------------------
+
+static void combo_box_changed_cb( GtkComboBox* widget, gpointer data ) {
+
+ // Two things can happen to get here:
+ // An item is selected in the drop-down menu.
+ // Text is typed.
+ // We only react here if an item is selected.
+
+ // Get action
+ Ink_ComboBoxEntry_Action *action = INK_COMBOBOXENTRY_ACTION( data );
+
+ // Check if item selected:
+ gint newActive = gtk_combo_box_get_active(widget);
+ if( newActive >= 0 && newActive != action->active ) {
+
+ action->active = newActive;
+
+ GtkTreeIter iter;
+ if( gtk_combo_box_get_active_iter( GTK_COMBO_BOX( action->combobox ), &iter ) ) {
+
+ gchar* text = 0;
+ gtk_tree_model_get( action->model, &iter, 0, &text, -1 );
+ gtk_entry_set_text( action->entry, text );
+
+ g_free( action->text );
+ action->text = text;
+ }
+
+ // Now let the world know
+ g_signal_emit( G_OBJECT(action), signals[CHANGED], 0 );
+ }
+}
+
+static void entry_activate_cb( GtkEntry* widget, gpointer data ) {
+
+ // Get text from entry box.. check if it matches a menu entry.
+
+ // Get action
+ Ink_ComboBoxEntry_Action *action = INK_COMBOBOXENTRY_ACTION( data );
+
+ // Get text
+ g_free( action->text );
+ action->text = g_strdup( gtk_entry_get_text( widget ) );
+
+ // Get row
+ action->active =
+ get_active_row_from_text( action, action->text );
+
+ // Set active row
+ gtk_combo_box_set_active( GTK_COMBO_BOX( action->combobox), action->active );
+
+ // Now let the world know
+ g_signal_emit( G_OBJECT(action), signals[CHANGED], 0 );
+
+}
+
+static gboolean match_selected_cb( GtkEntryCompletion* /*widget*/, GtkTreeModel* model, GtkTreeIter* iter, gpointer data )
+{
+ // Get action
+ Ink_ComboBoxEntry_Action *action = INK_COMBOBOXENTRY_ACTION( data );
+ GtkEntry *entry = action->entry;
+
+ if( entry) {
+ gchar *family = 0;
+ gtk_tree_model_get(model, iter, 0, &family, -1);
+
+ // Set text in GtkEntry
+ gtk_entry_set_text (GTK_ENTRY (entry), family );
+
+ // Set text in GtkAction
+ g_free( action->text );
+ action->text = family;
+
+ // Get row
+ action->active =
+ get_active_row_from_text( action, action->text );
+
+ // Set active row
+ gtk_combo_box_set_active( GTK_COMBO_BOX( action->combobox), action->active );
+
+ // Now let the world know
+ g_signal_emit( G_OBJECT(action), signals[CHANGED], 0 );
+
+ return true;
+ }
+ return false;
+}
+
+static void ink_comboboxentry_action_defocus( Ink_ComboBoxEntry_Action* action )
+{
+ if ( action->focusWidget ) {
+ gtk_widget_grab_focus( action->focusWidget );
+ }
+}
+
+gboolean keypress_cb( GtkWidget * /*widget*/, GdkEventKey *event, gpointer data )
+{
+ gboolean wasConsumed = FALSE; /* default to report event not consumed */
+ guint key = 0;
+ Ink_ComboBoxEntry_Action* action = INK_COMBOBOXENTRY_ACTION( data );
+ gdk_keymap_translate_keyboard_state( gdk_keymap_get_for_display( gdk_display_get_default() ),
+ event->hardware_keycode, (GdkModifierType)event->state,
+ 0, &key, 0, 0, 0 );
+
+ switch ( key ) {
+
+ // TODO Add bindings for Tab/LeftTab
+ case GDK_KEY_Escape:
+ {
+ //gtk_spin_button_set_value( GTK_SPIN_BUTTON(widget), action->private_data->lastVal );
+ ink_comboboxentry_action_defocus( action );
+ wasConsumed = TRUE;
+ }
+ break;
+
+ case GDK_KEY_Return:
+ case GDK_KEY_KP_Enter:
+ {
+ ink_comboboxentry_action_defocus( action );
+ //wasConsumed = TRUE;
+ }
+ break;
+
+
+ }
+
+ return wasConsumed;
+}