/* LIBGIMP - The GIMP Library
 * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball
 *
 * gimpintcombobox.c
 * Copyright (C) 2004  Sven Neumann <sven@gimp.org>
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

#include "config.h"

#include <libintl.h>

#include <gtk/gtk.h>

#include "gimpwidgetstypes.h"

#include "gimpintcombobox.h"
#include "gimpintstore.h"


/**
 * SECTION: gimpintcombobox
 * @title: GimpIntComboBox
 * @short_description: A widget providing a popup menu of integer
 *                     values (e.g. enums).
 *
 * A widget providing a popup menu of integer values (e.g. enums).
 **/


enum
{
  PROP_0,
  PROP_ELLIPSIZE,
  PROP_LABEL,
  PROP_LAYOUT
};


typedef struct
{
  GtkCellRenderer        *pixbuf_renderer;
  GtkCellRenderer        *text_renderer;

  GtkCellRenderer        *menu_pixbuf_renderer;
  GtkCellRenderer        *menu_text_renderer;

  PangoEllipsizeMode      ellipsize;
  gchar                  *label;
  GtkCellRenderer        *label_renderer;
  GimpIntComboBoxLayout   layout;

  GimpIntSensitivityFunc  sensitivity_func;
  gpointer                sensitivity_data;
  GDestroyNotify          sensitivity_destroy;
} GimpIntComboBoxPrivate;

#define GIMP_INT_COMBO_BOX_GET_PRIVATE(obj) \
  ((GimpIntComboBoxPrivate *) ((GimpIntComboBox *) (obj))->priv)


static void  gimp_int_combo_box_finalize     (GObject         *object);
static void  gimp_int_combo_box_set_property (GObject         *object,
                                              guint            property_id,
                                              const GValue    *value,
                                              GParamSpec      *pspec);
static void  gimp_int_combo_box_get_property (GObject         *object,
                                              guint            property_id,
                                              GValue          *value,
                                              GParamSpec      *pspec);

static void  gimp_int_combo_box_create_cells (GimpIntComboBox *combo_box);
static void  gimp_int_combo_box_data_func    (GtkCellLayout   *layout,
                                              GtkCellRenderer *cell,
                                              GtkTreeModel    *model,
                                              GtkTreeIter     *iter,
                                              gpointer         data);


G_DEFINE_TYPE (GimpIntComboBox, gimp_int_combo_box, GTK_TYPE_COMBO_BOX)

#define parent_class gimp_int_combo_box_parent_class


static void
gimp_int_combo_box_class_init (GimpIntComboBoxClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);

  object_class->finalize     = gimp_int_combo_box_finalize;
  object_class->set_property = gimp_int_combo_box_set_property;
  object_class->get_property = gimp_int_combo_box_get_property;

  /**
   * GimpIntComboBox: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.4
   */
  g_object_class_install_property (object_class, PROP_ELLIPSIZE,
                                   g_param_spec_enum ("ellipsize",
                                                      "Ellipsize",
                                                      "Ellipsize mode for the used text cell renderer",
                                                      PANGO_TYPE_ELLIPSIZE_MODE,
                                                      PANGO_ELLIPSIZE_NONE,
                                                      GIMP_PARAM_READWRITE));

  /**
   * GimpIntComboBox:label:
   *
   * Sets a label on the combo-box, see gimp_int_combo_box_set_label().
   *
   * Since: 2.10
   */
  g_object_class_install_property (object_class, PROP_LABEL,
                                   g_param_spec_string ("label",
                                                        "Label",
                                                        "An optional label to be displayed",
                                                        NULL,
                                                        GIMP_PARAM_READWRITE));

  /**
   * GimpIntComboBox:layout:
   *
   * Specifies the combo box layout.
   *
   * Since: 2.10
   */
  g_object_class_install_property (object_class, PROP_LAYOUT,
                                   g_param_spec_enum ("layout",
                                                      "Layout",
                                                      "Combo box layout",
                                                      GIMP_TYPE_INT_COMBO_BOX_LAYOUT,
                                                      GIMP_INT_COMBO_BOX_LAYOUT_ABBREVIATED,
                                                      GIMP_PARAM_READWRITE));

  g_type_class_add_private (object_class, sizeof (GimpIntComboBoxPrivate));
}

static void
gimp_int_combo_box_init (GimpIntComboBox *combo_box)
{
  GimpIntComboBoxPrivate *priv;
  GtkListStore           *store;

  combo_box->priv = priv = G_TYPE_INSTANCE_GET_PRIVATE (combo_box,
                                                        GIMP_TYPE_INT_COMBO_BOX,
                                                        GimpIntComboBoxPrivate);

  store = gimp_int_store_new ();
  gtk_combo_box_set_model (GTK_COMBO_BOX (combo_box), GTK_TREE_MODEL (store));
  g_object_unref (store);

  priv->layout = GIMP_INT_COMBO_BOX_LAYOUT_ABBREVIATED;

  gimp_int_combo_box_create_cells (GIMP_INT_COMBO_BOX (combo_box));
}

static void
gimp_int_combo_box_finalize (GObject *object)
{
  GimpIntComboBoxPrivate *priv = GIMP_INT_COMBO_BOX_GET_PRIVATE (object);

  g_clear_pointer (&priv->label, g_free);

  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_int_combo_box_set_property (GObject      *object,
                                 guint         property_id,
                                 const GValue *value,
                                 GParamSpec   *pspec)
{
  GimpIntComboBoxPrivate *priv = GIMP_INT_COMBO_BOX_GET_PRIVATE (object);

  switch (property_id)
    {
    case PROP_ELLIPSIZE:
      priv->ellipsize = g_value_get_enum (value);
      if (priv->text_renderer)
        {
          g_object_set_property (G_OBJECT (priv->text_renderer),
                                 pspec->name, value);
        }
      break;
    case PROP_LABEL:
      gimp_int_combo_box_set_label (GIMP_INT_COMBO_BOX (object),
                                    g_value_get_string (value));
      break;
    case PROP_LAYOUT:
      gimp_int_combo_box_set_layout (GIMP_INT_COMBO_BOX (object),
                                     g_value_get_enum (value));
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
    }
}

static void
gimp_int_combo_box_get_property (GObject    *object,
                                 guint       property_id,
                                 GValue     *value,
                                 GParamSpec *pspec)
{
  GimpIntComboBoxPrivate *priv = GIMP_INT_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;
    case PROP_LAYOUT:
      g_value_set_enum (value, priv->layout);
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
    }
}


/**
 * gimp_int_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 integer 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 #GimpIntComboBox, it's best to use
 * g_object_new (GIMP_TYPE_INT_COMBO_BOX, NULL).
 *
 * Return value: a new #GimpIntComboBox.
 *
 * Since: 2.2
 **/
GtkWidget *
gimp_int_combo_box_new (const gchar *first_label,
                        gint         first_value,
                        ...)
{
  GtkWidget *combo_box;
  va_list    args;

  va_start (args, first_value);

  combo_box = gimp_int_combo_box_new_valist (first_label, first_value, args);

  va_end (args);

  return combo_box;
}

/**
 * gimp_int_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_int_combo_box_new() that takes a va_list of
 * label/value pairs. Probably only useful for language bindings.
 *
 * Return value: a new #GimpIntComboBox.
 *
 * Since: 2.2
 **/
GtkWidget *
gimp_int_combo_box_new_valist (const gchar *first_label,
                               gint         first_value,
                               va_list      values)
{
  GtkWidget    *combo_box;
  GtkListStore *store;
  const gchar  *label;
  gint          value;

  combo_box = g_object_new (GIMP_TYPE_INT_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, gint))
    {
      GtkTreeIter  iter = { 0, };

      gtk_list_store_append (store, &iter);
      gtk_list_store_set (store, &iter,
                          GIMP_INT_STORE_VALUE, value,
                          GIMP_INT_STORE_LABEL, label,
                          -1);
    }

  return combo_box;
}

/**
 * gimp_int_combo_box_new_array:
 * @n_values: the number of values
 * @labels:   an array of labels (array length must be @n_values)
 *
 * A variant of gimp_int_combo_box_new() that takes an array of labels.
 * The array indices are used as values.
 *
 * Return value: a new #GimpIntComboBox.
 *
 * Since: 2.2
 **/
GtkWidget *
gimp_int_combo_box_new_array (gint         n_values,
                              const gchar *labels[])
{
  GtkWidget    *combo_box;
  GtkListStore *store;
  gint          i;

  g_return_val_if_fail (n_values >= 0, NULL);
  g_return_val_if_fail (labels != NULL || n_values == 0, NULL);

  combo_box = g_object_new (GIMP_TYPE_INT_COMBO_BOX, NULL);

  store = GTK_LIST_STORE (gtk_combo_box_get_model (GTK_COMBO_BOX (combo_box)));

  for (i = 0; i < n_values; i++)
    {
      GtkTreeIter  iter;

      if (labels[i])
        {
          gtk_list_store_append (store, &iter);
          gtk_list_store_set (store, &iter,
                              GIMP_INT_STORE_VALUE, i,
                              GIMP_INT_STORE_LABEL, gettext (labels[i]),
                              -1);
        }
    }

  return combo_box;
}

/**
 * gimp_int_combo_box_prepend:
 * @combo_box: a #GimpIntComboBox
 * @...:       pairs of column number and value, terminated with -1
 *
 * This function provides a convenient way to prepend items to a
 * #GimpIntComboBox. 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 #GimpIntStoreColumns.
 *
 * Since: 2.2
 **/
void
gimp_int_combo_box_prepend (GimpIntComboBox *combo_box,
                            ...)
{
  GtkListStore *store;
  GtkTreeIter   iter;
  va_list       args;

  g_return_if_fail (GIMP_IS_INT_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_int_combo_box_append:
 * @combo_box: a #GimpIntComboBox
 * @...:       pairs of column number and value, terminated with -1
 *
 * This function provides a convenient way to append items to a
 * #GimpIntComboBox. 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 #GimpIntStoreColumns.
 *
 * Since: 2.2
 **/
void
gimp_int_combo_box_append (GimpIntComboBox *combo_box,
                           ...)
{
  GtkListStore *store;
  GtkTreeIter   iter;
  va_list       args;

  g_return_if_fail (GIMP_IS_INT_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_int_combo_box_set_active:
 * @combo_box: a #GimpIntComboBox
 * @value:     an integer 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.2
 **/
gboolean
gimp_int_combo_box_set_active (GimpIntComboBox *combo_box,
                               gint             value)
{
  GtkTreeModel *model;
  GtkTreeIter   iter;

  g_return_val_if_fail (GIMP_IS_INT_COMBO_BOX (combo_box), FALSE);

  model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo_box));

  if (gimp_int_store_lookup_by_value (model, value, &iter))
    {
      gtk_combo_box_set_active_iter (GTK_COMBO_BOX (combo_box), &iter);
      return TRUE;
    }

  return FALSE;
}

/**
 * gimp_int_combo_box_get_active:
 * @combo_box: a #GimpIntComboBox
 * @value:     return location for the integer 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.2
 **/
gboolean
gimp_int_combo_box_get_active (GimpIntComboBox *combo_box,
                               gint            *value)
{
  GtkTreeIter  iter;

  g_return_val_if_fail (GIMP_IS_INT_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_INT_STORE_VALUE, value,
                          -1);
      return TRUE;
    }

  return FALSE;
}

/**
 * gimp_int_combo_box_set_active_by_user_data:
 * @combo_box: a #GimpIntComboBox
 * @user_data: an integer value
 *
 * Looks up the item that has the given @user_data and makes it the
 * selected item in the @combo_box.
 *
 * Return value: %TRUE on success or %FALSE if there was no item for
 *               this user-data.
 *
 * Since: 2.10
 **/
gboolean
gimp_int_combo_box_set_active_by_user_data (GimpIntComboBox *combo_box,
                                            gpointer         user_data)
{
  GtkTreeModel *model;
  GtkTreeIter   iter;

  g_return_val_if_fail (GIMP_IS_INT_COMBO_BOX (combo_box), FALSE);

  model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo_box));

  if (gimp_int_store_lookup_by_user_data (model, user_data, &iter))
    {
      gtk_combo_box_set_active_iter (GTK_COMBO_BOX (combo_box), &iter);
      return TRUE;
    }

  return FALSE;
}

/**
 * gimp_int_combo_box_get_active_user_data:
 * @combo_box: a #GimpIntComboBox
 * @user_data: return location for the gpointer value
 *
 * Retrieves the user-data of the selected (active) item in the @combo_box.
 *
 * Return value: %TRUE if @user_data has been set or %FALSE if no item was
 *               active.
 *
 * Since: 2.10
 **/
gboolean
gimp_int_combo_box_get_active_user_data (GimpIntComboBox *combo_box,
                                         gpointer        *user_data)
{
  GtkTreeIter  iter;

  g_return_val_if_fail (GIMP_IS_INT_COMBO_BOX (combo_box), FALSE);
  g_return_val_if_fail (user_data != 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_INT_STORE_USER_DATA, user_data,
                          -1);
      return TRUE;
    }

  return FALSE;
}

/**
 * gimp_int_combo_box_connect:
 * @combo_box: a #GimpIntComboBox
 * @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
 * #GimpIntComboBox 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.2
 **/
gulong
gimp_int_combo_box_connect (GimpIntComboBox *combo_box,
                            gint             value,
                            GCallback        callback,
                            gpointer         data)
{
  gulong handler = 0;

  g_return_val_if_fail (GIMP_IS_INT_COMBO_BOX (combo_box), 0);

  if (callback)
    handler = g_signal_connect (combo_box, "changed", callback, data);

  if (! gimp_int_combo_box_set_active (combo_box, value))
    g_signal_emit_by_name (combo_box, "changed", NULL);

  return handler;
}

/**
 * gimp_int_combo_box_set_label:
 * @combo_box: a #GimpIntComboBox
 * @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_int_combo_box_set_label (GimpIntComboBox *combo_box,
                              const gchar     *label)
{
  GimpIntComboBoxPrivate *priv;

  g_return_if_fail (GIMP_IS_INT_COMBO_BOX (combo_box));

  priv = GIMP_INT_COMBO_BOX_GET_PRIVATE (combo_box);

  if (label == priv->label)
    return;

  g_free (priv->label);

  priv->label = g_strdup (label);

  gimp_int_combo_box_create_cells (combo_box);

  g_object_notify (G_OBJECT (combo_box), "label");
}

/**
 * gimp_int_combo_box_get_label:
 * @combo_box: a #GimpIntComboBox
 *
 * Returns the label previously set with gimp_int_combo_box_set_label(),
 * or %NULL,
 *
 * Return value: the @combo_box' label.
 *
 * Since: 2.10
 **/
const gchar *
gimp_int_combo_box_get_label (GimpIntComboBox *combo_box)
{
  g_return_val_if_fail (GIMP_IS_INT_COMBO_BOX (combo_box), NULL);

  return GIMP_INT_COMBO_BOX_GET_PRIVATE (combo_box)->label;
}

/**
 * gimp_int_combo_box_set_layout:
 * @combo_box: a #GimpIntComboBox
 * @layout:    the combo box layout
 *
 * Sets the layout of @combo_box to @layout.
 *
 * Since: 2.10
 **/
void
gimp_int_combo_box_set_layout (GimpIntComboBox       *combo_box,
                               GimpIntComboBoxLayout  layout)
{
  GimpIntComboBoxPrivate *priv;

  g_return_if_fail (GIMP_IS_INT_COMBO_BOX (combo_box));

  priv = GIMP_INT_COMBO_BOX_GET_PRIVATE (combo_box);

  if (layout == priv->layout)
    return;

  priv->layout = layout;

  gimp_int_combo_box_create_cells (combo_box);

  g_object_notify (G_OBJECT (combo_box), "layout");
}

/**
 * gimp_int_combo_box_get_layout:
 * @combo_box: a #GimpIntComboBox
 *
 * Returns the layout of @combo_box
 *
 * Return value: the @combo_box's layout.
 *
 * Since: 2.10
 **/
GimpIntComboBoxLayout
gimp_int_combo_box_get_layout (GimpIntComboBox *combo_box)
{
  g_return_val_if_fail (GIMP_IS_INT_COMBO_BOX (combo_box),
                        GIMP_INT_COMBO_BOX_LAYOUT_ABBREVIATED);

  return GIMP_INT_COMBO_BOX_GET_PRIVATE (combo_box)->layout;
}

/**
 * gimp_int_combo_box_set_sensitivity:
 * @combo_box: a #GimpIntComboBox
 * @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.4
 **/
void
gimp_int_combo_box_set_sensitivity (GimpIntComboBox        *combo_box,
                                    GimpIntSensitivityFunc  func,
                                    gpointer                data,
                                    GDestroyNotify          destroy)
{
  GimpIntComboBoxPrivate *priv;

  g_return_if_fail (GIMP_IS_INT_COMBO_BOX (combo_box));

  priv = GIMP_INT_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;

  gimp_int_combo_box_create_cells (combo_box);
}


/*  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_int_combo_box_create_cells (GimpIntComboBox *combo_box)
{
  GimpIntComboBoxPrivate *priv = GIMP_INT_COMBO_BOX_GET_PRIVATE (combo_box);
  GtkCellLayout          *layout;

  /*  menu layout  */

  layout = GTK_CELL_LAYOUT (combo_box);

  gtk_cell_layout_clear (layout);

  priv->menu_pixbuf_renderer = gtk_cell_renderer_pixbuf_new ();
  g_object_set (priv->menu_pixbuf_renderer,
                "xpad", 2,
                NULL);

  priv->menu_text_renderer = gtk_cell_renderer_text_new ();

  gtk_cell_layout_pack_start (layout,
                              priv->menu_pixbuf_renderer, FALSE);
  gtk_cell_layout_pack_start (layout,
                              priv->menu_text_renderer, TRUE);

  gtk_cell_layout_set_attributes (layout,
                                  priv->menu_pixbuf_renderer,
                                  "icon-name", GIMP_INT_STORE_ICON_NAME,
                                  "pixbuf",    GIMP_INT_STORE_PIXBUF,
                                  NULL);
  gtk_cell_layout_set_attributes (layout,
                                  priv->menu_text_renderer,
                                  "text", GIMP_INT_STORE_LABEL,
                                  NULL);

  if (priv->sensitivity_func)
    {
      gtk_cell_layout_set_cell_data_func (layout,
                                          priv->menu_pixbuf_renderer,
                                          gimp_int_combo_box_data_func,
                                          priv, NULL);

      gtk_cell_layout_set_cell_data_func (layout,
                                          priv->menu_text_renderer,
                                          gimp_int_combo_box_data_func,
                                          priv, NULL);
    }

  /*  combo box layout  */

  layout = GTK_CELL_LAYOUT (gtk_bin_get_child (GTK_BIN (combo_box)));

  gtk_cell_layout_clear (layout);

  if (priv->layout != GIMP_INT_COMBO_BOX_LAYOUT_ICON_ONLY)
    {
      priv->text_renderer = gtk_cell_renderer_text_new ();
      g_object_set (priv->text_renderer,
                    "ellipsize", priv->ellipsize,
                    NULL);
    }
  else
    {
      priv->text_renderer = NULL;
    }

  priv->pixbuf_renderer = gtk_cell_renderer_pixbuf_new ();

  if (priv->text_renderer)
    {
      g_object_set (priv->pixbuf_renderer,
                    "xpad", 2,
                    NULL);
    }

  if (priv->label)
    {
      priv->label_renderer = gtk_cell_renderer_text_new ();
      g_object_set (priv->label_renderer,
                    "text", priv->label,
                    NULL);

      gtk_cell_layout_pack_start (layout,
                                  priv->label_renderer, FALSE);

      gtk_cell_layout_pack_end (layout,
                                priv->pixbuf_renderer, FALSE);

      if (priv->text_renderer)
        {
          gtk_cell_layout_pack_end (layout,
                                    priv->text_renderer, TRUE);

          g_object_set (priv->text_renderer,
                        "xalign", 1.0,
                        NULL);
        }
    }
  else
    {
      gtk_cell_layout_pack_start (layout,
                                  priv->pixbuf_renderer, FALSE);

      if (priv->text_renderer)
        {
          gtk_cell_layout_pack_start (layout,
                                      priv->text_renderer, TRUE);
        }
    }

  gtk_cell_layout_set_attributes (layout,
                                  priv->pixbuf_renderer,
                                  "icon-name", GIMP_INT_STORE_ICON_NAME,
                                  NULL);

  if (priv->text_renderer)
    {
      gtk_cell_layout_set_attributes (layout,
                                      priv->text_renderer,
                                      "text", GIMP_INT_STORE_LABEL,
                                      NULL);
    }

  if (priv->layout == GIMP_INT_COMBO_BOX_LAYOUT_ABBREVIATED ||
      priv->sensitivity_func)
    {
      gtk_cell_layout_set_cell_data_func (layout,
                                          priv->pixbuf_renderer,
                                          gimp_int_combo_box_data_func,
                                          priv, NULL);

      if (priv->text_renderer)
        {
          gtk_cell_layout_set_cell_data_func (layout,
                                              priv->text_renderer,
                                              gimp_int_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_int_combo_box_data_func (GtkCellLayout   *layout,
                              GtkCellRenderer *cell,
                              GtkTreeModel    *model,
                              GtkTreeIter     *iter,
                              gpointer         data)
{
  GimpIntComboBoxPrivate *priv = data;

  if (priv->layout == GIMP_INT_COMBO_BOX_LAYOUT_ABBREVIATED &&
      cell == priv->text_renderer)
    {
      gchar *abbrev;

      gtk_tree_model_get (model, iter,
                          GIMP_INT_STORE_ABBREV, &abbrev,
                          -1);

      if (abbrev)
        {
          g_object_set (cell,
                        "text", abbrev,
                        NULL);

          g_free (abbrev);
        }
    }

  if (priv->sensitivity_func)
    {
      gint      value;
      gboolean  sensitive;

      gtk_tree_model_get (model, iter,
                          GIMP_INT_STORE_VALUE, &value,
                          -1);

      sensitive = priv->sensitivity_func (value, priv->sensitivity_data);

      g_object_set (cell,
                    "sensitive", sensitive,
                    NULL);
    }
}
