/* Copyright (C) 2011, 2012 Matthias Vogelgesang (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 #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); }