[gimp/symmetry: 6/6] Bug 648776 - fixes symmetry painting after Massimo and Mitch's reviews.



commit 6fee94f2447cedc38fa14ef1ef34e7b70e506186
Author: Jehan <jehan girinstud io>
Date:   Thu Jan 21 21:43:15 2016 +0100

    Bug 648776 - fixes symmetry painting after Massimo and Mitch's reviews.
    
    Use a GType for the PROP_SYMMETRY property of GimpImage, and create
    a default "identity" symmetry for an image.
    Thanks Massimo and Mitch for reviewing my code.

 app/core/gimpimage-symmetry.c      |   18 +-
 app/core/gimpimage.c               |   56 ++--
 app/widgets/gimpsymmetryeditor.c   |   18 +-
 app/xcf/xcf-save.c                 |    5 +
 libgimpwidgets/Makefile.am         |    6 +
 libgimpwidgets/gimpgtypecombobox.c |  729 ++++++++++++++++++++++++++++++++++++
 libgimpwidgets/gimpgtypecombobox.h |  103 +++++
 libgimpwidgets/gimpgtypestore.c    |  311 +++++++++++++++
 libgimpwidgets/gimpgtypestore.h    |  100 +++++
 libgimpwidgets/gimppropwidgets.c   |  104 +++++
 libgimpwidgets/gimppropwidgets.h   |    6 +
 libgimpwidgets/gimpwidgets.h       |    2 +
 libgimpwidgets/gimpwidgetstypes.h  |    2 +
 13 files changed, 1410 insertions(+), 50 deletions(-)
---
diff --git a/app/core/gimpimage-symmetry.c b/app/core/gimpimage-symmetry.c
index 1fc300f..b6c6dff 100644
--- a/app/core/gimpimage-symmetry.c
+++ b/app/core/gimpimage-symmetry.c
@@ -55,6 +55,9 @@ gimp_image_symmetry_list (void)
  * @type:  the #GType of the symmetry
  *
  * Creates a new #GimpSymmetry of @type attached to @image.
+ * @type must be a subtype of `GIMP_TYPE_SYMMETRY`.
+ * Note that using the base @type `GIMP_TYPE_SYMMETRY` creates an
+ * identity transformation.
  *
  * Returns: the new #GimpSymmetry.
  **/
@@ -63,6 +66,7 @@ gimp_image_symmetry_new (GimpImage *image,
                          GType      type)
 {
   GimpSymmetry *sym = NULL;
+  g_return_val_if_fail (g_type_is_a (type, GIMP_TYPE_SYMMETRY), NULL);
 
   if (type != G_TYPE_NONE)
     {
@@ -176,21 +180,11 @@ gimp_image_symmetry_select (GimpImage *image,
 GimpSymmetry *
 gimp_image_symmetry_selected (GimpImage *image)
 {
-  static GimpImage    *last_image = NULL;
-  static GimpSymmetry *identity = NULL;
-  GimpImagePrivate    *private;
+  GimpImagePrivate *private;
 
   g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
 
-  if (last_image != image)
-    {
-      if (identity)
-        g_object_unref (identity);
-      identity = gimp_image_symmetry_new (image,
-                                          GIMP_TYPE_SYMMETRY);
-    }
-
   private = GIMP_IMAGE_GET_PRIVATE (image);
 
-  return private->selected_symmetry ? private->selected_symmetry : identity;
+  return private->selected_symmetry;
 }
diff --git a/app/core/gimpimage.c b/app/core/gimpimage.c
index 5900715..6e762ec 100644
--- a/app/core/gimpimage.c
+++ b/app/core/gimpimage.c
@@ -628,10 +628,11 @@ gimp_image_class_init (GimpImageClass *klass)
   g_object_class_override_property (object_class, PROP_BUFFER, "buffer");
 
   g_object_class_install_property (object_class, PROP_SYMMETRY,
-                                   g_param_spec_int ("symmetry",
-                                                     NULL, _("Symmetry"),
-                                                     G_TYPE_NONE, G_MAXINT, G_TYPE_NONE,
-                                                     GIMP_PARAM_READWRITE));
+                                   g_param_spec_gtype ("symmetry",
+                                                       NULL, _("Symmetry"),
+                                                       GIMP_TYPE_SYMMETRY,
+                                                       GIMP_PARAM_READWRITE |
+                                                       G_PARAM_CONSTRUCT));
   g_type_class_add_private (klass, sizeof (GimpImagePrivate));
 }
 
@@ -873,7 +874,8 @@ gimp_image_set_property (GObject      *object,
       break;
     case PROP_SYMMETRY:
       {
-        GType type = g_value_get_int (value);
+        GList *iter;
+        GType  type = g_value_get_gtype (value);
 
         if (private->selected_symmetry)
           g_object_set (private->selected_symmetry,
@@ -881,29 +883,25 @@ gimp_image_set_property (GObject      *object,
                         NULL);
         private->selected_symmetry = NULL;
 
-        if (type != G_TYPE_NONE)
+
+        for (iter = private->symmetries; iter; iter = g_list_next (iter))
           {
-            GList *iter;
-
-            for (iter = private->symmetries; iter; iter = g_list_next (iter))
-              {
-                GimpSymmetry *sym = iter->data;
-                if (g_type_is_a (sym->type, type))
-                  private->selected_symmetry = iter->data;
-              }
-
-            if (private->selected_symmetry == NULL)
-              {
-                GimpSymmetry *sym;
-
-                sym = gimp_image_symmetry_new (image, type);
-                gimp_image_symmetry_add (image, sym);
-                private->selected_symmetry = sym;
-              }
-            g_object_set (private->selected_symmetry,
-                          "active", TRUE,
-                          NULL);
+            GimpSymmetry *sym = iter->data;
+            if (type == sym->type)
+              private->selected_symmetry = iter->data;
+          }
+
+        if (private->selected_symmetry == NULL)
+          {
+            GimpSymmetry *sym;
+
+            sym = gimp_image_symmetry_new (image, type);
+            gimp_image_symmetry_add (image, sym);
+            private->selected_symmetry = sym;
           }
+        g_object_set (private->selected_symmetry,
+                      "active", TRUE,
+                      NULL);
       }
       break;
     default:
@@ -948,9 +946,9 @@ gimp_image_get_property (GObject    *object,
       g_value_set_object (value, gimp_image_get_buffer (GIMP_PICKABLE (image)));
       break;
     case PROP_SYMMETRY:
-      g_value_set_int (value,
-                       private->selected_symmetry ?
-                       private->selected_symmetry->type : G_TYPE_NONE);
+      g_value_set_gtype (value,
+                         private->selected_symmetry ?
+                         private->selected_symmetry->type : GIMP_TYPE_SYMMETRY);
       break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
diff --git a/app/widgets/gimpsymmetryeditor.c b/app/widgets/gimpsymmetryeditor.c
index eb5169d..8effb69 100644
--- a/app/widgets/gimpsymmetryeditor.c
+++ b/app/widgets/gimpsymmetryeditor.c
@@ -258,7 +258,7 @@ gimp_symmetry_editor_image_changed (GimpContext        *context,
       GList        *sym_iter;
       GimpSymmetry *symmetry;
 
-      store = gimp_int_store_new ();
+      store = gimp_gtype_store_new ();
 
       /* The menu of available symmetries. */
       syms = gimp_image_symmetry_list ();
@@ -272,9 +272,9 @@ gimp_symmetry_editor_image_changed (GimpContext        *context,
 
           gtk_list_store_prepend (store, &iter);
           gtk_list_store_set (store, &iter,
-                              GIMP_INT_STORE_LABEL,
+                              GIMP_GTYPE_STORE_LABEL,
                               klass->label,
-                              GIMP_INT_STORE_VALUE,
+                              GIMP_GTYPE_STORE_VALUE,
                               sym_iter->data,
                               -1);
           g_type_class_unref (klass);
@@ -283,15 +283,15 @@ gimp_symmetry_editor_image_changed (GimpContext        *context,
 
       gtk_list_store_prepend (store, &iter);
       gtk_list_store_set (store, &iter,
-                          GIMP_INT_STORE_LABEL, _("None"),
-                          GIMP_INT_STORE_VALUE, G_TYPE_NONE,
+                          GIMP_GTYPE_STORE_LABEL, _("None"),
+                          GIMP_GTYPE_STORE_VALUE, GIMP_TYPE_SYMMETRY,
                           -1);
-      editor->p->menu = gimp_prop_int_combo_box_new (G_OBJECT (image),
-                                                     "symmetry",
-                                                     GIMP_INT_STORE (store));
+      editor->p->menu = gimp_prop_gtype_combo_box_new (G_OBJECT (image),
+                                                       "symmetry",
+                                                       GIMP_GTYPE_STORE (store));
       g_object_unref (store);
 
-      gimp_int_combo_box_set_label (GIMP_INT_COMBO_BOX (editor->p->menu),
+      gimp_gtype_combo_box_set_label (GIMP_GTYPE_COMBO_BOX (editor->p->menu),
                                     _("Symmetry"));
       g_object_set (editor->p->menu, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
 
diff --git a/app/xcf/xcf-save.c b/app/xcf/xcf-save.c
index 0e8e0ae..5d69a8f 100644
--- a/app/xcf/xcf-save.c
+++ b/app/xcf/xcf-save.c
@@ -435,9 +435,14 @@ xcf_save_image_props (XcfInfo    *info,
   if (g_list_length (gimp_image_symmetry_get (image)))
     {
       GimpParasite *parasite  = NULL;
+      GimpSymmetry *symmetry;
 
       for (iter = gimp_image_symmetry_get (image); iter; iter = g_list_next (iter))
         {
+          symmetry = GIMP_SYMMETRY (iter->data);
+          if (symmetry->type == GIMP_TYPE_SYMMETRY)
+            /* Do not save the identity symmetry. */
+            continue;
           parasite = gimp_symmetry_to_parasite (GIMP_SYMMETRY (iter->data));
           gimp_parasite_list_add (private->parasites, parasite);
           symmetry_parasites = g_list_prepend (symmetry_parasites, parasite);
diff --git a/libgimpwidgets/Makefile.am b/libgimpwidgets/Makefile.am
index f15900f..bf57084 100644
--- a/libgimpwidgets/Makefile.am
+++ b/libgimpwidgets/Makefile.am
@@ -122,6 +122,10 @@ libgimpwidgets_sources = \
        gimpfileentry.h                 \
        gimpframe.c                     \
        gimpframe.h                     \
+       gimpgtypecombobox.c             \
+       gimpgtypecombobox.h             \
+       gimpgtypestore.c                \
+       gimpgtypestore.h                \
        gimphelpui.c                    \
        gimphelpui.h                    \
        gimphintbox.c                   \
@@ -228,6 +232,8 @@ libgimpwidgetsinclude_HEADERS = \
        gimpenumwidgets.h               \
        gimpfileentry.h                 \
        gimpframe.h                     \
+       gimpgtypecombobox.h             \
+       gimpgtypestore.h                \
        gimphelpui.h                    \
        gimphintbox.h                   \
        gimpicons.h                     \
diff --git a/libgimpwidgets/gimpgtypecombobox.c b/libgimpwidgets/gimpgtypecombobox.c
new file mode 100644
index 0000000..63cdffa
--- /dev/null
+++ b/libgimpwidgets/gimpgtypecombobox.c
@@ -0,0 +1,729 @@
+/* LIBGIMP - The GIMP Library
+ * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball
+ *
+ * gimpgtypecombobox.c
+ * Copyright (C) 2004  Sven Neumann <sven gimp org>
+ * Copyright (C) 2015  Jehan <jehan girinstud io>
+ *
+ * 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 3 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, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <libintl.h>
+
+#include <gtk/gtk.h>
+
+#include "gimpwidgetstypes.h"
+
+#include "gimpgtypecombobox.h"
+#include "gimpgtypestore.h"
+
+
+/**
+ * SECTION: gimpgtypecombobox
+ * @title:  GimpGTypeComboBox
+ * @short_description: A widget providing a popup menu of GType
+ *                     values.
+ *
+ * A widget providing a popup menu of GType values.
+ **/
+
+
+enum
+{
+  PROP_0,
+  PROP_ELLIPSIZE,
+  PROP_LABEL
+};
+
+
+typedef struct
+{
+  GtkCellRenderer          *pixbuf_renderer;
+  GtkCellRenderer          *text_renderer;
+
+  PangoEllipsizeMode        ellipsize;
+  gchar                    *label;
+  GtkCellRenderer          *label_renderer;
+
+  GimpGTypeSensitivityFunc  sensitivity_func;
+  gpointer                  sensitivity_data;
+  GDestroyNotify            sensitivity_destroy;
+} GimpGTypeComboBoxPrivate;
+
+#define GIMP_GTYPE_COMBO_BOX_GET_PRIVATE(obj) \
+  ((GimpGTypeComboBoxPrivate *) ((GimpGTypeComboBox *) (obj))->priv)
+
+
+static void  gimp_gtype_combo_box_finalize     (GObject           *object);
+static void  gimp_gtype_combo_box_set_property (GObject           *object,
+                                                guint              property_id,
+                                                const GValue      *value,
+                                                GParamSpec        *pspec);
+static void  gimp_gtype_combo_box_get_property (GObject           *object,
+                                                guint              property_id,
+                                                GValue            *value,
+                                                GParamSpec        *pspec);
+
+static void  gimp_gtype_combo_box_create_cells (GimpGTypeComboBox *combo_box);
+static void  gimp_gtype_combo_box_data_func    (GtkCellLayout     *layout,
+                                                GtkCellRenderer   *cell,
+                                                GtkTreeModel      *model,
+                                                GtkTreeIter       *iter,
+                                                gpointer           data);
+
+
+G_DEFINE_TYPE (GimpGTypeComboBox, gimp_gtype_combo_box, GTK_TYPE_COMBO_BOX)
+
+#define parent_class gimp_gtype_combo_box_parent_class
+
+static void
+gimp_gtype_combo_box_class_init (GimpGTypeComboBoxClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize     = gimp_gtype_combo_box_finalize;
+  object_class->set_property = gimp_gtype_combo_box_set_property;
+  object_class->get_property = gimp_gtype_combo_box_get_property;
+
+  /**
+   * GimpGTypeComboBox:ellipsize:
+   *
+   * Specifies the preferred place to ellipsize text in the combo-box,
+   * if the cell renderer does not have enough room to display the
+   * entire string.
+   *
+   * Since: 2.10
+   */
+  g_object_class_install_property (object_class, PROP_ELLIPSIZE,
+                                   g_param_spec_enum ("ellipsize", NULL, NULL,
+                                                      PANGO_TYPE_ELLIPSIZE_MODE,
+                                                      PANGO_ELLIPSIZE_NONE,
+                                                      GIMP_PARAM_READWRITE));
+
+  /**
+   * GimpGTypeComboBox:label:
+   *
+   * Sets a label on the combo-box, see gimp_gtype_combo_box_set_label().
+   *
+   * Since: 2.10
+   */
+  g_object_class_install_property (object_class, PROP_LABEL,
+                                   g_param_spec_string ("label", NULL, NULL,
+                                                        NULL,
+                                                        GIMP_PARAM_READWRITE));
+
+  g_type_class_add_private (object_class, sizeof (GimpGTypeComboBoxPrivate));
+}
+
+static void
+gimp_gtype_combo_box_init (GimpGTypeComboBox *combo_box)
+{
+  GtkListStore *store;
+
+  combo_box->priv = G_TYPE_INSTANCE_GET_PRIVATE (combo_box,
+                                                 GIMP_TYPE_GTYPE_COMBO_BOX,
+                                                 GimpGTypeComboBoxPrivate);
+
+  store = gimp_gtype_store_new ();
+  gtk_combo_box_set_model (GTK_COMBO_BOX (combo_box), GTK_TREE_MODEL (store));
+  g_object_unref (store);
+
+  gimp_gtype_combo_box_create_cells (GIMP_GTYPE_COMBO_BOX (combo_box));
+}
+
+static void
+gimp_gtype_combo_box_finalize (GObject *object)
+{
+  GimpGTypeComboBoxPrivate *priv = GIMP_GTYPE_COMBO_BOX_GET_PRIVATE (object);
+
+  if (priv->label)
+    {
+      g_free (priv->label);
+      priv->label = NULL;
+    }
+
+  if (priv->sensitivity_destroy)
+    {
+      GDestroyNotify d = priv->sensitivity_destroy;
+
+      priv->sensitivity_destroy = NULL;
+      d (priv->sensitivity_data);
+    }
+
+  G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gimp_gtype_combo_box_set_property (GObject      *object,
+                                   guint         property_id,
+                                   const GValue *value,
+                                   GParamSpec   *pspec)
+{
+  GimpGTypeComboBoxPrivate *priv = GIMP_GTYPE_COMBO_BOX_GET_PRIVATE (object);
+
+  switch (property_id)
+    {
+    case PROP_ELLIPSIZE:
+      priv->ellipsize = g_value_get_enum (value);
+      g_object_set_property (G_OBJECT (priv->text_renderer),
+                             pspec->name, value);
+      break;
+    case PROP_LABEL:
+      gimp_gtype_combo_box_set_label (GIMP_GTYPE_COMBO_BOX (object),
+                                      g_value_get_string (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+      break;
+    }
+}
+
+static void
+gimp_gtype_combo_box_get_property (GObject    *object,
+                                   guint       property_id,
+                                   GValue     *value,
+                                   GParamSpec *pspec)
+{
+  GimpGTypeComboBoxPrivate *priv = GIMP_GTYPE_COMBO_BOX_GET_PRIVATE (object);
+
+  switch (property_id)
+    {
+    case PROP_ELLIPSIZE:
+      g_value_set_enum (value, priv->ellipsize);
+      break;
+    case PROP_LABEL:
+      g_value_set_string (value, priv->label);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+      break;
+    }
+}
+
+
+/**
+ * gimp_gtype_combo_box_new:
+ * @first_label: the label of the first item
+ * @first_value: the value of the first item
+ * @...: a %NULL terminated list of more label, value pairs
+ *
+ * Creates a GtkComboBox that has GType values associated with each
+ * item. The items to fill the combo box with are specified as a %NULL
+ * terminated list of label/value pairs.
+ *
+ * If you need to construct an empty #GimpGTypeComboBox, it's best to use
+ * g_object_new (GIMP_TYPE_GTYPE_COMBO_BOX, NULL).
+ *
+ * Return value: a new #GimpGTypeComboBox.
+ *
+ * Since: 2.10
+ **/
+GtkWidget *
+gimp_gtype_combo_box_new (const gchar *first_label,
+                          GType        first_value,
+                          ...)
+{
+  GtkWidget *combo_box;
+  va_list    args;
+
+  va_start (args, first_value);
+
+  combo_box = gimp_gtype_combo_box_new_valist (first_label, first_value, args);
+
+  va_end (args);
+
+  return combo_box;
+}
+
+/**
+ * gimp_gtype_combo_box_new_valist:
+ * @first_label: the label of the first item
+ * @first_value: the value of the first item
+ * @values: a va_list with more values
+ *
+ * A variant of gimp_gtype_combo_box_new() that takes a va_list of
+ * label/value pairs. Probably only useful for language bindings.
+ *
+ * Return value: a new #GimpGTypeComboBox.
+ *
+ * Since: 2.10
+ **/
+GtkWidget *
+gimp_gtype_combo_box_new_valist (const gchar *first_label,
+                                 GType        first_value,
+                                 va_list      values)
+{
+  GtkWidget    *combo_box;
+  GtkListStore *store;
+  const gchar  *label;
+  GType         value;
+
+  combo_box = g_object_new (GIMP_TYPE_GTYPE_COMBO_BOX, NULL);
+
+  store = GTK_LIST_STORE (gtk_combo_box_get_model (GTK_COMBO_BOX (combo_box)));
+
+  for (label = first_label, value = first_value;
+       label;
+       label = va_arg (values, const gchar *), value = va_arg (values, GType))
+    {
+      GtkTreeIter  iter = { 0, };
+
+      gtk_list_store_append (store, &iter);
+      gtk_list_store_set (store, &iter,
+                          GIMP_GTYPE_STORE_VALUE, value,
+                          GIMP_GTYPE_STORE_LABEL, label,
+                          -1);
+    }
+
+  return combo_box;
+}
+
+/**
+ * gimp_gtype_combo_box_prepend:
+ * @combo_box: a #GimpGTypeComboBox
+ * @...:       pairs of column number and value, terminated with -1
+ *
+ * This function provides a convenient way to prepend items to a
+ * #GimpGTypeComboBox. It prepends a row to the @combo_box's list store
+ * and calls gtk_list_store_set() for you.
+ *
+ * The column number must be taken from the enum #GimpGTypeStoreColumns.
+ *
+ * Since: 2.10
+ **/
+void
+gimp_gtype_combo_box_prepend (GimpGTypeComboBox *combo_box,
+                              ...)
+{
+  GtkListStore *store;
+  GtkTreeIter   iter;
+  va_list       args;
+
+  g_return_if_fail (GIMP_IS_GTYPE_COMBO_BOX (combo_box));
+
+  store = GTK_LIST_STORE (gtk_combo_box_get_model (GTK_COMBO_BOX (combo_box)));
+
+  va_start (args, combo_box);
+
+  gtk_list_store_prepend (store, &iter);
+  gtk_list_store_set_valist (store, &iter, args);
+
+  va_end (args);
+}
+
+/**
+ * gimp_gtype_combo_box_append:
+ * @combo_box: a #GimpGTypeComboBox
+ * @...:       pairs of column number and value, terminated with -1
+ *
+ * This function provides a convenient way to append items to a
+ * #GimpGTypeComboBox. It appends a row to the @combo_box's list store
+ * and calls gtk_list_store_set() for you.
+ *
+ * The column number must be taken from the enum #GimpGTypeStoreColumns.
+ *
+ * Since: 2.10
+ **/
+void
+gimp_gtype_combo_box_append (GimpGTypeComboBox *combo_box,
+                             ...)
+{
+  GtkListStore *store;
+  GtkTreeIter   iter;
+  va_list       args;
+
+  g_return_if_fail (GIMP_IS_GTYPE_COMBO_BOX (combo_box));
+
+  store = GTK_LIST_STORE (gtk_combo_box_get_model (GTK_COMBO_BOX (combo_box)));
+
+  va_start (args, combo_box);
+
+  gtk_list_store_append (store, &iter);
+  gtk_list_store_set_valist (store, &iter, args);
+
+  va_end (args);
+}
+
+/**
+ * gimp_gtype_combo_box_set_active:
+ * @combo_box: a #GimpGTypeComboBox
+ * @value:     a GType value
+ *
+ * Looks up the item that belongs to the given @value and makes it the
+ * selected item in the @combo_box.
+ *
+ * Return value: %TRUE on success or %FALSE if there was no item for
+ *               this value.
+ *
+ * Since: 2.10
+ **/
+gboolean
+gimp_gtype_combo_box_set_active (GimpGTypeComboBox *combo_box,
+                                 GType              value)
+{
+  GtkTreeModel *model;
+  GtkTreeIter   iter;
+
+  g_return_val_if_fail (GIMP_IS_GTYPE_COMBO_BOX (combo_box), FALSE);
+
+  model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo_box));
+
+  if (gimp_gtype_store_lookup_by_value (model, value, &iter))
+    {
+      gtk_combo_box_set_active_iter (GTK_COMBO_BOX (combo_box), &iter);
+      return TRUE;
+    }
+
+  return FALSE;
+}
+
+/**
+ * gimp_gtype_combo_box_get_active:
+ * @combo_box: a #GimpGTypeComboBox
+ * @value:     return location for the GType value
+ *
+ * Retrieves the value of the selected (active) item in the @combo_box.
+ *
+ * Return value: %TRUE if @value has been set or %FALSE if no item was
+ *               active.
+ *
+ * Since: 2.10
+ **/
+gboolean
+gimp_gtype_combo_box_get_active (GimpGTypeComboBox *combo_box,
+                                 GType             *value)
+{
+  GtkTreeIter  iter;
+
+  g_return_val_if_fail (GIMP_IS_GTYPE_COMBO_BOX (combo_box), FALSE);
+  g_return_val_if_fail (value != NULL, FALSE);
+
+  if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (combo_box), &iter))
+    {
+      gtk_tree_model_get (gtk_combo_box_get_model (GTK_COMBO_BOX (combo_box)),
+                          &iter,
+                          GIMP_GTYPE_STORE_VALUE, value,
+                          -1);
+      return TRUE;
+    }
+
+  return FALSE;
+}
+
+/**
+ * gimp_gtype_combo_box_connect:
+ * @combo_box: a #GimpGTypeComboBox
+ * @value:     the value to set
+ * @callback:  a callback to connect to the @combo_box's "changed" signal
+ * @data:      a pointer passed as data to g_signal_connect()
+ *
+ * A convenience function that sets the initial @value of a
+ * #GimpGTypeComboBox and connects @callback to the "changed"
+ * signal.
+ *
+ * This function also calls the @callback once after setting the
+ * initial @value. This is often convenient when working with combo
+ * boxes that select a default active item, like for example
+ * gimp_drawable_combo_box_new(). If you pass an invalid initial
+ * @value, the @callback will be called with the default item active.
+ *
+ * Return value: the signal handler ID as returned by g_signal_connect()
+ *
+ * Since: 2.10
+ **/
+gulong
+gimp_gtype_combo_box_connect (GimpGTypeComboBox *combo_box,
+                              GType              value,
+                              GCallback          callback,
+                              gpointer           data)
+{
+  gulong handler = 0;
+
+  g_return_val_if_fail (GIMP_IS_GTYPE_COMBO_BOX (combo_box), 0);
+
+  if (callback)
+    handler = g_signal_connect (combo_box, "changed", callback, data);
+
+  if (! gimp_gtype_combo_box_set_active (combo_box, value))
+    g_signal_emit_by_name (combo_box, "changed", NULL);
+
+  return handler;
+}
+
+/**
+ * gimp_gtype_combo_box_set_label:
+ * @combo_box: a #GimpGTypeComboBox
+ * @label:     a string to be shown as label
+ *
+ * Sets a caption on the @combo_box that will be displayed
+ * left-aligned inside the box. When a label is set, the remaining
+ * contents of the box will be right-aligned. This is useful for
+ * places where screen estate is rare, like in tool options.
+ *
+ * Since: 2.10
+ **/
+void
+gimp_gtype_combo_box_set_label (GimpGTypeComboBox *combo_box,
+                                const gchar     *label)
+{
+  GimpGTypeComboBoxPrivate *priv;
+
+  g_return_if_fail (GIMP_IS_GTYPE_COMBO_BOX (combo_box));
+
+  priv = GIMP_GTYPE_COMBO_BOX_GET_PRIVATE (combo_box);
+
+  if (label == priv->label)
+    return;
+
+  if (priv->label)
+    {
+      g_free (priv->label);
+      priv->label = NULL;
+
+      g_signal_handlers_disconnect_by_func (combo_box,
+                                            gimp_gtype_combo_box_create_cells,
+                                            NULL);
+    }
+
+  if (label)
+    {
+      priv->label = g_strdup (label);
+
+      g_signal_connect (combo_box, "notify::popup-shown",
+                        G_CALLBACK (gimp_gtype_combo_box_create_cells),
+                        NULL);
+    }
+
+  gimp_gtype_combo_box_create_cells (combo_box);
+
+  g_object_notify (G_OBJECT (combo_box), "label");
+}
+
+/**
+ * gimp_gtype_combo_box_get_label:
+ * @combo_box: a #GimpGTypeComboBox
+ *
+ * Returns the label previously set with gimp_gtype_combo_box_set_label(),
+ * or %NULL,
+ *
+ * Return value: the @combo_box' label.
+ *
+ * Since: 2.10
+ **/
+const gchar *
+gimp_gtype_combo_box_get_label (GimpGTypeComboBox *combo_box)
+{
+  g_return_val_if_fail (GIMP_IS_GTYPE_COMBO_BOX (combo_box), NULL);
+
+  return GIMP_GTYPE_COMBO_BOX_GET_PRIVATE (combo_box)->label;
+}
+
+/**
+ * gimp_gtype_combo_box_set_sensitivity:
+ * @combo_box: a #GimpGTypeComboBox
+ * @func: a function that returns a boolean value, or %NULL to unset
+ * @data: data to pass to @func
+ * @destroy: destroy notification for @data
+ *
+ * Sets a function that is used to decide about the sensitivity of
+ * rows in the @combo_box. Use this if you want to set certain rows
+ * insensitive.
+ *
+ * Calling gtk_widget_queue_draw() on the @combo_box will cause the
+ * sensitivity to be updated.
+ *
+ * Since: 2.10
+ **/
+void
+gimp_gtype_combo_box_set_sensitivity (GimpGTypeComboBox        *combo_box,
+                                      GimpGTypeSensitivityFunc  func,
+                                      gpointer                  data,
+                                      GDestroyNotify            destroy)
+{
+  GimpGTypeComboBoxPrivate *priv;
+
+  g_return_if_fail (GIMP_IS_GTYPE_COMBO_BOX (combo_box));
+
+  priv = GIMP_GTYPE_COMBO_BOX_GET_PRIVATE (combo_box);
+
+  if (priv->sensitivity_destroy)
+    {
+      GDestroyNotify d = priv->sensitivity_destroy;
+
+      priv->sensitivity_destroy = NULL;
+      d (priv->sensitivity_data);
+    }
+
+  priv->sensitivity_func    = func;
+  priv->sensitivity_data    = data;
+  priv->sensitivity_destroy = destroy;
+
+  gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (combo_box),
+                                      priv->pixbuf_renderer,
+                                      func ?
+                                      gimp_gtype_combo_box_data_func : NULL,
+                                      priv, NULL);
+
+  gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (combo_box),
+                                      priv->text_renderer,
+                                      func ?
+                                      gimp_gtype_combo_box_data_func : NULL,
+                                      priv, NULL);
+}
+
+
+/*  private functions  */
+
+static void
+queue_resize_cell_view (GtkContainer *container)
+{
+  GList *children = gtk_container_get_children (container);
+  GList *list;
+
+  for (list = children; list; list = g_list_next (list))
+    {
+      if (GTK_IS_CELL_VIEW (list->data))
+        {
+          gtk_widget_queue_resize (list->data);
+          break;
+        }
+      else if (GTK_IS_CONTAINER (list->data))
+        {
+          queue_resize_cell_view (list->data);
+        }
+    }
+
+  g_list_free (children);
+}
+
+static void
+gimp_gtype_combo_box_create_cells (GimpGTypeComboBox *combo_box)
+{
+  GimpGTypeComboBoxPrivate *priv = GIMP_GTYPE_COMBO_BOX_GET_PRIVATE (combo_box);
+  gboolean                  shown;
+
+  g_object_get (combo_box, "popup-shown", &shown, NULL);
+
+  gtk_cell_layout_clear (GTK_CELL_LAYOUT (combo_box));
+
+  priv->pixbuf_renderer = gtk_cell_renderer_pixbuf_new ();
+  g_object_set (priv->pixbuf_renderer,
+                "xpad", 2,
+                NULL);
+
+  priv->text_renderer = gtk_cell_renderer_text_new ();
+
+  if (! shown)
+    g_object_set (priv->text_renderer,
+                  "ellipsize", priv->ellipsize,
+                  NULL);
+
+  if (priv->label && ! shown)
+    {
+      priv->label_renderer = gtk_cell_renderer_text_new ();
+      g_object_set (priv->label_renderer,
+                    "text", priv->label,
+                    NULL);
+
+      gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo_box),
+                                  priv->label_renderer, FALSE);
+
+      gtk_cell_layout_pack_end (GTK_CELL_LAYOUT (combo_box),
+                                priv->pixbuf_renderer, FALSE);
+      gtk_cell_layout_pack_end (GTK_CELL_LAYOUT (combo_box),
+                                priv->text_renderer, TRUE);
+
+      g_object_set (priv->text_renderer,
+                    "xalign", 1.0,
+                    NULL);
+    }
+  else
+    {
+      gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo_box),
+                                  priv->pixbuf_renderer, FALSE);
+      gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo_box),
+                                  priv->text_renderer, TRUE);
+    }
+
+  gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combo_box),
+                                  priv->pixbuf_renderer,
+                                  "icon-name", GIMP_GTYPE_STORE_ICON_NAME,
+                                  "pixbuf",    GIMP_GTYPE_STORE_PIXBUF,
+                                  NULL);
+  gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combo_box),
+                                  priv->text_renderer,
+                                  "text", GIMP_GTYPE_STORE_LABEL,
+                                  NULL);
+
+  if (priv->sensitivity_func)
+    {
+      gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (combo_box),
+                                          priv->pixbuf_renderer,
+                                          gimp_gtype_combo_box_data_func,
+                                          priv, NULL);
+
+      gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (combo_box),
+                                          priv->text_renderer,
+                                          gimp_gtype_combo_box_data_func,
+                                          priv, NULL);
+    }
+
+  /* HACK: GtkCellView doesn't invalidate itself when stuff is
+   * added/removed, work around this bug until GTK+ 2.24.19
+   */
+  if (gtk_check_version (2, 24, 19))
+    {
+      GList *attached_menus;
+
+      queue_resize_cell_view (GTK_CONTAINER (combo_box));
+
+      /* HACK HACK HACK OMG */
+      attached_menus = g_object_get_data (G_OBJECT (combo_box),
+                                          "gtk-attached-menus");
+
+      for (; attached_menus; attached_menus = g_list_next (attached_menus))
+        queue_resize_cell_view (attached_menus->data);
+    }
+}
+
+static void
+gimp_gtype_combo_box_data_func (GtkCellLayout   *layout,
+                              GtkCellRenderer *cell,
+                              GtkTreeModel    *model,
+                              GtkTreeIter     *iter,
+                              gpointer         data)
+{
+  GimpGTypeComboBoxPrivate *priv = data;
+
+  if (priv->sensitivity_func)
+    {
+      GType     value;
+      gboolean  sensitive;
+
+      gtk_tree_model_get (model, iter,
+                          GIMP_GTYPE_STORE_VALUE, &value,
+                          -1);
+
+      sensitive = priv->sensitivity_func (value, priv->sensitivity_data);
+
+      g_object_set (cell,
+                    "sensitive", sensitive,
+                    NULL);
+    }
+}
diff --git a/libgimpwidgets/gimpgtypecombobox.h b/libgimpwidgets/gimpgtypecombobox.h
new file mode 100644
index 0000000..a2809ed
--- /dev/null
+++ b/libgimpwidgets/gimpgtypecombobox.h
@@ -0,0 +1,103 @@
+/* LIBGIMP - The GIMP Library
+ * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball
+ *
+ * gimpgtypecombobox.h
+ * Copyright (C) 2004  Sven Neumann <sven gimp org>
+ * Copyright (C) 2015  Jehan <jehan girinstud io>
+ *
+ * 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 3 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, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined (__GIMP_WIDGETS_H_INSIDE__) && !defined (GIMP_WIDGETS_COMPILATION)
+#error "Only <libgimpwidgets/gimpwidgets.h> can be included directly."
+#endif
+
+#ifndef __GIMP_GTYPE_COMBO_BOX_H__
+#define __GIMP_GTYPE_COMBO_BOX_H__
+
+G_BEGIN_DECLS
+
+
+#define GIMP_TYPE_GTYPE_COMBO_BOX            (gimp_gtype_combo_box_get_type ())
+#define GIMP_GTYPE_COMBO_BOX(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_GTYPE_COMBO_BOX, 
GimpGTypeComboBox))
+#define GIMP_GTYPE_COMBO_BOX_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_GTYPE_COMBO_BOX, 
GimpGTypeComboBoxClass))
+#define GIMP_IS_GTYPE_COMBO_BOX(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_GTYPE_COMBO_BOX))
+#define GIMP_IS_GTYPE_COMBO_BOX_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_GTYPE_COMBO_BOX))
+#define GIMP_GTYPE_COMBO_BOX_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_GTYPE_COMBO_BOX, 
GimpGTypeComboBoxClass))
+
+
+typedef struct _GimpGTypeComboBoxClass  GimpGTypeComboBoxClass;
+
+struct _GimpGTypeComboBox
+{
+  GtkComboBox       parent_instance;
+
+  /*< private >*/
+  gpointer          priv;
+
+  /* Padding for future expansion (should have gone to the class) */
+  void (* _gimp_reserved2) (void);
+  void (* _gimp_reserved3) (void);
+  void (* _gimp_reserved4) (void);
+};
+
+struct _GimpGTypeComboBoxClass
+{
+  GtkComboBoxClass  parent_class;
+};
+
+
+typedef  gboolean (* GimpGTypeSensitivityFunc)    (GType             value,
+                                                   gpointer          data);
+
+
+
+GType         gimp_gtype_combo_box_get_type        (void) G_GNUC_CONST;
+
+GtkWidget   * gimp_gtype_combo_box_new             (const gchar       *first_label,
+                                                    GType              first_value,
+                                                    ...) G_GNUC_NULL_TERMINATED;
+GtkWidget   * gimp_gtype_combo_box_new_valist      (const gchar       *first_label,
+                                                    GType              first_value,
+                                                    va_list            values);
+
+void          gimp_gtype_combo_box_prepend         (GimpGTypeComboBox *combo_box,
+                                                    ...);
+void          gimp_gtype_combo_box_append          (GimpGTypeComboBox *combo_box,
+                                                    ...);
+
+gboolean      gimp_gtype_combo_box_set_active      (GimpGTypeComboBox *combo_box,
+                                                    GType              value);
+gboolean      gimp_gtype_combo_box_get_active      (GimpGTypeComboBox *combo_box,
+                                                    GType             *value);
+
+gulong        gimp_gtype_combo_box_connect         (GimpGTypeComboBox *combo_box,
+                                                    GType              value,
+                                                    GCallback          callback,
+                                                    gpointer           data);
+
+void          gimp_gtype_combo_box_set_label       (GimpGTypeComboBox *combo_box,
+                                                    const gchar       *label);
+const gchar * gimp_gtype_combo_box_get_label       (GimpGTypeComboBox *combo_box);
+
+void          gimp_gtype_combo_box_set_sensitivity (GimpGTypeComboBox        *combo_box,
+                                                    GimpGTypeSensitivityFunc  func,
+                                                    gpointer                  data,
+                                                    GDestroyNotify            destroy);
+
+
+G_END_DECLS
+
+#endif  /* __GIMP_GTYPE_COMBO_BOX_H__ */
diff --git a/libgimpwidgets/gimpgtypestore.c b/libgimpwidgets/gimpgtypestore.c
new file mode 100644
index 0000000..d96b5e9
--- /dev/null
+++ b/libgimpwidgets/gimpgtypestore.c
@@ -0,0 +1,311 @@
+/* LIBGIMP - The GIMP Library
+ * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball
+ *
+ * gimpgtypestore.c
+ * Copyright (C) 2004-2007  Sven Neumann <sven gimp org>
+ * Copyright (C) 2015 Jehan <jehan girinstud io>
+ *
+ * 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 3 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, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gtk/gtk.h>
+
+#include "gimpwidgetstypes.h"
+
+#include "gimpgtypestore.h"
+
+#include "libgimp/libgimp-intl.h"
+
+
+/**
+ * SECTION: gimpgtypestore
+ * @title: GimpGTypeStore
+ * @short_description: A model for GType based name-value pairs.
+ *
+ * A model for GType based name-value pairs.
+ **/
+
+
+enum
+{
+  PROP_0,
+  PROP_USER_DATA_TYPE
+};
+
+typedef struct
+{
+  GType  user_data_type;
+} GimpGTypeStorePrivate;
+
+
+static void  gimp_gtype_store_tree_model_init (GtkTreeModelIface *iface);
+
+static void  gimp_gtype_store_constructed     (GObject           *object);
+static void  gimp_gtype_store_finalize        (GObject           *object);
+static void  gimp_gtype_store_set_property    (GObject           *object,
+                                               guint              property_id,
+                                               const GValue      *value,
+                                               GParamSpec        *pspec);
+static void  gimp_gtype_store_get_property    (GObject           *object,
+                                               guint              property_id,
+                                               GValue            *value,
+                                               GParamSpec        *pspec);
+
+static void  gimp_gtype_store_row_inserted    (GtkTreeModel      *model,
+                                               GtkTreePath       *path,
+                                               GtkTreeIter       *iter);
+static void  gimp_gtype_store_row_deleted     (GtkTreeModel      *model,
+                                               GtkTreePath       *path);
+static void  gimp_gtype_store_add_empty       (GimpGTypeStore    *store);
+
+
+G_DEFINE_TYPE_WITH_CODE (GimpGTypeStore, gimp_gtype_store, GTK_TYPE_LIST_STORE,
+                         G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_MODEL,
+                                                gimp_gtype_store_tree_model_init))
+
+#define GIMP_GTYPE_STORE_GET_PRIVATE(obj) \
+  G_TYPE_INSTANCE_GET_PRIVATE (obj, GIMP_TYPE_GTYPE_STORE, GimpGTypeStorePrivate)
+
+#define parent_class gimp_gtype_store_parent_class
+
+static GtkTreeModelIface *parent_iface = NULL;
+
+
+static void
+gimp_gtype_store_class_init (GimpGTypeStoreClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->constructed  = gimp_gtype_store_constructed;
+  object_class->finalize     = gimp_gtype_store_finalize;
+  object_class->set_property = gimp_gtype_store_set_property;
+  object_class->get_property = gimp_gtype_store_get_property;
+
+  /**
+   * GimpGTypeStore:user-data-type:
+   *
+   * Sets the #GType for the GIMP_GTYPE_STORE_USER_DATA column.
+   *
+   * You need to set this property when constructing the store if you want
+   * to use the GIMP_GTYPE_STORE_USER_DATA column and want to have the store
+   * handle ref-counting of your user data.
+   *
+   * Since: 2.10
+   */
+  g_object_class_install_property (object_class,
+                                   PROP_USER_DATA_TYPE,
+                                   g_param_spec_gtype ("user-data-type",
+                                                       NULL, NULL,
+                                                       G_TYPE_NONE,
+                                                       G_PARAM_CONSTRUCT_ONLY |
+                                                       GIMP_PARAM_READWRITE));
+
+  g_type_class_add_private (object_class, sizeof (GimpGTypeStorePrivate));
+}
+
+static void
+gimp_gtype_store_tree_model_init (GtkTreeModelIface *iface)
+{
+  parent_iface = g_type_interface_peek_parent (iface);
+
+  iface->row_inserted = gimp_gtype_store_row_inserted;
+  iface->row_deleted  = gimp_gtype_store_row_deleted;
+}
+
+static void
+gimp_gtype_store_init (GimpGTypeStore *store)
+{
+  store->empty_iter = NULL;
+}
+
+static void
+gimp_gtype_store_constructed (GObject *object)
+{
+  GimpGTypeStore        *store = GIMP_GTYPE_STORE (object);
+  GimpGTypeStorePrivate *priv  = GIMP_GTYPE_STORE_GET_PRIVATE (store);
+  GType                  types[GIMP_GTYPE_STORE_NUM_COLUMNS];
+
+  G_OBJECT_CLASS (parent_class)->constructed (object);
+
+  types[GIMP_GTYPE_STORE_VALUE]     = G_TYPE_GTYPE;
+  types[GIMP_GTYPE_STORE_LABEL]     = G_TYPE_STRING;
+  types[GIMP_GTYPE_STORE_ICON_NAME] = G_TYPE_STRING;
+  types[GIMP_GTYPE_STORE_PIXBUF]    = GDK_TYPE_PIXBUF;
+  types[GIMP_GTYPE_STORE_USER_DATA] = (priv->user_data_type != G_TYPE_NONE ?
+                                       priv->user_data_type : G_TYPE_POINTER);
+
+  gtk_list_store_set_column_types (GTK_LIST_STORE (store),
+                                   GIMP_GTYPE_STORE_NUM_COLUMNS, types);
+
+  gimp_gtype_store_add_empty (store);
+}
+
+static void
+gimp_gtype_store_finalize (GObject *object)
+{
+  GimpGTypeStore *store = GIMP_GTYPE_STORE (object);
+
+  if (store->empty_iter)
+    {
+      gtk_tree_iter_free (store->empty_iter);
+      store->empty_iter = NULL;
+    }
+
+  G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gimp_gtype_store_set_property (GObject      *object,
+                               guint         property_id,
+                               const GValue *value,
+                               GParamSpec   *pspec)
+{
+  GimpGTypeStorePrivate *priv = GIMP_GTYPE_STORE_GET_PRIVATE (object);
+
+  switch (property_id)
+    {
+    case PROP_USER_DATA_TYPE:
+      priv->user_data_type = g_value_get_gtype (value);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+      break;
+    }
+}
+
+static void
+gimp_gtype_store_get_property (GObject    *object,
+                               guint       property_id,
+                               GValue     *value,
+                               GParamSpec *pspec)
+{
+  GimpGTypeStorePrivate *priv = GIMP_GTYPE_STORE_GET_PRIVATE (object);
+
+  switch (property_id)
+    {
+    case PROP_USER_DATA_TYPE:
+      g_value_set_gtype (value, priv->user_data_type);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+      break;
+    }
+}
+
+static void
+gimp_gtype_store_row_inserted (GtkTreeModel *model,
+                               GtkTreePath  *path,
+                               GtkTreeIter  *iter)
+{
+  GimpGTypeStore *store = GIMP_GTYPE_STORE (model);
+
+  if (parent_iface->row_inserted)
+    parent_iface->row_inserted (model, path, iter);
+
+  if (store->empty_iter &&
+      memcmp (iter, store->empty_iter, sizeof (GtkTreeIter)))
+    {
+      gtk_list_store_remove (GTK_LIST_STORE (store), store->empty_iter);
+      gtk_tree_iter_free (store->empty_iter);
+      store->empty_iter = NULL;
+    }
+}
+
+static void
+gimp_gtype_store_row_deleted (GtkTreeModel *model,
+                              GtkTreePath  *path)
+{
+  if (parent_iface->row_deleted)
+    parent_iface->row_deleted (model, path);
+}
+
+static void
+gimp_gtype_store_add_empty (GimpGTypeStore *store)
+{
+  GtkTreeIter iter = { 0, };
+
+  g_return_if_fail (store->empty_iter == NULL);
+
+  gtk_list_store_prepend (GTK_LIST_STORE (store), &iter);
+  gtk_list_store_set (GTK_LIST_STORE (store), &iter,
+                      GIMP_GTYPE_STORE_VALUE, -1,
+                      /* This string appears in an empty menu as in
+                       * "nothing selected and nothing to select"
+                       */
+                      GIMP_GTYPE_STORE_LABEL, (_("(Empty)")),
+                      -1);
+
+  store->empty_iter = gtk_tree_iter_copy (&iter);
+}
+
+/**
+ * gimp_gtype_store_new:
+ *
+ * Creates a #GtkListStore with a number of useful columns.
+ * #GimpGTypeStore is especially useful if the items you want to store
+ * are identified using an GType value.
+ *
+ * Return value: a new #GimpGTypeStore.
+ *
+ * Since: 2.10
+ **/
+GtkListStore *
+gimp_gtype_store_new (void)
+{
+  return g_object_new (GIMP_TYPE_GTYPE_STORE, NULL);
+}
+
+/**
+ * gimp_gtype_store_lookup_by_value:
+ * @model: a #GimpGTypeStore
+ * @value: a #GType value to lookup in the @model
+ * @iter:  return location for the iter of the given @value
+ *
+ * Iterate over the @model looking for @value.
+ *
+ * Return value: %TRUE if the value has been located and @iter is
+ *               valid, %FALSE otherwise.
+ *
+ * Since: 2.10
+ **/
+gboolean
+gimp_gtype_store_lookup_by_value (GtkTreeModel *model,
+                                  GType         value,
+                                  GtkTreeIter  *iter)
+{
+  gboolean  iter_valid;
+
+  g_return_val_if_fail (GTK_IS_TREE_MODEL (model), FALSE);
+  g_return_val_if_fail (iter != NULL, FALSE);
+
+  for (iter_valid = gtk_tree_model_get_iter_first (model, iter);
+       iter_valid;
+       iter_valid = gtk_tree_model_iter_next (model, iter))
+    {
+      GType  this;
+
+      gtk_tree_model_get (model, iter,
+                          GIMP_GTYPE_STORE_VALUE, &this,
+                          -1);
+      if (this == value)
+        break;
+    }
+
+  return iter_valid;
+}
diff --git a/libgimpwidgets/gimpgtypestore.h b/libgimpwidgets/gimpgtypestore.h
new file mode 100644
index 0000000..1420113
--- /dev/null
+++ b/libgimpwidgets/gimpgtypestore.h
@@ -0,0 +1,100 @@
+/* LIBGIMP - The GIMP Library
+ * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball
+ *
+ * gimpgtypestore.c
+ * Copyright (C) 2004  Sven Neumann <sven gimp org>
+ * Copyright (C) 2015 Jehan <jehan girinstud io>
+ *
+ * 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 3 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, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined (__GIMP_WIDGETS_H_INSIDE__) && !defined (GIMP_WIDGETS_COMPILATION)
+#error "Only <libgimpwidgets/gimpwidgets.h> can be included directly."
+#endif
+
+#ifndef __GIMP_GTYPE_STORE_H__
+#define __GIMP_GTYPE_STORE_H__
+
+G_BEGIN_DECLS
+
+
+/**
+ * GimpGTypeStoreColumns:
+ * @GIMP_GTYPE_STORE_VALUE:       the GType value
+ * @GIMP_GTYPE_STORE_LABEL:       a human-readable label
+ * @GIMP_GTYPE_STORE_ICON_NAME:   an icon name
+ * @GIMP_GTYPE_STORE_PIXBUF:      a #GdkPixbuf
+ * @GIMP_GTYPE_STORE_USER_DATA:   arbitrary user data
+ * @GIMP_GTYPE_STORE_NUM_COLUMNS: the number of columns
+ * @GIMP_GTYPE_STORE_STOCK_ID:    compat alias for @GIMP_GTYPE_STORE_ICON_NAME
+ *
+ * The column types of #GimpGTypeStore.
+ **/
+typedef enum
+{
+  GIMP_GTYPE_STORE_VALUE,
+  GIMP_GTYPE_STORE_LABEL,
+  GIMP_GTYPE_STORE_ICON_NAME,
+  GIMP_GTYPE_STORE_PIXBUF,
+  GIMP_GTYPE_STORE_USER_DATA,
+  GIMP_GTYPE_STORE_NUM_COLUMNS,
+
+  /* deprecated */
+  GIMP_GTYPE_STORE_STOCK_ID = GIMP_GTYPE_STORE_ICON_NAME
+} GimpGTypeStoreColumns;
+
+
+#define GIMP_TYPE_GTYPE_STORE            (gimp_gtype_store_get_type ())
+#define GIMP_GTYPE_STORE(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_GTYPE_STORE, 
GimpGTypeStore))
+#define GIMP_GTYPE_STORE_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_GTYPE_STORE, 
GimpGTypeStoreClass))
+#define GIMP_IS_GTYPE_STORE(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_GTYPE_STORE))
+#define GIMP_IS_GTYPE_STORE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_GTYPE_STORE))
+#define GIMP_GTYPE_STORE_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_GTYPE_STORE, 
GimpGTypeStoreClass))
+
+
+typedef struct _GimpGTypeStoreClass  GimpGTypeStoreClass;
+
+struct _GimpGTypeStore
+{
+  GtkListStore  parent_instance;
+
+  /*< private >*/
+  GtkTreeIter  *empty_iter;
+};
+
+struct _GimpGTypeStoreClass
+{
+  GtkListStoreClass  parent_class;
+
+  /* Padding for future expansion */
+  void (* _gimp_reserved1) (void);
+  void (* _gimp_reserved2) (void);
+  void (* _gimp_reserved3) (void);
+  void (* _gimp_reserved4) (void);
+};
+
+
+GType          gimp_gtype_store_get_type        (void) G_GNUC_CONST;
+
+GtkListStore * gimp_gtype_store_new             (void);
+
+gboolean       gimp_gtype_store_lookup_by_value (GtkTreeModel  *model,
+                                                 GType          value,
+                                                 GtkTreeIter   *iter);
+
+
+G_END_DECLS
+
+#endif  /* __GIMP_GTYPE_STORE_H__ */
diff --git a/libgimpwidgets/gimppropwidgets.c b/libgimpwidgets/gimppropwidgets.c
index 37e91ce..7d08458 100644
--- a/libgimpwidgets/gimppropwidgets.c
+++ b/libgimpwidgets/gimppropwidgets.c
@@ -510,6 +510,110 @@ gimp_prop_int_combo_box_notify (GObject    *config,
                                      config);
 }
 
+/************************/
+/*  GType combo box   */
+/************************/
+
+static void   gimp_prop_gtype_combo_box_callback (GtkWidget   *widget,
+                                                  GObject     *config);
+static void   gimp_prop_gtype_combo_box_notify   (GObject     *config,
+                                                  GParamSpec  *param_spec,
+                                                  GtkWidget   *widget);
+
+/**
+ * gimp_prop_gtype_combo_box_new:
+ * @config:        Object to which property is attached.
+ * @property_name: Name of GType property controlled by combo box.
+ * @store:         #GimpGTypeStore holding list of labels, values, etc.
+ *
+ * Creates a #GimpGTypeComboBox widget to display and set the specified
+ * property.  The contents of the widget are determined by @store,
+ * which should be created using gimp_gtype_store_new().
+ *
+ * Return value: The newly created #GimpGTypeComboBox widget.
+ *
+ * Since GIMP 2.10
+ */
+GtkWidget *
+gimp_prop_gtype_combo_box_new (GObject        *config,
+                               const gchar    *property_name,
+                               GimpGTypeStore *store)
+{
+  GParamSpec *param_spec;
+  GtkWidget  *combo_box;
+  GType       value;
+
+  g_return_val_if_fail (G_IS_OBJECT (config), NULL);
+  g_return_val_if_fail (property_name != NULL, NULL);
+
+  param_spec = check_param_spec_w (config, property_name,
+                                   G_TYPE_PARAM_GTYPE, G_STRFUNC);
+  if (! param_spec)
+    return NULL;
+
+  g_object_get (config,
+                property_name, &value,
+                NULL);
+
+  combo_box = g_object_new (GIMP_TYPE_GTYPE_COMBO_BOX,
+                            "model", store,
+                            NULL);
+
+  gimp_gtype_combo_box_set_active (GIMP_GTYPE_COMBO_BOX (combo_box), value);
+
+  g_signal_connect (combo_box, "changed",
+                    G_CALLBACK (gimp_prop_gtype_combo_box_callback),
+                    config);
+
+  set_param_spec (G_OBJECT (combo_box), combo_box, param_spec);
+
+  connect_notify (config, property_name,
+                  G_CALLBACK (gimp_prop_gtype_combo_box_notify),
+                  combo_box);
+
+  return combo_box;
+}
+
+static void
+gimp_prop_gtype_combo_box_callback (GtkWidget *widget,
+                                    GObject   *config)
+{
+  GParamSpec  *param_spec;
+  GType        value;
+
+  param_spec = get_param_spec (G_OBJECT (widget));
+  if (! param_spec)
+    return;
+
+  if (gimp_gtype_combo_box_get_active (GIMP_GTYPE_COMBO_BOX (widget), &value))
+    {
+      g_object_set (config,
+                    param_spec->name, value,
+                    NULL);
+    }
+}
+
+static void
+gimp_prop_gtype_combo_box_notify (GObject    *config,
+                                  GParamSpec *param_spec,
+                                  GtkWidget  *combo_box)
+{
+  GType value;
+
+  g_object_get (config,
+                param_spec->name, &value,
+                NULL);
+
+  g_signal_handlers_block_by_func (combo_box,
+                                   gimp_prop_gtype_combo_box_callback,
+                                   config);
+
+  gimp_gtype_combo_box_set_active (GIMP_GTYPE_COMBO_BOX (combo_box), value);
+
+  g_signal_handlers_unblock_by_func (combo_box,
+                                     gimp_prop_gtype_combo_box_callback,
+                                     config);
+}
 
 /************************/
 /*  boolean combo box   */
diff --git a/libgimpwidgets/gimppropwidgets.h b/libgimpwidgets/gimppropwidgets.h
index 469e667..3cb2c92 100644
--- a/libgimpwidgets/gimppropwidgets.h
+++ b/libgimpwidgets/gimppropwidgets.h
@@ -55,6 +55,12 @@ GtkWidget     * gimp_prop_int_combo_box_new       (GObject      *config,
                                                    const gchar  *property_name,
                                                    GimpIntStore *store);
 
+/*  GParamGType  */
+
+GtkWidget     * gimp_prop_gtype_combo_box_new     (GObject        *config,
+                                                   const gchar    *property_name,
+                                                   GimpGTypeStore *store);
+
 
 /*  GParamEnum  */
 
diff --git a/libgimpwidgets/gimpwidgets.h b/libgimpwidgets/gimpwidgets.h
index 58a4a02..9eef812 100644
--- a/libgimpwidgets/gimpwidgets.h
+++ b/libgimpwidgets/gimpwidgets.h
@@ -54,6 +54,8 @@
 #include <libgimpwidgets/gimpenumwidgets.h>
 #include <libgimpwidgets/gimpfileentry.h>
 #include <libgimpwidgets/gimpframe.h>
+#include <libgimpwidgets/gimpgtypecombobox.h>
+#include <libgimpwidgets/gimpgtypestore.h>
 #include <libgimpwidgets/gimphelpui.h>
 #include <libgimpwidgets/gimphintbox.h>
 #include <libgimpwidgets/gimpicons.h>
diff --git a/libgimpwidgets/gimpwidgetstypes.h b/libgimpwidgets/gimpwidgetstypes.h
index 18c925a..d245514 100644
--- a/libgimpwidgets/gimpwidgetstypes.h
+++ b/libgimpwidgets/gimpwidgetstypes.h
@@ -57,6 +57,8 @@ typedef struct _GimpEnumComboBox              GimpEnumComboBox;
 typedef struct _GimpEnumLabel                 GimpEnumLabel;
 typedef struct _GimpFileEntry                 GimpFileEntry;
 typedef struct _GimpFrame                     GimpFrame;
+typedef struct _GimpGTypeComboBox             GimpGTypeComboBox;
+typedef struct _GimpGTypeStore                GimpGTypeStore;
 typedef struct _GimpIntComboBox               GimpIntComboBox;
 typedef struct _GimpIntStore                  GimpIntStore;
 typedef struct _GimpMemsizeEntry              GimpMemsizeEntry;


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]