diff options
Diffstat (limited to 'gui/egg-property-cell-renderer.c')
-rw-r--r-- | gui/egg-property-cell-renderer.c | 594 |
1 files changed, 594 insertions, 0 deletions
diff --git a/gui/egg-property-cell-renderer.c b/gui/egg-property-cell-renderer.c new file mode 100644 index 0000000..9df5cc3 --- /dev/null +++ b/gui/egg-property-cell-renderer.c @@ -0,0 +1,594 @@ +/* Copyright (C) 2011, 2012 Matthias Vogelgesang <matthias.vogelgesang@kit.edu> + (Karlsruhe Institute of Technology) + + This library is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by the + Free Software Foundation; either version 2.1 of the License, or (at your + option) any later version. + + This library is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more + details. + + You should have received a copy of the GNU Lesser General Public License along + with this library; if not, write to the Free Software Foundation, Inc., 51 + Franklin St, Fifth Floor, Boston, MA 02110, USA */ + +#include <stdlib.h> +#include "egg-property-cell-renderer.h" + +G_DEFINE_TYPE (EggPropertyCellRenderer, egg_property_cell_renderer, GTK_TYPE_CELL_RENDERER) + +#define EGG_PROPERTY_CELL_RENDERER_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), EGG_TYPE_PROPERTY_CELL_RENDERER, EggPropertyCellRendererPrivate)) + + +struct _EggPropertyCellRendererPrivate +{ + GObject *object; + GtkListStore *list_store; + GtkCellRenderer *renderer; + GtkCellRenderer *text_renderer; + GtkCellRenderer *spin_renderer; + GtkCellRenderer *toggle_renderer; + GtkCellRenderer *combo_renderer; + GHashTable *combo_models; +}; + +enum +{ + PROP_0, + PROP_PROP_NAME, + N_PROPERTIES +}; + +enum +{ + COMBO_COLUMN_VALUE_NAME, + COMBO_COLUMN_VALUE, + N_COMBO_COLUMNS +}; + +static GParamSpec *egg_property_cell_renderer_properties[N_PROPERTIES] = { NULL, }; + +GtkCellRenderer * +egg_property_cell_renderer_new (GObject *object, + GtkListStore *list_store) +{ + EggPropertyCellRenderer *renderer; + + renderer = EGG_PROPERTY_CELL_RENDERER (g_object_new (EGG_TYPE_PROPERTY_CELL_RENDERER, NULL)); + renderer->priv->object = object; + renderer->priv->list_store = list_store; + return GTK_CELL_RENDERER (renderer); +} + +static GParamSpec * +get_pspec_from_object (GObject *object, const gchar *prop_name) +{ + GObjectClass *oclass = G_OBJECT_GET_CLASS (object); + return g_object_class_find_property (oclass, prop_name); +} + +static void +get_string_double_repr (GObject *object, const gchar *prop_name, gchar **text, gdouble *number) +{ + GParamSpec *pspec; + GValue from = { 0 }; + GValue to_string = { 0 }; + GValue to_double = { 0 }; + + pspec = get_pspec_from_object (object, prop_name); + g_value_init (&from, pspec->value_type); + g_value_init (&to_string, G_TYPE_STRING); + g_value_init (&to_double, G_TYPE_DOUBLE); + g_object_get_property (object, prop_name, &from); + + if (g_value_transform (&from, &to_string)) + *text = g_strdup (g_value_get_string (&to_string)); + else + g_warning ("Could not convert from %s gchar*\n", g_type_name (pspec->value_type)); + + if (g_value_transform (&from, &to_double)) + *number = g_value_get_double (&to_double); + else + g_warning ("Could not convert from %s to gdouble\n", g_type_name (pspec->value_type)); +} + +static void +clear_adjustment (GObject *object) +{ + GtkAdjustment *adjustment; + + g_object_get (object, + "adjustment", &adjustment, + NULL); + + if (adjustment) + g_object_unref (adjustment); + + g_object_set (object, + "adjustment", NULL, + NULL); +} + +static void +egg_property_cell_renderer_set_renderer (EggPropertyCellRenderer *renderer, + const gchar *prop_name) +{ + EggPropertyCellRendererPrivate *priv; + GParamSpec *pspec; + gchar *text = NULL; + gdouble number; + + priv = EGG_PROPERTY_CELL_RENDERER_GET_PRIVATE (renderer); + pspec = get_pspec_from_object (priv->object, prop_name); + + /* + * Set this renderers mode, so that any actions can be forwarded to our + * child renderers. + */ + switch (pspec->value_type) { + /* toggle renderers */ + case G_TYPE_BOOLEAN: + priv->renderer = priv->toggle_renderer; + g_object_set (renderer, "mode", GTK_CELL_RENDERER_MODE_ACTIVATABLE, NULL); + break; + + /* spin renderers */ + case G_TYPE_FLOAT: + case G_TYPE_DOUBLE: + priv->renderer = priv->spin_renderer; + g_object_set (renderer, "mode", GTK_CELL_RENDERER_MODE_EDITABLE, NULL); + g_object_set (priv->renderer, "digits", 5, NULL); + break; + + case G_TYPE_INT: + case G_TYPE_UINT: + case G_TYPE_LONG: + case G_TYPE_ULONG: + case G_TYPE_INT64: + case G_TYPE_UINT64: + priv->renderer = priv->spin_renderer; + g_object_set (renderer, "mode", GTK_CELL_RENDERER_MODE_EDITABLE, NULL); + g_object_set (priv->renderer, "digits", 0, NULL); + break; + + /* text renderers */ + case G_TYPE_POINTER: + case G_TYPE_STRING: + priv->renderer = priv->text_renderer; + g_object_set (renderer, "mode", GTK_CELL_RENDERER_MODE_EDITABLE, NULL); + break; + + /* combo renderers */ + default: + if (G_TYPE_IS_ENUM (pspec->value_type)) { + priv->renderer = priv->combo_renderer; + g_object_set (renderer, "mode", GTK_CELL_RENDERER_MODE_EDITABLE, NULL); + } + break; + } + + /* + * Set the content from the objects property. + */ + switch (pspec->value_type) { + case G_TYPE_BOOLEAN: + { + gboolean val; + + g_object_get (priv->object, prop_name, &val, NULL); + g_object_set (priv->renderer, + "active", val, + "activatable", pspec->flags & G_PARAM_WRITABLE ? TRUE : FALSE, + NULL); + break; + } + + case G_TYPE_INT: + case G_TYPE_UINT: + case G_TYPE_LONG: + case G_TYPE_ULONG: + case G_TYPE_INT64: + case G_TYPE_UINT64: + case G_TYPE_FLOAT: + case G_TYPE_DOUBLE: + get_string_double_repr (priv->object, prop_name, &text, &number); + break; + + case G_TYPE_STRING: + g_object_get (priv->object, prop_name, &text, NULL); + break; + + case G_TYPE_POINTER: + { + gpointer val; + + g_object_get (priv->object, prop_name, &val, NULL); + text = g_strdup_printf ("0x%x", GPOINTER_TO_INT (val)); + } + break; + + default: + if (G_TYPE_IS_ENUM (pspec->value_type)) { + GParamSpecEnum *pspec_enum; + GEnumClass *enum_class; + GtkTreeModel *combo_model; + GtkTreeIter iter; + gint value; + + g_object_get (priv->object, prop_name, &value, NULL); + + pspec_enum = G_PARAM_SPEC_ENUM (pspec); + enum_class = pspec_enum->enum_class; + combo_model = g_hash_table_lookup (priv->combo_models, prop_name); + + if (combo_model == NULL) { + combo_model = GTK_TREE_MODEL (gtk_list_store_new (N_COMBO_COLUMNS, G_TYPE_STRING, G_TYPE_INT)); + g_hash_table_insert (priv->combo_models, g_strdup (prop_name), combo_model); + + for (guint i = 0; i < enum_class->n_values; i++) { + gtk_list_store_append (GTK_LIST_STORE (combo_model), &iter); + gtk_list_store_set (GTK_LIST_STORE (combo_model), &iter, + COMBO_COLUMN_VALUE_NAME, enum_class->values[i].value_name, + COMBO_COLUMN_VALUE, enum_class->values[i].value, + -1); + } + } + + + for (guint i = 0; i < enum_class->n_values; i++) { + if (enum_class->values[i].value == value) + text = g_strdup (enum_class->values[i].value_name); + } + + g_object_set (priv->renderer, + "model", combo_model, + "text-column", 0, + NULL); + } + break; + } + + if (pspec->flags & G_PARAM_WRITABLE) { + if (GTK_IS_CELL_RENDERER_TOGGLE (priv->renderer)) + g_object_set (priv->renderer, "mode", GTK_CELL_RENDERER_MODE_ACTIVATABLE, NULL); + else + g_object_set (priv->renderer, "foreground", "#000000", NULL); + + if (GTK_IS_CELL_RENDERER_TEXT (priv->renderer)) { + g_object_set (priv->renderer, + "editable", TRUE, + "mode", GTK_CELL_RENDERER_MODE_EDITABLE, + NULL); + } + + if (GTK_IS_CELL_RENDERER_SPIN (priv->renderer)) { + GtkObject *adjustment = NULL; + +#define gtk_typed_adjustment_new(type, pspec, val, step_inc, page_inc) \ + gtk_adjustment_new (val, ((type *) pspec)->minimum, ((type *) pspec)->maximum, step_inc, page_inc, 0) + + switch (pspec->value_type) { + case G_TYPE_INT: + adjustment = gtk_typed_adjustment_new (GParamSpecInt, pspec, number, 1, 10); + break; + case G_TYPE_UINT: + adjustment = gtk_typed_adjustment_new (GParamSpecUInt, pspec, number, 1, 10); + break; + case G_TYPE_LONG: + adjustment = gtk_typed_adjustment_new (GParamSpecLong, pspec, number, 1, 10); + break; + case G_TYPE_ULONG: + adjustment = gtk_typed_adjustment_new (GParamSpecULong, pspec, number, 1, 10); + break; + case G_TYPE_INT64: + adjustment = gtk_typed_adjustment_new (GParamSpecInt64, pspec, number, 1, 10); + break; + case G_TYPE_UINT64: + adjustment = gtk_typed_adjustment_new (GParamSpecUInt64, pspec, number, 1, 10); + break; + case G_TYPE_FLOAT: + adjustment = gtk_typed_adjustment_new (GParamSpecFloat, pspec, number, 0.05, 10); + break; + case G_TYPE_DOUBLE: + adjustment = gtk_typed_adjustment_new (GParamSpecDouble, pspec, number, 0.05, 10); + break; + } + + clear_adjustment (G_OBJECT (priv->renderer)); + g_object_set (priv->renderer, "adjustment", adjustment, NULL); + } + } + else { + g_object_set (priv->renderer, "mode", GTK_CELL_RENDERER_MODE_INERT, NULL); + + if (!GTK_IS_CELL_RENDERER_TOGGLE (priv->renderer)) + g_object_set (priv->renderer, "foreground", "#aaaaaa", NULL); + } + + if (text != NULL) { + g_object_set (priv->renderer, "text", text, NULL); + g_free (text); + } +} + +static gchar * +get_prop_name_from_tree_model (GtkTreeModel *model, const gchar *path) +{ + GtkTreeIter iter; + gchar *prop_name = NULL; + + /* TODO: don't assume column 0 to contain the prop name */ + if (gtk_tree_model_get_iter_from_string (model, &iter, path)) + gtk_tree_model_get (model, &iter, 0, &prop_name, -1); + + return prop_name; +} + +static void +egg_property_cell_renderer_toggle_cb (GtkCellRendererToggle *renderer, + gchar *path, + gpointer user_data) +{ + EggPropertyCellRendererPrivate *priv; + gchar *prop_name; + + priv = (EggPropertyCellRendererPrivate *) user_data; + prop_name = get_prop_name_from_tree_model (GTK_TREE_MODEL (priv->list_store), path); + + if (prop_name != NULL) { + gboolean activated; + + g_object_get (priv->object, prop_name, &activated, NULL); + g_object_set (priv->object, prop_name, !activated, NULL); + g_free (prop_name); + } +} + +static void +egg_property_cell_renderer_text_edited_cb (GtkCellRendererText *renderer, + gchar *path, + gchar *new_text, + gpointer user_data) +{ + EggPropertyCellRendererPrivate *priv; + gchar *prop_name; + + priv = (EggPropertyCellRendererPrivate *) user_data; + prop_name = get_prop_name_from_tree_model (GTK_TREE_MODEL (priv->list_store), path); + + if (prop_name != NULL) { + g_object_set (priv->object, prop_name, new_text, NULL); + g_free (prop_name); + } +} + +static void +egg_property_cell_renderer_spin_edited_cb (GtkCellRendererText *renderer, + gchar *path, + gchar *new_text, + gpointer user_data) +{ + EggPropertyCellRendererPrivate *priv; + gchar *prop_name; + + priv = (EggPropertyCellRendererPrivate *) user_data; + prop_name = get_prop_name_from_tree_model (GTK_TREE_MODEL (priv->list_store), path); + + if (prop_name != NULL) { + GParamSpec *pspec; + GValue from = { 0 }; + GValue to = { 0 }; + + pspec = get_pspec_from_object (priv->object, prop_name); + + g_value_init (&from, G_TYPE_DOUBLE); + g_value_init (&to, pspec->value_type); + g_value_set_double (&from, strtod (new_text, NULL)); + + if (g_value_transform (&from, &to)) + g_object_set_property (priv->object, prop_name, &to); + else + g_warning ("Could not transform %s to %s\n", + g_value_get_string (&from), g_type_name (pspec->value_type)); + + g_free (prop_name); + } +} + +static void +egg_property_cell_renderer_changed_cb (GtkCellRendererCombo *combo, + gchar *path, + GtkTreeIter *new_iter, + gpointer user_data) +{ + EggPropertyCellRendererPrivate *priv; + gchar *prop_name; + + priv = (EggPropertyCellRendererPrivate *) user_data; + prop_name = get_prop_name_from_tree_model (GTK_TREE_MODEL (priv->list_store), path); + + if (prop_name != NULL) { + GtkTreeModel *combo_model; + gchar *value_name; + gint value; + + combo_model = g_hash_table_lookup (priv->combo_models, prop_name); + + gtk_tree_model_get (combo_model, new_iter, + COMBO_COLUMN_VALUE_NAME, &value_name, + COMBO_COLUMN_VALUE, &value, + -1); + + g_object_set (priv->object, prop_name, value, NULL); + g_free (value_name); + g_free (prop_name); + } +} + +static void +egg_property_cell_renderer_get_size (GtkCellRenderer *cell, + GtkWidget *widget, + GdkRectangle *cell_area, + gint *x_offset, + gint *y_offset, + gint *width, + gint *height) +{ + + EggPropertyCellRendererPrivate *priv = EGG_PROPERTY_CELL_RENDERER_GET_PRIVATE (cell); + gtk_cell_renderer_get_size (priv->renderer, widget, cell_area, x_offset, y_offset, width, height); +} + +static void +egg_property_cell_renderer_render (GtkCellRenderer *cell, + GdkDrawable *window, + GtkWidget *widget, + GdkRectangle *background_area, + GdkRectangle *cell_area, + GdkRectangle *expose_area, + GtkCellRendererState flags) +{ + EggPropertyCellRendererPrivate *priv = EGG_PROPERTY_CELL_RENDERER_GET_PRIVATE (cell); + gtk_cell_renderer_render (priv->renderer, window, widget, background_area, cell_area, expose_area, flags); +} + +static gboolean +egg_property_cell_renderer_activate (GtkCellRenderer *cell, + GdkEvent *event, + GtkWidget *widget, + const gchar *path, + GdkRectangle *background_area, + GdkRectangle *cell_area, + GtkCellRendererState flags) +{ + EggPropertyCellRendererPrivate *priv = EGG_PROPERTY_CELL_RENDERER_GET_PRIVATE (cell); + return gtk_cell_renderer_activate (priv->renderer, event, widget, path, background_area, cell_area, flags); +} + +static GtkCellEditable * +egg_property_cell_renderer_start_editing (GtkCellRenderer *cell, + GdkEvent *event, + GtkWidget *widget, + const gchar *path, + GdkRectangle *background_area, + GdkRectangle *cell_area, + GtkCellRendererState flags) +{ + EggPropertyCellRendererPrivate *priv = EGG_PROPERTY_CELL_RENDERER_GET_PRIVATE (cell); + return gtk_cell_renderer_start_editing (priv->renderer, event, widget, path, background_area, cell_area, flags); +} + +static void +egg_property_cell_renderer_dispose (GObject *object) +{ + EggPropertyCellRenderer *renderer = EGG_PROPERTY_CELL_RENDERER (object); + g_hash_table_destroy (renderer->priv->combo_models); +} + +static void +egg_property_cell_renderer_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + g_return_if_fail (EGG_IS_PROPERTY_CELL_RENDERER (object)); + EggPropertyCellRenderer *renderer = EGG_PROPERTY_CELL_RENDERER (object); + + switch (property_id) { + case PROP_PROP_NAME: + egg_property_cell_renderer_set_renderer (renderer, g_value_get_string (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + return; + } +} + +static void +egg_property_cell_renderer_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + g_return_if_fail (EGG_IS_PROPERTY_CELL_RENDERER (object)); + + switch (property_id) { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + return; + } +} + +static void +egg_property_cell_renderer_class_init (EggPropertyCellRendererClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GtkCellRendererClass *cellrenderer_class = GTK_CELL_RENDERER_CLASS (klass); + + gobject_class->set_property = egg_property_cell_renderer_set_property; + gobject_class->get_property = egg_property_cell_renderer_get_property; + gobject_class->dispose = egg_property_cell_renderer_dispose; + + cellrenderer_class->render = egg_property_cell_renderer_render; + cellrenderer_class->get_size = egg_property_cell_renderer_get_size; + cellrenderer_class->activate = egg_property_cell_renderer_activate; + cellrenderer_class->start_editing = egg_property_cell_renderer_start_editing; + + egg_property_cell_renderer_properties[PROP_PROP_NAME] = + g_param_spec_string("prop-name", + "Property name", "Property name", "", + G_PARAM_READWRITE); + + g_object_class_install_property(gobject_class, PROP_PROP_NAME, egg_property_cell_renderer_properties[PROP_PROP_NAME]); + + g_type_class_add_private (klass, sizeof (EggPropertyCellRendererPrivate)); +} + +static void +egg_property_cell_renderer_init (EggPropertyCellRenderer *renderer) +{ + EggPropertyCellRendererPrivate *priv; + + renderer->priv = priv = EGG_PROPERTY_CELL_RENDERER_GET_PRIVATE (renderer); + + priv->text_renderer = gtk_cell_renderer_text_new (); + priv->spin_renderer = gtk_cell_renderer_spin_new (); + priv->toggle_renderer = gtk_cell_renderer_toggle_new (); + priv->combo_renderer = gtk_cell_renderer_combo_new (); + priv->renderer = priv->text_renderer; + priv->combo_models = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref); + + g_object_set (priv->text_renderer, + "editable", TRUE, + NULL); + + g_object_set (priv->spin_renderer, + "editable", TRUE, + NULL); + + g_object_set (priv->toggle_renderer, + "xalign", 0.0f, + "activatable", TRUE, + "mode", GTK_CELL_RENDERER_MODE_ACTIVATABLE, + NULL); + + g_object_set (priv->combo_renderer, + "has-entry", FALSE, + NULL); + + g_signal_connect (priv->spin_renderer, "edited", + G_CALLBACK (egg_property_cell_renderer_spin_edited_cb), priv); + + g_signal_connect (priv->text_renderer, "edited", + G_CALLBACK (egg_property_cell_renderer_text_edited_cb), NULL); + + g_signal_connect (priv->toggle_renderer, "toggled", + G_CALLBACK (egg_property_cell_renderer_toggle_cb), priv); + + g_signal_connect (priv->combo_renderer, "changed", + G_CALLBACK (egg_property_cell_renderer_changed_cb), priv); +} |