#include <config.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <gtk/gtk.h>
#include <gladeui/glade.h>
#include <glib/gi18n-lib.h>
#include "glade-eprop-enum-int.h"
/* GObjectClass */
static void glade_eprop_enum_int_init (GladeEPropEnumInt *eprop);
static void glade_eprop_enum_int_class_init (GladeEPropEnumIntClass *klass);
static void glade_eprop_enum_int_finalize (GObject *object);
static void glade_eprop_enum_int_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec);
/* GladeEditorPropertyClass */
static void glade_eprop_enum_int_load (GladeEditorProperty *eprop, GladeProperty *property);
static GtkWidget * glade_eprop_enum_int_create_input (GladeEditorProperty *eprop);
typedef struct
{
GType type;
GtkWidget *combo_box;
GtkWidget *entry;
guint focus_out_idle;
} GladeEPropEnumIntPrivate;
enum {
PROP_0,
PROP_TYPE
};
enum {
COLUMN_ENUM_TEXT,
COLUMN_VALUE_INT,
N_COLUMNS
};
G_DEFINE_TYPE_WITH_PRIVATE (GladeEPropEnumInt,
glade_eprop_enum_int,
GLADE_TYPE_EDITOR_PROPERTY);
static void
glade_eprop_enum_int_init (GladeEPropEnumInt *eprop)
{
}
static void
glade_eprop_enum_int_class_init (GladeEPropEnumIntClass *klass)
{
GladeEditorPropertyClass *eprop_class = GLADE_EDITOR_PROPERTY_CLASS (klass);
GObjectClass *object_class = G_OBJECT_CLASS (klass);
eprop_class->load = glade_eprop_enum_int_load;
eprop_class->create_input = glade_eprop_enum_int_create_input;
object_class->finalize = glade_eprop_enum_int_finalize;
object_class->set_property = glade_eprop_enum_int_set_property;
g_object_class_install_property
(object_class, PROP_TYPE,
g_param_spec_gtype ("type", _("Type"),
_("Type"),
G_TYPE_NONE, G_PARAM_WRITABLE|G_PARAM_CONSTRUCT_ONLY));
}
static void
glade_eprop_enum_int_load (GladeEditorProperty *eprop, GladeProperty *property)
{
GladeEPropEnumInt *eprop_enum = GLADE_EPROP_ENUM_INT (eprop);
GladeEPropEnumIntPrivate *priv = glade_eprop_enum_int_get_instance_private (eprop_enum);
/* Chain up first */
GLADE_EDITOR_PROPERTY_CLASS (glade_eprop_enum_int_parent_class)->load (eprop, property);
if (property)
{
GEnumClass *enum_class;
gint value;
guint i;
gboolean found = FALSE;
enum_class = g_type_class_ref (priv->type);
value = g_value_get_int (glade_property_inline_value (property));
/*
* If we find the value in our enum, then set the active item, otherwise
* set the entry text
*/
for (i = 0; i < enum_class->n_values; i++)
{
if (enum_class->values[i].value == value)
{
found = TRUE;
break;
}
}
if (found)
{
gtk_combo_box_set_active (GTK_COMBO_BOX (priv->combo_box), i);
}
else
{
gchar *text = g_strdup_printf ("%d", value);
gtk_entry_set_text (GTK_ENTRY (priv->entry), text);
g_free (text);
}
g_type_class_unref (enum_class);
}
}
static const gchar *
string_from_value (GType etype, gint val)
{
GEnumClass *eclass;
const gchar *string = NULL;
guint i;
g_return_val_if_fail ((eclass = g_type_class_ref (etype)) != NULL, NULL);
for (i = 0; i < eclass->n_values; i++)
{
if (val == eclass->values[i].value)
{
if (glade_type_has_displayable_values (etype))
{
if (!glade_displayable_value_is_disabled (etype, eclass->values[i].value_nick))
string = glade_get_displayable_value (etype, eclass->values[i].value_nick);
}
else
{
string = eclass->values[i].value_nick;
}
break;
}
}
g_type_class_unref (eclass);
return string;
}
static gboolean
value_from_string (GType etype, const gchar *string, gint *value)
{
GEnumClass *eclass;
GEnumValue *ev;
gchar *endptr;
gint val = 0;
gboolean found = FALSE;
/* Try a number first */
val = strtoul (string, &endptr, 0);
if (endptr != string)
found = TRUE;
if (!found)
{
eclass = g_type_class_ref (etype);
ev = g_enum_get_value_by_name (eclass, string);
if (!ev)
ev = g_enum_get_value_by_nick (eclass, string);
if (ev)
{
val = ev->value;
found = TRUE;
}
if (!found && string && string[0])
{
/* Try Displayables */
string = glade_get_value_from_displayable (etype, string);
if (string)
{
ev = g_enum_get_value_by_name (eclass, string);
if (!ev)
ev = g_enum_get_value_by_nick (eclass, string);
if (ev)
{
val = ev->value;
found = TRUE;
}
}
}
g_type_class_unref (eclass);
}
if (found)
*value = val;
return found;
}
static void
glade_eprop_enum_int_changed_combo (GtkWidget *combo_box, GladeEditorProperty *eprop)
{
GladeEPropEnumInt *eprop_enum = GLADE_EPROP_ENUM_INT (eprop);
GladeEPropEnumIntPrivate *priv = glade_eprop_enum_int_get_instance_private (eprop_enum);
gint ival;
GtkTreeModel *tree_model;
GtkTreeIter iter;
GValue val = { 0, };
gboolean error = FALSE;
if (glade_editor_property_loading (eprop))
return;
tree_model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo_box));
if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (combo_box), &iter))
{
gtk_tree_model_get (tree_model, &iter,
COLUMN_VALUE_INT, &ival,
-1);
}
else
{
const char *text = gtk_entry_get_text (GTK_ENTRY (priv->entry));
if (!value_from_string (priv->type, text, &ival))
error = TRUE;
}
if (error)
{
gtk_entry_set_icon_from_icon_name (GTK_ENTRY (priv->entry), GTK_ENTRY_ICON_SECONDARY, "dialog-warning-symbolic");
}
else
{
gtk_entry_set_icon_from_icon_name (GTK_ENTRY (priv->entry), GTK_ENTRY_ICON_SECONDARY, NULL);
}
if (!error)
{
g_value_init (&val, G_TYPE_INT);
g_value_set_int (&val, ival);
glade_editor_property_commit_no_callback (eprop, &val);
g_value_unset (&val);
}
}
static gchar *
glade_eprop_enum_int_format_entry_cb (GtkComboBox *combo,
const gchar *path,
GladeEPropEnumInt *eprop_enum)
{
GladeEPropEnumIntPrivate *priv = glade_eprop_enum_int_get_instance_private (eprop_enum);
GtkTreeIter iter;
GtkTreeModel *model;
gint value;
const char *text;
gchar *endptr;
gboolean is_number = FALSE;
model = gtk_combo_box_get_model (combo);
/* Check if we currently have a number in the entry */
text = gtk_entry_get_text (GTK_ENTRY (priv->entry));
value = strtoul (text, &endptr, 0);
if (endptr != text)
is_number = TRUE;
/* Get the selected value */
gtk_tree_model_get_iter_from_string (model, &iter, path);
gtk_tree_model_get (model, &iter,
COLUMN_VALUE_INT, &value,
-1);
/* If we're currently in focus, and we have a number in the entry
* already, then we dont want to change it.
*
* E.g. If the user types "1" and we change it to "Pony" right away,
* then we ignore that the user might want to enter "10"
*
* After focusing out, we'll force refresh this so that we settle
* on displaying an enum value if one is valid.
*/
if (is_number && gtk_widget_has_focus (priv->entry))
return g_strdup_printf ("%d", value);
/* Now format a nice displayable value if possible
*/
text = string_from_value (priv->type, value);
if (text)
return g_strdup (text);
return g_strdup_printf ("%d", value);
}
static gboolean
glade_eprop_enum_int_focus_out_idle (gpointer user_data)
{
GladeEPropEnumInt *eprop_enum = GLADE_EPROP_ENUM_INT (user_data);
GladeEPropEnumIntPrivate *priv = glade_eprop_enum_int_get_instance_private (eprop_enum);
/* If the editor is no longer loaded with a property (i.e. the user changed
* focus by selecting another widget), then just return.
*/
if (glade_editor_property_get_property (GLADE_EDITOR_PROPERTY (eprop_enum)))
{
/* After focusing out, ensure we have a valid selection here if an entered
* number matches an enum value. Do this by provoking the combobox to
* format it's value.
*/
g_signal_emit_by_name (priv->combo_box, "changed");
}
priv->focus_out_idle = 0;
return FALSE;
}
static gboolean
glade_eprop_enum_int_entry_focus_out (GtkWidget *widget,
GdkEvent *event,
GladeEPropEnumInt *eprop_enum)
{
GladeEPropEnumIntPrivate *priv = glade_eprop_enum_int_get_instance_private (eprop_enum);
/* Queue the focus out idle, we may want to reformat the entry here
*/
if (priv->focus_out_idle == 0)
priv->focus_out_idle = g_idle_add (glade_eprop_enum_int_focus_out_idle, eprop_enum);
return FALSE;
}
static GtkWidget *
glade_eprop_enum_int_create_input (GladeEditorProperty *eprop)
{
GladeEPropEnumInt *eprop_enum = GLADE_EPROP_ENUM_INT (eprop);
GladeEPropEnumIntPrivate *priv = glade_eprop_enum_int_get_instance_private (eprop_enum);
GtkListStore *list_store;
GtkTreeIter iter;
guint i;
GEnumClass *enum_class;
enum_class = g_type_class_ref (priv->type);
list_store = gtk_list_store_new (N_COLUMNS,
G_TYPE_STRING, /* COLUMN_ENUM_TEXT */
G_TYPE_INT); /* COLUMN_VALUE_INT */
if (!glade_type_has_displayable_values (priv->type))
g_warning ("No displayable value found for type %s", g_type_name (priv->type));
gtk_tree_model_get_iter_first (GTK_TREE_MODEL (list_store), &iter);
for (i = 0; i < enum_class->n_values; i++)
{
if (glade_displayable_value_is_disabled (priv->type, enum_class->values[i].value_nick))
continue;
gtk_list_store_append (list_store, &iter);
gtk_list_store_set (list_store, &iter,
COLUMN_ENUM_TEXT, string_from_value (priv->type, enum_class->values[i].value),
COLUMN_VALUE_INT, enum_class->values[i].value,
-1);
}
priv->combo_box = gtk_combo_box_new_with_model_and_entry (GTK_TREE_MODEL (list_store));
priv->entry = gtk_bin_get_child (GTK_BIN (priv->combo_box));
gtk_widget_set_halign (priv->combo_box, GTK_ALIGN_FILL);
gtk_widget_set_valign (priv->combo_box, GTK_ALIGN_CENTER);
gtk_widget_set_hexpand (priv->combo_box, TRUE);
gtk_combo_box_set_entry_text_column (GTK_COMBO_BOX (priv->combo_box), 0);
g_signal_connect (G_OBJECT (priv->combo_box), "changed",
G_CALLBACK (glade_eprop_enum_int_changed_combo), eprop);
g_signal_connect (G_OBJECT (priv->combo_box), "format-entry-text",
G_CALLBACK (glade_eprop_enum_int_format_entry_cb), eprop);
g_signal_connect_after (G_OBJECT (priv->entry), "focus-out-event",
G_CALLBACK (glade_eprop_enum_int_entry_focus_out), eprop);
glade_util_remove_scroll_events (priv->combo_box);
g_type_class_unref (enum_class);
return priv->combo_box;
}
static void
glade_eprop_enum_int_finalize (GObject *object)
{
GladeEPropEnumInt *self = GLADE_EPROP_ENUM_INT(object);
GladeEPropEnumIntPrivate *priv = glade_eprop_enum_int_get_instance_private (self);
/* Be safe, dont leave idles hanging around */
if (priv->focus_out_idle != 0)
g_source_remove (priv->focus_out_idle);
}
static void
glade_eprop_enum_int_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
GladeEPropEnumInt *self = GLADE_EPROP_ENUM_INT(object);
GladeEPropEnumIntPrivate *priv = glade_eprop_enum_int_get_instance_private (self);
switch (property_id)
{
case PROP_TYPE :
priv->type = g_value_get_gtype (value);
break;
default:
/* We don't have any other property... */
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
GladeEditorProperty *
glade_eprop_enum_int_new (GladePropertyClass *pclass,
GType type,
gboolean use_command)
{
GladeEditorProperty *eprop =
g_object_new (GLADE_TYPE_EPROP_ENUM_INT,
"property-class", pclass,
"use-command", use_command,
"type", type,
NULL);
return eprop;
}