view src/gtkcombobox.c @ 12095:16be443fe00d

[gaim-migrate @ 14392] Some updates. The Jeopardy! college tournament last week and this week is at the RBC Center in Raleigh, NC. It's the basketball area where the NC State Wolfpack play and the NHL Carolina Hurricanes. I was there for the taping of the last two episodes. They'll be aired this Thursday and Friday. You should watch, maybe you'll see me! Of course, you don't have any idea what I look like. committer: Tailor Script <tailor@pidgin.im>
author Mark Doliner <mark@kingant.net>
date Mon, 14 Nov 2005 23:53:57 +0000
parents 8cb75ba77f9d
children 1ee0a397157f
line wrap: on
line source

/* gtkcombobox.c
 * Copyright (C) 2002, 2003  Kristian Rietveld <kris@gtk.org>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

/*
#include <config.h>
*/
#include <gtk/gtkversion.h>
#if !GTK_CHECK_VERSION(2,4,0)
#include "gtkcombobox.h"

#include <gtk/gtkarrow.h>
#include <gtk/gtkbindings.h>
#include "gtkcelllayout.h"
#include <gtk/gtkcellrenderertext.h>
#include "gtkcellview.h"
#include "gtkcellviewmenuitem.h"
#include <gtk/gtkeventbox.h>
#include <gtk/gtkframe.h>
#include <gtk/gtkhbox.h>
#include <gtk/gtkliststore.h>
#include <gtk/gtkmain.h>
#include <gtk/gtkmenu.h>
#include <gtk/gtktogglebutton.h>
#include <gtk/gtktreeselection.h>
/*
#include <gtk/gtktreeprivate.h>
*/
#include <gtk/gtkvseparator.h>
#include <gtk/gtkwindow.h>
#include <gtk/gtkversion.h>

#include <gdk/gdkkeysyms.h>

#include <gobject/gvaluecollector.h>

#include <string.h>
#include <stdarg.h>

#ifdef ENABLE_NLS
#  include <libintl.h>
#  define _(x) gettext(x)
#  ifdef gettext_noop
#    define N_(String) gettext_noop (String)
#  else
#    define N_(String) (String)
#  endif
#else
#  define N_(String) (String)
#  define _(x) (x)
#endif

/* WELCOME, to THE house of evil code */

typedef struct _ComboCellInfo ComboCellInfo;
struct _ComboCellInfo
{
  GtkCellRenderer *cell;
  GSList *attributes;

  GtkCellLayoutDataFunc func;
  gpointer func_data;
  GDestroyNotify destroy;

  guint expand : 1;
  guint pack : 1;
};

struct _GtkComboBoxPrivate
{
  GtkTreeModel *model;

  gint col_column;
  gint row_column;

  gint wrap_width;

  gint active_item;

  GtkWidget *tree_view;
  GtkTreeViewColumn *column;

  GtkWidget *cell_view;
  GtkWidget *cell_view_frame;

  GtkWidget *button;
  GtkWidget *box;
  GtkWidget *arrow;
  GtkWidget *separator;

  GtkWidget *popup_widget;
  GtkWidget *popup_window;
  GtkWidget *popup_frame;

  guint inserted_id;
  guint deleted_id;
  guint reordered_id;
  guint changed_id;

  gint width;
  GSList *cells;

  guint popup_in_progress : 1;
  guint destroying : 1;
};

/* While debugging this evil code, I have learned that
 * there are actually 4 modes to this widget, which can
 * be characterized as follows
 * 
 * 1) menu mode, no child added
 *
 * tree_view -> NULL
 * cell_view -> GtkCellView, regular child
 * cell_view_frame -> NULL
 * button -> GtkToggleButton set_parent to combo
 * box -> child of button
 * arrow -> child of box
 * separator -> child of box
 * popup_widget -> GtkMenu
 * popup_window -> NULL
 * popup_frame -> NULL
 *
 * 2) menu mode, child added
 * 
 * tree_view -> NULL
 * cell_view -> NULL 
 * cell_view_frame -> NULL
 * button -> GtkToggleButton set_parent to combo
 * box -> NULL
 * arrow -> GtkArrow, child of button
 * separator -> NULL
 * popup_widget -> GtkMenu
 * popup_window -> NULL
 * popup_frame -> NULL
 *
 * 3) list mode, no child added
 * 
 * tree_view -> GtkTreeView, child of popup_frame
 * cell_view -> GtkCellView, regular child
 * cell_view_frame -> GtkFrame, set parent to combo
 * button -> GtkToggleButton, set_parent to combo
 * box -> NULL
 * arrow -> GtkArrow, child of button
 * separator -> NULL
 * popup_widget -> tree_view
 * popup_window -> GtkWindow
 * popup_frame -> GtkFrame, child of popup_window
 *
 * 4) list mode, child added
 *
 * tree_view -> GtkTreeView, child of popup_frame
 * cell_view -> NULL
 * cell_view_frame -> NULL
 * button -> GtkToggleButton, set_parent to combo
 * box -> NULL
 * arrow -> GtkArrow, child of button
 * separator -> NULL
 * popup_widget -> tree_view
 * popup_window -> GtkWindow
 * popup_frame -> GtkFrame, child of popup_window
 * 
 */

enum {
  CHANGED,
  LAST_SIGNAL
};

enum {
  PROP_0,
  PROP_MODEL,
  PROP_WRAP_WIDTH,
  PROP_ROW_SPAN_COLUMN,
  PROP_COLUMN_SPAN_COLUMN,
  PROP_ACTIVE
};

static GtkBinClass *parent_class = NULL;
static guint combo_box_signals[LAST_SIGNAL] = {0,};

#define BONUS_PADDING 4


/* common */
static void     gtk_combo_box_class_init           (GtkComboBoxClass *klass);
static void     gtk_combo_box_cell_layout_init     (GtkCellLayoutIface *iface);
static void     gtk_combo_box_init                 (GtkComboBox      *combo_box);
static void     gtk_combo_box_finalize             (GObject          *object);
static void     gtk_combo_box_destroy              (GtkObject        *object);

static void     gtk_combo_box_set_property         (GObject         *object,
                                                    guint            prop_id,
                                                    const GValue    *value,
                                                    GParamSpec      *spec);
static void     gtk_combo_box_get_property         (GObject         *object,
                                                    guint            prop_id,
                                                    GValue          *value,
                                                    GParamSpec      *spec);

static void     gtk_combo_box_state_changed        (GtkWidget        *widget,
			                            GtkStateType      previous);
static void     gtk_combo_box_style_set            (GtkWidget       *widget,
                                                    GtkStyle        *previous);
static void     gtk_combo_box_button_toggled       (GtkWidget       *widget,
                                                    gpointer         data);
static void     gtk_combo_box_add                  (GtkContainer    *container,
                                                    GtkWidget       *widget);
static void     gtk_combo_box_remove               (GtkContainer    *container,
                                                    GtkWidget       *widget);

static ComboCellInfo *gtk_combo_box_get_cell_info  (GtkComboBox      *combo_box,
                                                    GtkCellRenderer  *cell);

static void     gtk_combo_box_menu_show            (GtkWidget        *menu,
                                                    gpointer          user_data);
static void     gtk_combo_box_menu_hide            (GtkWidget        *menu,
                                                    gpointer          user_data);

static void     gtk_combo_box_set_popup_widget     (GtkComboBox      *combo_box,
                                                    GtkWidget        *popup);
#if GTK_CHECK_VERSION(2,2,0)
static void     gtk_combo_box_menu_position_below  (GtkMenu          *menu,
                                                    gint             *x,
                                                    gint             *y,
                                                    gint             *push_in,
                                                    gpointer          user_data);
static void     gtk_combo_box_menu_position_over   (GtkMenu          *menu,
                                                    gint             *x,
                                                    gint             *y,
                                                    gint             *push_in,
                                                    gpointer          user_data);
static void     gtk_combo_box_menu_position        (GtkMenu          *menu,
                                                    gint             *x,
                                                    gint             *y,
                                                    gint             *push_in,
                                                    gpointer          user_data);
#endif

static gint     gtk_combo_box_calc_requested_width (GtkComboBox      *combo_box,
                                                    GtkTreePath      *path);
static void     gtk_combo_box_remeasure            (GtkComboBox      *combo_box);

static void     gtk_combo_box_unset_model          (GtkComboBox      *combo_box);

static void     gtk_combo_box_size_request         (GtkWidget        *widget,
                                                    GtkRequisition   *requisition);
static void     gtk_combo_box_size_allocate        (GtkWidget        *widget,
                                                    GtkAllocation    *allocation);
static void     gtk_combo_box_forall               (GtkContainer     *container,
                                                    gboolean          include_internals,
                                                    GtkCallback       callback,
                                                    gpointer          callback_data);
static gboolean gtk_combo_box_expose_event         (GtkWidget        *widget,
                                                    GdkEventExpose   *event);
static gboolean gtk_combo_box_scroll_event         (GtkWidget        *widget,
                                                    GdkEventScroll   *event);
static void     gtk_combo_box_set_active_internal  (GtkComboBox      *combo_box,
						    gint              index);
static gboolean gtk_combo_box_key_press            (GtkWidget        *widget,
						    GdkEventKey      *event,
						    gpointer          data);

/* listening to the model */
static void     gtk_combo_box_model_row_inserted   (GtkTreeModel     *model,
						    GtkTreePath      *path,
						    GtkTreeIter      *iter,
						    gpointer          user_data);
static void     gtk_combo_box_model_row_deleted    (GtkTreeModel     *model,
						    GtkTreePath      *path,
						    gpointer          user_data);
static void     gtk_combo_box_model_rows_reordered (GtkTreeModel     *model,
						    GtkTreePath      *path,
						    GtkTreeIter      *iter,
						    gint             *new_order,
						    gpointer          user_data);
static void     gtk_combo_box_model_row_changed    (GtkTreeModel     *model,
						    GtkTreePath      *path,
						    GtkTreeIter      *iter,
						    gpointer          data);

/* list */
#if GTK_CHECK_VERSION(2,2,0)
static void     gtk_combo_box_list_position        (GtkComboBox      *combo_box, 
						    gint             *x, 
						    gint             *y, 
						    gint             *width,
						    gint             *height);
#endif
static void     gtk_combo_box_list_setup           (GtkComboBox      *combo_box);
static void     gtk_combo_box_list_destroy         (GtkComboBox      *combo_box);

static void     gtk_combo_box_list_remove_grabs    (GtkComboBox      *combo_box);

static gboolean gtk_combo_box_list_button_released (GtkWidget        *widget,
                                                    GdkEventButton   *event,
                                                    gpointer          data);
static gboolean gtk_combo_box_list_key_press       (GtkWidget        *widget,
                                                    GdkEventKey      *event,
                                                    gpointer          data);
static gboolean gtk_combo_box_list_button_pressed  (GtkWidget        *widget,
                                                    GdkEventButton   *event,
                                                    gpointer          data);

static void     gtk_combo_box_list_row_changed     (GtkTreeModel     *model,
                                                    GtkTreePath      *path,
                                                    GtkTreeIter      *iter,
                                                    gpointer          data);

/* menu */
static void     gtk_combo_box_menu_setup           (GtkComboBox      *combo_box,
                                                    gboolean          add_children);
static void     gtk_combo_box_menu_fill            (GtkComboBox      *combo_box);
static void     gtk_combo_box_menu_destroy         (GtkComboBox      *combo_box);

static void     gtk_combo_box_item_get_size        (GtkComboBox      *combo_box,
                                                    gint              index,
                                                    gint             *cols,
                                                    gint             *rows);
static void     gtk_combo_box_relayout_item        (GtkComboBox      *combo_box,
                                                    gint              index);
static void     gtk_combo_box_relayout             (GtkComboBox      *combo_box);

static gboolean gtk_combo_box_menu_button_press    (GtkWidget        *widget,
                                                    GdkEventButton   *event,
                                                    gpointer          user_data);
static void     gtk_combo_box_menu_item_activate   (GtkWidget        *item,
                                                    gpointer          user_data);
static void     gtk_combo_box_menu_row_inserted    (GtkTreeModel     *model,
                                                    GtkTreePath      *path,
                                                    GtkTreeIter      *iter,
                                                    gpointer          user_data);
static void     gtk_combo_box_menu_row_deleted     (GtkTreeModel     *model,
                                                    GtkTreePath      *path,
                                                    gpointer          user_data);
static void     gtk_combo_box_menu_rows_reordered  (GtkTreeModel     *model,
						    GtkTreePath      *path,
						    GtkTreeIter      *iter,
						    gint             *new_order,
						    gpointer          user_data);
static void     gtk_combo_box_menu_row_changed     (GtkTreeModel     *model,
                                                    GtkTreePath      *path,
                                                    GtkTreeIter      *iter,
                                                    gpointer          data);
static gboolean gtk_combo_box_menu_key_press       (GtkWidget        *widget,
						    GdkEventKey      *event,
						    gpointer          data);

/* cell layout */
static void     gtk_combo_box_cell_layout_pack_start         (GtkCellLayout         *layout,
                                                              GtkCellRenderer       *cell,
                                                              gboolean               expand);
static void     gtk_combo_box_cell_layout_pack_end           (GtkCellLayout         *layout,
                                                              GtkCellRenderer       *cell,
                                                              gboolean               expand);
static void     gtk_combo_box_cell_layout_clear              (GtkCellLayout         *layout);
static void     gtk_combo_box_cell_layout_add_attribute      (GtkCellLayout         *layout,
                                                              GtkCellRenderer       *cell,
                                                              const gchar           *attribute,
                                                              gint                   column);
static void     gtk_combo_box_cell_layout_set_cell_data_func (GtkCellLayout         *layout,
                                                              GtkCellRenderer       *cell,
                                                              GtkCellLayoutDataFunc  func,
                                                              gpointer               func_data,
                                                              GDestroyNotify         destroy);
static void     gtk_combo_box_cell_layout_clear_attributes   (GtkCellLayout         *layout,
                                                              GtkCellRenderer       *cell);
static void     gtk_combo_box_cell_layout_reorder            (GtkCellLayout         *layout,
                                                              GtkCellRenderer       *cell,
                                                              gint                   position);
static gboolean gtk_combo_box_mnemonic_activate              (GtkWidget    *widget,
							      gboolean      group_cycling);

static void     cell_view_sync_cells (GtkComboBox *combo_box,
                                      GtkCellView *cell_view);

#if !GTK_CHECK_VERSION(2,4,0)
static void     gtk_menu_attach (GtkMenu   *menu,
                                 GtkWidget *child,
                                 guint      left_attach,
                                 guint      right_attach,
                                 guint      top_attach,
                                 guint      bottom_attach);
#endif

GType
gtk_combo_box_get_type (void)
{
  static GType combo_box_type = 0;

  if (!combo_box_type)
    {
      static const GTypeInfo combo_box_info =
        {
          sizeof (GtkComboBoxClass),
          NULL, /* base_init */
          NULL, /* base_finalize */
          (GClassInitFunc) gtk_combo_box_class_init,
          NULL, /* class_finalize */
          NULL, /* class_data */
          sizeof (GtkComboBox),
          0,
          (GInstanceInitFunc) gtk_combo_box_init
        };

      static const GInterfaceInfo cell_layout_info =
        {
          (GInterfaceInitFunc) gtk_combo_box_cell_layout_init,
          NULL,
          NULL
        };

      combo_box_type = g_type_register_static (GTK_TYPE_BIN,
                                               "GaimGtkComboBox",
                                               &combo_box_info,
                                               0);

      g_type_add_interface_static (combo_box_type,
                                   GTK_TYPE_CELL_LAYOUT,
                                   &cell_layout_info);
    }

  return combo_box_type;
}

/* common */
static void
gtk_combo_box_class_init (GtkComboBoxClass *klass)
{
  GObjectClass *object_class;
  GtkBindingSet *binding_set;
  GtkObjectClass *gtk_object_class;
  GtkContainerClass *container_class;
  GtkWidgetClass *widget_class;

  binding_set = gtk_binding_set_by_class (klass);

  container_class = (GtkContainerClass *)klass;
  container_class->forall = gtk_combo_box_forall;
  container_class->add = gtk_combo_box_add;
  container_class->remove = gtk_combo_box_remove;

  widget_class = (GtkWidgetClass *)klass;
  widget_class->size_allocate = gtk_combo_box_size_allocate;
  widget_class->size_request = gtk_combo_box_size_request;
  widget_class->expose_event = gtk_combo_box_expose_event;
  widget_class->scroll_event = gtk_combo_box_scroll_event;
  widget_class->mnemonic_activate = gtk_combo_box_mnemonic_activate;
  widget_class->style_set = gtk_combo_box_style_set;
  widget_class->state_changed = gtk_combo_box_state_changed;

  gtk_object_class = (GtkObjectClass *)klass;
  gtk_object_class->destroy = gtk_combo_box_destroy;

  object_class = (GObjectClass *)klass;
  object_class->finalize = gtk_combo_box_finalize;
  object_class->set_property = gtk_combo_box_set_property;
  object_class->get_property = gtk_combo_box_get_property;

  parent_class = g_type_class_peek_parent (klass);

  /* signals */
  combo_box_signals[CHANGED] =
    g_signal_new ("changed",
                  G_OBJECT_CLASS_TYPE (klass),
                  G_SIGNAL_RUN_LAST,
                  G_STRUCT_OFFSET (GtkComboBoxClass, changed),
                  NULL, NULL,
                  g_cclosure_marshal_VOID__VOID,
                  G_TYPE_NONE, 0);

  /* properties */
  g_object_class_install_property (object_class,
                                   PROP_MODEL,
                                   g_param_spec_object ("model",
                                                        _("ComboBox model"),
                                                        _("The model for the combo box"),
                                                        GTK_TYPE_TREE_MODEL,
                                                        G_PARAM_READWRITE));

  g_object_class_install_property (object_class,
                                   PROP_WRAP_WIDTH,
                                   g_param_spec_int ("wrap_width",
                                                     _("Wrap width"),
                                                     _("Wrap width for layouting the items in a grid"),
                                                     0,
                                                     G_MAXINT,
                                                     0,
                                                     G_PARAM_READWRITE));

  g_object_class_install_property (object_class,
                                   PROP_ROW_SPAN_COLUMN,
                                   g_param_spec_int ("row_span_column",
                                                     _("Row span column"),
                                                     _("TreeModel column containing the row span values"),
                                                     0,
                                                     G_MAXINT,
                                                     0,
                                                     G_PARAM_READWRITE));

  g_object_class_install_property (object_class,
                                   PROP_COLUMN_SPAN_COLUMN,
                                   g_param_spec_int ("column_span_column",
                                                     _("Column span column"),

                                                     _("TreeModel column containing the column span values"),
                                                     0,
                                                     G_MAXINT,
                                                     0,
                                                     G_PARAM_READWRITE));

  g_object_class_install_property (object_class,
                                   PROP_ACTIVE,
                                   g_param_spec_int ("active",
                                                     _("Active item"),
                                                     _("The item which is currently active"),
                                                     -1,
                                                     G_MAXINT,
                                                     -1,
                                                     G_PARAM_READWRITE));

  gtk_widget_class_install_style_property (widget_class,
                                           g_param_spec_boolean ("appears-as-list",
                                                                 _("Appears as list"),
                                                                 _("Whether combobox dropdowns should look like lists rather than menus"),
                                                                 FALSE,
                                                                 G_PARAM_READABLE));
}

static void
gtk_combo_box_cell_layout_init (GtkCellLayoutIface *iface)
{
  iface->pack_start = gtk_combo_box_cell_layout_pack_start;
  iface->pack_end = gtk_combo_box_cell_layout_pack_end;
  iface->clear = gtk_combo_box_cell_layout_clear;
  iface->add_attribute = gtk_combo_box_cell_layout_add_attribute;
  iface->set_cell_data_func = gtk_combo_box_cell_layout_set_cell_data_func;
  iface->clear_attributes = gtk_combo_box_cell_layout_clear_attributes;
  iface->reorder = gtk_combo_box_cell_layout_reorder;
}

static void
gtk_combo_box_init (GtkComboBox *combo_box)
{
  combo_box->priv = g_new0(GtkComboBoxPrivate,1);

  combo_box->priv->cell_view = gtk_cell_view_new ();
  gtk_widget_set_parent (combo_box->priv->cell_view, GTK_WIDGET (combo_box));
  GTK_BIN (combo_box)->child = combo_box->priv->cell_view;
  gtk_widget_show (combo_box->priv->cell_view);

  combo_box->priv->width = 0;
  combo_box->priv->wrap_width = 0;

  combo_box->priv->active_item = -1;
  combo_box->priv->col_column = -1;
  combo_box->priv->row_column = -1;
}

static void
gtk_combo_box_set_property (GObject      *object,
                            guint         prop_id,
                            const GValue *value,
                            GParamSpec   *pspec)
{
  GtkComboBox *combo_box = GTK_COMBO_BOX (object);

  switch (prop_id)
    {
      case PROP_MODEL:
        gtk_combo_box_set_model (combo_box, g_value_get_object (value));
        break;

      case PROP_WRAP_WIDTH:
        gtk_combo_box_set_wrap_width (combo_box, g_value_get_int (value));
        break;

      case PROP_ROW_SPAN_COLUMN:
        gtk_combo_box_set_row_span_column (combo_box, g_value_get_int (value));
        break;

      case PROP_COLUMN_SPAN_COLUMN:
        gtk_combo_box_set_column_span_column (combo_box, g_value_get_int (value));
        break;

      case PROP_ACTIVE:
        gtk_combo_box_set_active (combo_box, g_value_get_int (value));
        break;

      default:
        break;
    }
}

static void
gtk_combo_box_get_property (GObject    *object,
                            guint       prop_id,
                            GValue     *value,
                            GParamSpec *pspec)
{
  GtkComboBox *combo_box = GTK_COMBO_BOX (object);

  switch (prop_id)
    {
      case PROP_MODEL:
        g_value_set_object (value, combo_box->priv->model);
        break;

      case PROP_WRAP_WIDTH:
        g_value_set_int (value, combo_box->priv->wrap_width);
        break;

      case PROP_ROW_SPAN_COLUMN:
        g_value_set_int (value, combo_box->priv->row_column);
        break;

      case PROP_COLUMN_SPAN_COLUMN:
        g_value_set_int (value, combo_box->priv->col_column);
        break;

      case PROP_ACTIVE:
        g_value_set_int (value, gtk_combo_box_get_active (combo_box));
        break;

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

static void
gtk_combo_box_state_changed (GtkWidget    *widget,
			     GtkStateType  previous)
{
  GtkComboBox *combo_box = GTK_COMBO_BOX (widget);

  if (GTK_WIDGET_REALIZED (widget))
    {
      if (combo_box->priv->tree_view && combo_box->priv->cell_view)
	gtk_cell_view_set_background_color (GTK_CELL_VIEW (combo_box->priv->cell_view), 
					    &widget->style->base[GTK_WIDGET_STATE (widget)]);
    }

  gtk_widget_queue_draw (widget);
}

static void
gtk_combo_box_check_appearance (GtkComboBox *combo_box)
{
  gboolean appears_as_list;

  /* if wrap_width > 0, then we are in grid-mode and forced to use
   * unix style
   */
  if (combo_box->priv->wrap_width)
    appears_as_list = FALSE;
  else
    gtk_widget_style_get (GTK_WIDGET (combo_box),
			  "appears-as-list", &appears_as_list,
			  NULL);

  if (appears_as_list)
    {
      /* Destroy all the menu mode widgets, if they exist. */
      if (GTK_IS_MENU (combo_box->priv->popup_widget))
	gtk_combo_box_menu_destroy (combo_box);

      /* Create the list mode widgets, if they don't already exist. */
      if (!GTK_IS_TREE_VIEW (combo_box->priv->tree_view))
	gtk_combo_box_list_setup (combo_box);
    }
  else
    {
      /* Destroy all the list mode widgets, if they exist. */
      if (GTK_IS_TREE_VIEW (combo_box->priv->tree_view))
	gtk_combo_box_list_destroy (combo_box);

      /* Create the menu mode widgets, if they don't already exist. */
      if (!GTK_IS_MENU (combo_box->priv->popup_widget))
	gtk_combo_box_menu_setup (combo_box, TRUE);
    }
}

static void
gtk_combo_box_style_set (GtkWidget *widget,
                         GtkStyle  *previous)
{
  GtkComboBox *combo_box = GTK_COMBO_BOX (widget);

  gtk_combo_box_check_appearance (combo_box);

  if (combo_box->priv->tree_view && combo_box->priv->cell_view)
    gtk_cell_view_set_background_color (GTK_CELL_VIEW (combo_box->priv->cell_view), 
					&widget->style->base[GTK_WIDGET_STATE (widget)]);
}

static void
gtk_combo_box_button_toggled (GtkWidget *widget,
                              gpointer   data)
{
  GtkComboBox *combo_box = GTK_COMBO_BOX (data);

  if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)))
    {
      if (!combo_box->priv->popup_in_progress)
        gtk_combo_box_popup (combo_box);
    }
  else
    gtk_combo_box_popdown (combo_box);
}

static void
gtk_combo_box_add (GtkContainer *container,
                   GtkWidget    *widget)
{
  GtkComboBox *combo_box = GTK_COMBO_BOX (container);

  if (combo_box->priv->cell_view && combo_box->priv->cell_view->parent)
    {
      gtk_widget_unparent (combo_box->priv->cell_view);
      GTK_BIN (container)->child = NULL;
      gtk_widget_queue_resize (GTK_WIDGET (container));
    }
  
  gtk_widget_set_parent (widget, GTK_WIDGET (container));
  GTK_BIN (container)->child = widget;

  if (combo_box->priv->cell_view &&
      widget != combo_box->priv->cell_view)
    {
      /* since the cell_view was unparented, it's gone now */
      combo_box->priv->cell_view = NULL;

      if (!combo_box->priv->tree_view && combo_box->priv->separator)
        {
	  gtk_container_remove (GTK_CONTAINER (combo_box->priv->separator->parent),
				combo_box->priv->separator);
	  combo_box->priv->separator = NULL;

          gtk_widget_queue_resize (GTK_WIDGET (container));
        }
      else if (combo_box->priv->cell_view_frame)
        {
          gtk_widget_unparent (combo_box->priv->cell_view_frame);
          combo_box->priv->cell_view_frame = NULL;
        }
    }
}

static void
gtk_combo_box_remove (GtkContainer *container,
		      GtkWidget    *widget)
{
  GtkComboBox *combo_box = GTK_COMBO_BOX (container);
  gboolean appears_as_list;

  gtk_widget_unparent (widget);
  GTK_BIN (container)->child = NULL;

  if (combo_box->priv->destroying)
    return;

  gtk_widget_queue_resize (GTK_WIDGET (container));

  if (!combo_box->priv->tree_view)
    appears_as_list = FALSE;
  else
    appears_as_list = TRUE;
  
  if (appears_as_list)
    gtk_combo_box_list_destroy (combo_box);
  else if (GTK_IS_MENU (combo_box->priv->popup_widget))
    {
      gtk_combo_box_menu_destroy (combo_box);
      gtk_menu_detach (GTK_MENU (combo_box->priv->popup_widget));
      combo_box->priv->popup_widget = NULL;
    }

  if (!combo_box->priv->cell_view)
    {
      combo_box->priv->cell_view = gtk_cell_view_new ();
      gtk_widget_set_parent (combo_box->priv->cell_view, GTK_WIDGET (container));
      GTK_BIN (container)->child = combo_box->priv->cell_view;
      
      gtk_widget_show (combo_box->priv->cell_view);
      gtk_cell_view_set_model (GTK_CELL_VIEW (combo_box->priv->cell_view),
			       combo_box->priv->model);
      cell_view_sync_cells (combo_box, GTK_CELL_VIEW (combo_box->priv->cell_view));
    }


  if (appears_as_list)
    gtk_combo_box_list_setup (combo_box);
  else
    gtk_combo_box_menu_setup (combo_box, TRUE);

  gtk_combo_box_set_active_internal (combo_box, combo_box->priv->active_item);
}

static ComboCellInfo *
gtk_combo_box_get_cell_info (GtkComboBox     *combo_box,
                             GtkCellRenderer *cell)
{
  GSList *i;

  for (i = combo_box->priv->cells; i; i = i->next)
    {
      ComboCellInfo *info = (ComboCellInfo *)i->data;

      if (info && info->cell == cell)
        return info;
    }

  return NULL;
}

static void
gtk_combo_box_menu_show (GtkWidget *menu,
                         gpointer   user_data)
{
  GtkComboBox *combo_box = GTK_COMBO_BOX (user_data);

  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (combo_box->priv->button),
                                TRUE);
  combo_box->priv->popup_in_progress = FALSE;
}

static void
gtk_combo_box_menu_hide (GtkWidget *menu,
                         gpointer   user_data)
{
  GtkComboBox *combo_box = GTK_COMBO_BOX (user_data);

  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (combo_box->priv->button),
                                FALSE);
}

static void
gtk_combo_box_detacher (GtkWidget *widget,
			GtkMenu	  *menu)
{
  GtkComboBox *combo_box;

  g_return_if_fail (GTK_IS_COMBO_BOX (widget));

  combo_box = GTK_COMBO_BOX (widget);
  g_return_if_fail (combo_box->priv->popup_widget == (GtkWidget*) menu);

  g_signal_handlers_disconnect_by_func (menu,
					gtk_combo_box_menu_show,
					combo_box);
  g_signal_handlers_disconnect_by_func (menu,
					gtk_combo_box_menu_hide,
					combo_box);
  
  combo_box->priv->popup_widget = NULL;
}

static void
gtk_combo_box_set_popup_widget (GtkComboBox *combo_box,
                                GtkWidget   *popup)
{
  if (GTK_IS_MENU (combo_box->priv->popup_widget))
    {
      gtk_menu_detach (GTK_MENU (combo_box->priv->popup_widget));
      combo_box->priv->popup_widget = NULL;
    }
  else if (combo_box->priv->popup_widget)
    {
      gtk_container_remove (GTK_CONTAINER (combo_box->priv->popup_frame),
                            combo_box->priv->popup_widget);
      g_object_unref (G_OBJECT (combo_box->priv->popup_widget));
      combo_box->priv->popup_widget = NULL;
    }

  if (GTK_IS_MENU (popup))
    {
      if (combo_box->priv->popup_window)
        {
          gtk_widget_destroy (combo_box->priv->popup_window);
          combo_box->priv->popup_window = NULL;
	  combo_box->priv->popup_frame = NULL;
        }

      combo_box->priv->popup_widget = popup;

      g_signal_connect (popup, "show",
                        G_CALLBACK (gtk_combo_box_menu_show), combo_box);
      g_signal_connect (popup, "hide",
                        G_CALLBACK (gtk_combo_box_menu_hide), combo_box);

      gtk_menu_attach_to_widget (GTK_MENU (popup),
				 GTK_WIDGET (combo_box),
				 gtk_combo_box_detacher);
    }
  else
    {
      if (!combo_box->priv->popup_window)
        {
          combo_box->priv->popup_window = gtk_window_new (GTK_WINDOW_POPUP);
	  gtk_window_set_resizable (GTK_WINDOW (combo_box->priv->popup_window), FALSE);
#if GTK_CHECK_VERSION(2,2,0)
          gtk_window_set_screen (GTK_WINDOW (combo_box->priv->popup_window),
                                 gtk_widget_get_screen (GTK_WIDGET (combo_box)));
#endif

          combo_box->priv->popup_frame = gtk_frame_new (NULL);
          gtk_frame_set_shadow_type (GTK_FRAME (combo_box->priv->popup_frame),
                                     GTK_SHADOW_ETCHED_IN);
          gtk_container_add (GTK_CONTAINER (combo_box->priv->popup_window),
                             combo_box->priv->popup_frame);

          gtk_widget_show (combo_box->priv->popup_frame);
        }

      gtk_container_add (GTK_CONTAINER (combo_box->priv->popup_frame),
                         popup);
      gtk_widget_show (popup);
      g_object_ref (G_OBJECT (popup));
      combo_box->priv->popup_widget = popup;
    }
}

#if GTK_CHECK_VERSION(2,2,0)
static void
gtk_combo_box_menu_position_below (GtkMenu  *menu,
				   gint     *x,
				   gint     *y,
				   gint     *push_in,
				   gpointer  user_data)
{
  GtkComboBox *combo_box = GTK_COMBO_BOX (user_data);
  gint sx, sy;
  GtkWidget *child;
  GtkRequisition req;
  GdkScreen *screen;
  gint monitor_num;
  GdkRectangle monitor;
  
  /* FIXME: is using the size request here broken? */
   child = GTK_BIN (combo_box)->child;
   
   gdk_window_get_origin (child->window, &sx, &sy);
   
   if (GTK_WIDGET_NO_WINDOW (child))
      {
	sx += child->allocation.x;
	sy += child->allocation.y;
      }

   gtk_widget_size_request (GTK_WIDGET (menu), &req);

   if (gtk_widget_get_direction (GTK_WIDGET (combo_box)) == GTK_TEXT_DIR_LTR)
     *x = sx;
   else
     *x = sx + child->allocation.width - req.width;
   *y = sy;

  screen = gtk_widget_get_screen (GTK_WIDGET (combo_box));
  monitor_num = gdk_screen_get_monitor_at_window (screen, 
						  GTK_WIDGET (combo_box)->window);
  gdk_screen_get_monitor_geometry (screen, monitor_num, &monitor);
  
  if (*x < monitor.x)
    *x = monitor.x;
  else if (*x + req.width > monitor.x + monitor.width)
    *x = monitor.x + monitor.width - req.width;
  
  if (monitor.y + monitor.height - *y - child->allocation.height >= req.height)
    *y += child->allocation.height;
  else if (*y - monitor.y >= req.height)
    *y -= req.height;
  else if (monitor.y + monitor.height - *y - child->allocation.height > *y - monitor.y) 
    *y += child->allocation.height;
  else
    *y -= req.height;

   *push_in = FALSE;
}

static void
gtk_combo_box_menu_position_over (GtkMenu  *menu,
				  gint     *x,
				  gint     *y,
				  gboolean *push_in,
				  gpointer  user_data)
{
  GtkComboBox *combo_box;
  GtkWidget *active;
  GtkWidget *child;
  GtkWidget *widget;
  GtkRequisition requisition;
  GList *children;
  gint screen_width;
  gint menu_xpos;
  gint menu_ypos;
  gint menu_width;

  g_return_if_fail (GTK_IS_COMBO_BOX (user_data));
  
  combo_box = GTK_COMBO_BOX (user_data);
  widget = GTK_WIDGET (combo_box);

  gtk_widget_get_child_requisition (GTK_WIDGET (menu), &requisition);
  menu_width = requisition.width;

  active = gtk_menu_get_active (GTK_MENU (combo_box->priv->popup_widget));
  gdk_window_get_origin (widget->window, &menu_xpos, &menu_ypos);

  menu_xpos += widget->allocation.x;
  menu_ypos += widget->allocation.y + widget->allocation.height / 2 - 2;

  if (active != NULL)
    {
      gtk_widget_get_child_requisition (active, &requisition);
      menu_ypos -= requisition.height / 2;
    }

  children = GTK_MENU_SHELL (combo_box->priv->popup_widget)->children;
  while (children)
    {
      child = children->data;

      if (active == child)
	break;

      if (GTK_WIDGET_VISIBLE (child))
	{
	  gtk_widget_get_child_requisition (child, &requisition);
	  menu_ypos -= requisition.height;
	}

      children = children->next;
    }

  if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
    menu_xpos = menu_xpos + widget->allocation.width - menu_width;

  /* Clamp the position on screen */
  screen_width = gdk_screen_get_width (gtk_widget_get_screen (widget));
  
  if (menu_xpos < 0)
    menu_xpos = 0;
  else if ((menu_xpos + menu_width) > screen_width)
    menu_xpos -= ((menu_xpos + menu_width) - screen_width);

  *x = menu_xpos;
  *y = menu_ypos;

  *push_in = TRUE;
}

static void
gtk_combo_box_menu_position (GtkMenu  *menu,
			     gint     *x,
			     gint     *y,
			     gint     *push_in,
			     gpointer  user_data)
{
  GtkComboBox *combo_box;
  GtkWidget *menu_item;

  combo_box = GTK_COMBO_BOX (user_data);

  if (combo_box->priv->wrap_width > 0 || combo_box->priv->cell_view == NULL)	
    gtk_combo_box_menu_position_below (menu, x, y, push_in, user_data);
  else
    {
      menu_item = gtk_menu_get_active (GTK_MENU (combo_box->priv->popup_widget));
      if (menu_item)
	gtk_menu_shell_select_item (GTK_MENU_SHELL (combo_box->priv->popup_widget), 
				    menu_item);

      gtk_combo_box_menu_position_over (menu, x, y, push_in, user_data);
    }

}

static void
gtk_combo_box_list_position (GtkComboBox *combo_box, 
			     gint        *x, 
			     gint        *y, 
			     gint        *width,
			     gint        *height)
{
  GtkWidget *sample;
  GdkScreen *screen;
  gint monitor_num;
  GdkRectangle monitor;
  GtkRequisition popup_req;
  
  sample = GTK_BIN (combo_box)->child;

  *width = sample->allocation.width;
  gtk_widget_size_request (combo_box->priv->popup_window, &popup_req);
  *height = popup_req.height;

  gdk_window_get_origin (sample->window, x, y);

  if (combo_box->priv->cell_view_frame)
    {
       *x -= GTK_CONTAINER (combo_box->priv->cell_view_frame)->border_width +
	     GTK_WIDGET (combo_box->priv->cell_view_frame)->style->xthickness;
       *width += 2 * (GTK_CONTAINER (combo_box->priv->cell_view_frame)->border_width +
            GTK_WIDGET (combo_box->priv->cell_view_frame)->style->xthickness);
    }

  if (GTK_WIDGET_NO_WINDOW (sample))
    {
      *x += sample->allocation.x;
      *y += sample->allocation.y;
    }
  
  screen = gtk_widget_get_screen (GTK_WIDGET (combo_box));
  monitor_num = gdk_screen_get_monitor_at_window (screen, 
						  GTK_WIDGET (combo_box)->window);
  gdk_screen_get_monitor_geometry (screen, monitor_num, &monitor);
  
  if (*x < monitor.x)
    *x = monitor.x;
  else if (*x + *width > monitor.x + monitor.width)
    *x = monitor.x + monitor.width - *width;
  
  if (*y + sample->allocation.height + *height <= monitor.y + monitor.height)
    *y += sample->allocation.height;
  else
    *y -= *height;
} 
#endif /* Gtk 2.2 */

/**
 * gtk_combo_box_popup:
 * @combo_box: a #GtkComboBox
 * 
 * Pops up the menu or dropdown list of @combo_box. 
 *
 * This function is mostly intended for use by accessibility technologies;
 * applications should have little use for it.
 *
 * Since: 2.4
 **/
void
gtk_combo_box_popup (GtkComboBox *combo_box)
{
  gint x, y, width;
#if GTK_CHECK_VERSION(2,2,0)
  gint height;
#endif
  
  g_return_if_fail (GTK_IS_COMBO_BOX (combo_box));

  if (GTK_WIDGET_MAPPED (combo_box->priv->popup_widget))
    return;

  if (GTK_IS_MENU (combo_box->priv->popup_widget))
    {
      gtk_menu_set_active (GTK_MENU (combo_box->priv->popup_widget),
			   combo_box->priv->active_item);

      if (combo_box->priv->wrap_width == 0)
	{
          GtkRequisition requisition;

	  width = GTK_WIDGET (combo_box)->allocation.width;
          gtk_widget_size_request (combo_box->priv->popup_widget, &requisition);

	  gtk_widget_set_size_request (combo_box->priv->popup_widget,
                                       MAX (width, requisition.width), -1);
	}
      
      gtk_menu_popup (GTK_MENU (combo_box->priv->popup_widget),
		      NULL, NULL,
#if GTK_CHECK_VERSION(2,2,0)
		      gtk_combo_box_menu_position,
#else
		      NULL,
#endif
		      combo_box, 0, 0);
      return;
    }

  gtk_widget_show_all (combo_box->priv->popup_frame);
#if GTK_CHECK_VERSION(2,2,0)
  gtk_combo_box_list_position (combo_box, &x, &y, &width, &height);
#endif

  gtk_widget_set_size_request (combo_box->priv->popup_window, width, -1);  
  gtk_window_move (GTK_WINDOW (combo_box->priv->popup_window), x, y);

  /* popup */
  gtk_widget_show (combo_box->priv->popup_window);

  gtk_widget_grab_focus (combo_box->priv->popup_window);
  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (combo_box->priv->button),
                                TRUE);

  if (!GTK_WIDGET_HAS_FOCUS (combo_box->priv->tree_view))
    {
      gdk_keyboard_grab (combo_box->priv->popup_window->window,
                         FALSE, GDK_CURRENT_TIME);
      gtk_widget_grab_focus (combo_box->priv->tree_view);
    }

  gtk_grab_add (combo_box->priv->popup_window);
  gdk_pointer_grab (combo_box->priv->popup_window->window, TRUE,
                    GDK_BUTTON_PRESS_MASK |
                    GDK_BUTTON_RELEASE_MASK |
                    GDK_POINTER_MOTION_MASK,
                    NULL, NULL, GDK_CURRENT_TIME);

  gtk_grab_add (combo_box->priv->tree_view);
}

/**
 * gtk_combo_box_popdown:
 * @combo_box: a #GtkComboBox
 * 
 * Hides the menu or dropdown list of @combo_box.
 *
 * This function is mostly intended for use by accessibility technologies;
 * applications should have little use for it.
 *
 * Since: 2.4
 **/
void
gtk_combo_box_popdown (GtkComboBox *combo_box)
{
  g_return_if_fail (GTK_IS_COMBO_BOX (combo_box));

  if (!GTK_WIDGET_REALIZED (GTK_WIDGET (combo_box)))
    return;

  if (GTK_IS_MENU (combo_box->priv->popup_widget))
    {
      gtk_menu_popdown (GTK_MENU (combo_box->priv->popup_widget));
      return;
    }

  gtk_combo_box_list_remove_grabs (combo_box);
  gtk_widget_hide_all (combo_box->priv->popup_window);
  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (combo_box->priv->button),
                                FALSE);
}

static gint
gtk_combo_box_calc_requested_width (GtkComboBox *combo_box,
                                    GtkTreePath *path)
{
  gint padding;
  GtkRequisition req;

  if (combo_box->priv->cell_view)
    gtk_widget_style_get (combo_box->priv->cell_view,
                          "focus-line-width", &padding,
                          NULL);
  else
    padding = 0;

  /* add some pixels for good measure */
  padding += BONUS_PADDING;

  if (combo_box->priv->cell_view)
    gtk_cell_view_get_size_of_row (GTK_CELL_VIEW (combo_box->priv->cell_view),
                                   path, &req);
  else
    req.width = 0;

  return req.width + padding;
}

static void
gtk_combo_box_remeasure (GtkComboBox *combo_box)
{
  GtkTreeIter iter;
  GtkTreePath *path;
  gint padding = 0;

  if (!combo_box->priv->model ||
      !gtk_tree_model_get_iter_first (combo_box->priv->model, &iter))
    return;

  combo_box->priv->width = 0;

#if GTK_CHECK_VERSION(2,2,0)
  path = gtk_tree_path_new_from_indices (0, -1);
#else
  path = gtk_tree_path_new_first();
#endif

  if (combo_box->priv->cell_view)
    gtk_widget_style_get (combo_box->priv->cell_view,
                          "focus-line-width", &padding,
                          NULL);
  else
    padding = 0;

  /* add some pixels for good measure */
  padding += BONUS_PADDING;

  do
    {
      GtkRequisition req;

      if (combo_box->priv->cell_view)
	gtk_cell_view_get_size_of_row (GTK_CELL_VIEW (combo_box->priv->cell_view), 
                                       path, &req);
      else
        req.width = 0;

      combo_box->priv->width = MAX (combo_box->priv->width,
                                    req.width + padding);

      gtk_tree_path_next (path);
    }
  while (gtk_tree_model_iter_next (combo_box->priv->model, &iter));

  gtk_tree_path_free (path);
}

static void
gtk_combo_box_size_request (GtkWidget      *widget,
                            GtkRequisition *requisition)
{
  gint width, height;
  GtkRequisition bin_req;

  GtkComboBox *combo_box = GTK_COMBO_BOX (widget);

  /* common */
  gtk_widget_size_request (GTK_BIN (widget)->child, &bin_req);
  gtk_combo_box_remeasure (combo_box);
  bin_req.width = MAX (bin_req.width, combo_box->priv->width);

  gtk_combo_box_check_appearance (combo_box);
      
  if (!combo_box->priv->tree_view)
    {
      /* menu mode */

      if (combo_box->priv->cell_view)
        {
          GtkRequisition button_req, sep_req, arrow_req;
          gint border_width, xthickness, ythickness;

          gtk_widget_size_request (combo_box->priv->button, &button_req);
          border_width = GTK_CONTAINER (combo_box->priv->button)->border_width;
          xthickness = combo_box->priv->button->style->xthickness;
          ythickness = combo_box->priv->button->style->ythickness;

          bin_req.width = MAX (bin_req.width, combo_box->priv->width);

          gtk_widget_size_request (combo_box->priv->separator, &sep_req);
          gtk_widget_size_request (combo_box->priv->arrow, &arrow_req);

          height = MAX (sep_req.height, arrow_req.height);
          height = MAX (height, bin_req.height);

          width = bin_req.width + sep_req.width + arrow_req.width;

          height += border_width + 1 + ythickness * 2 + 4;
          width += border_width + 1 + xthickness * 2 + 4;

          requisition->width = width;
          requisition->height = height;
        }
      else
        {
          GtkRequisition but_req;

          gtk_widget_size_request (combo_box->priv->button, &but_req);

          requisition->width = bin_req.width + but_req.width;
          requisition->height = MAX (bin_req.height, but_req.height);
        }
    }
  else
    {
      /* list mode */
      GtkRequisition button_req, frame_req;

      /* sample + frame */
      *requisition = bin_req;

      if (combo_box->priv->cell_view_frame)
        {
	  gtk_widget_size_request (combo_box->priv->cell_view_frame, &frame_req);
          requisition->width += 2 *
            (GTK_CONTAINER (combo_box->priv->cell_view_frame)->border_width +
             GTK_WIDGET (combo_box->priv->cell_view_frame)->style->xthickness);
          requisition->height += 2 *
            (GTK_CONTAINER (combo_box->priv->cell_view_frame)->border_width +
             GTK_WIDGET (combo_box->priv->cell_view_frame)->style->ythickness);
        }

      /* the button */
      gtk_widget_size_request (combo_box->priv->button, &button_req);

      requisition->height = MAX (requisition->height, button_req.height);
      requisition->width += button_req.width;
    }
}

static void
gtk_combo_box_size_allocate (GtkWidget     *widget,
                             GtkAllocation *allocation)
{
  GtkComboBox *combo_box = GTK_COMBO_BOX (widget);
  GtkAllocation child;
  GtkRequisition req;
  gboolean is_rtl = gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL;

  widget->allocation = *allocation;

  gtk_combo_box_check_appearance (combo_box);

  if (!combo_box->priv->tree_view)
    {
      if (combo_box->priv->cell_view)
        {
          gint border_width, xthickness, ythickness;
          gint width;

          /* menu mode */
          gtk_widget_size_allocate (combo_box->priv->button, allocation);

          /* set some things ready */
          border_width = GTK_CONTAINER (combo_box->priv->button)->border_width;
          xthickness = combo_box->priv->button->style->xthickness;
          ythickness = combo_box->priv->button->style->ythickness;

          child.x = allocation->x + border_width + 1 + xthickness + 2;
          child.y = allocation->y + border_width + 1 + ythickness + 2;

          width = MAX(1, allocation->width - (border_width + 1 + xthickness * 2 + 4));

          /* handle the children */
          gtk_widget_size_request (combo_box->priv->arrow, &req);
          child.width = req.width;
          child.height = MAX(1, allocation->height - 2 * (child.y - allocation->y));
          if (!is_rtl)
            child.x += width - req.width;
          gtk_widget_size_allocate (combo_box->priv->arrow, &child);
          if (is_rtl)
            child.x += req.width;
          gtk_widget_size_request (combo_box->priv->separator, &req);
          child.width = req.width;
          if (!is_rtl)
            child.x -= req.width;
          gtk_widget_size_allocate (combo_box->priv->separator, &child);

          if (is_rtl)
            {
              child.x += req.width;
              child.width = MAX(1, allocation->x + allocation->width 
                - (border_width + 1 + xthickness + 2) - child.x);
            }
          else 
            {
              child.width = child.x;
              child.x = allocation->x + border_width + 1 + xthickness + 2;
              child.width = MAX(1, child.width - child.x);
            }

          gtk_widget_size_allocate (GTK_BIN (widget)->child, &child);
        }
      else
        {
          gtk_widget_size_request (combo_box->priv->button, &req);
          if (is_rtl)
            child.x = allocation->x;
          else
            child.x = allocation->x + allocation->width - req.width;
          child.y = allocation->y;
          child.width = req.width;
          child.height = allocation->height;
          gtk_widget_size_allocate (combo_box->priv->button, &child);

          if (is_rtl)
            child.x = allocation->x + req.width;
          else
            child.x = allocation->x;
          child.y = allocation->y;
          child.width = MAX(1, allocation->width - req.width);
          gtk_widget_size_allocate (GTK_BIN (widget)->child, &child);
        }
    }
  else
    {
      /* list mode */

      /* button */
      gtk_widget_size_request (combo_box->priv->button, &req);
      if (is_rtl)
        child.x = allocation->x;
      else
        child.x = allocation->x + allocation->width - req.width;
      child.y = allocation->y;
      child.width = req.width;
      child.height = allocation->height;
      gtk_widget_size_allocate (combo_box->priv->button, &child);

      /* frame */
      if (is_rtl)
        child.x = allocation->x + req.width;
      else
        child.x = allocation->x;
      child.y = allocation->y;
      child.width = MAX (1, allocation->width - req.width);
      child.height = allocation->height;

      if (combo_box->priv->cell_view_frame)
        {
          gtk_widget_size_allocate (combo_box->priv->cell_view_frame, &child);

          /* the sample */
          child.x +=
            GTK_CONTAINER (combo_box->priv->cell_view_frame)->border_width +
            GTK_WIDGET (combo_box->priv->cell_view_frame)->style->xthickness;
          child.y +=
            GTK_CONTAINER (combo_box->priv->cell_view_frame)->border_width +
            GTK_WIDGET (combo_box->priv->cell_view_frame)->style->ythickness;
          child.width -= 2 * (
            GTK_CONTAINER (combo_box->priv->cell_view_frame)->border_width +
            GTK_WIDGET (combo_box->priv->cell_view_frame)->style->xthickness);
	  child.width = MAX(1,child.width);
          child.height -= 2 * (
            GTK_CONTAINER (combo_box->priv->cell_view_frame)->border_width +
            GTK_WIDGET (combo_box->priv->cell_view_frame)->style->ythickness);
	  child.height = MAX(1,child.height);
        }

      gtk_widget_size_allocate (GTK_BIN (combo_box)->child, &child);
    }
}

static void
gtk_combo_box_unset_model (GtkComboBox *combo_box)
{
  if (combo_box->priv->model)
    {
      g_signal_handler_disconnect (combo_box->priv->model,
				   combo_box->priv->inserted_id);
      g_signal_handler_disconnect (combo_box->priv->model,
				   combo_box->priv->deleted_id);
      g_signal_handler_disconnect (combo_box->priv->model,
				   combo_box->priv->reordered_id);
      g_signal_handler_disconnect (combo_box->priv->model,
				   combo_box->priv->changed_id);
    }

  /* menu mode */
  if (!combo_box->priv->tree_view)
    {
      if (combo_box->priv->popup_widget)
        gtk_container_foreach (GTK_CONTAINER (combo_box->priv->popup_widget),
                               (GtkCallback)gtk_widget_destroy, NULL);
    }

  if (combo_box->priv->model)
    {
      g_object_unref (G_OBJECT (combo_box->priv->model));
      combo_box->priv->model = NULL;
    }

  if (combo_box->priv->cell_view)
    gtk_cell_view_set_displayed_row (GTK_CELL_VIEW (combo_box->priv->cell_view), NULL);
}

static void
gtk_combo_box_forall (GtkContainer *container,
                      gboolean      include_internals,
                      GtkCallback   callback,
                      gpointer      callback_data)
{
  GtkComboBox *combo_box = GTK_COMBO_BOX (container);

  if (include_internals)
    {
      if (combo_box->priv->button)
	(* callback) (combo_box->priv->button, callback_data);
      if (combo_box->priv->cell_view_frame)
	(* callback) (combo_box->priv->cell_view_frame, callback_data);
    }

  if (GTK_BIN (container)->child)
    (* callback) (GTK_BIN (container)->child, callback_data);
}

static gboolean
gtk_combo_box_expose_event (GtkWidget      *widget,
                            GdkEventExpose *event)
{
  GtkComboBox *combo_box = GTK_COMBO_BOX (widget);

  if (!combo_box->priv->tree_view)
    {
      gtk_container_propagate_expose (GTK_CONTAINER (widget),
                                      combo_box->priv->button, event);
    }
  else
    {
      gtk_container_propagate_expose (GTK_CONTAINER (widget),
                                      combo_box->priv->button, event);

      if (combo_box->priv->cell_view_frame)
        gtk_container_propagate_expose (GTK_CONTAINER (widget),
                                        combo_box->priv->cell_view_frame, event);
    }

  gtk_container_propagate_expose (GTK_CONTAINER (widget),
                                  GTK_BIN (widget)->child, event);

  return FALSE;
}

static gboolean
gtk_combo_box_scroll_event (GtkWidget          *widget,
                            GdkEventScroll     *event)
{
  GtkComboBox *combo_box = GTK_COMBO_BOX (widget);
  gint index;
  gint items;
    
  index = gtk_combo_box_get_active (combo_box);

  if (index != -1)
    {
      items = gtk_tree_model_iter_n_children (combo_box->priv->model, NULL);
      
      if (event->direction == GDK_SCROLL_UP)
        index--;
      else 
        index++;

      gtk_combo_box_set_active (combo_box, CLAMP (index, 0, items - 1));
    }

  return TRUE;
}

/*
 * menu style
 */

static void
cell_view_sync_cells (GtkComboBox *combo_box,
                      GtkCellView *cell_view)
{
  GSList *k;

  for (k = combo_box->priv->cells; k; k = k->next)
    {
      GSList *j;
      ComboCellInfo *info = (ComboCellInfo *)k->data;

      if (info->pack == GTK_PACK_START)
        gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (cell_view),
                                    info->cell, info->expand);
      else if (info->pack == GTK_PACK_END)
        gtk_cell_layout_pack_end (GTK_CELL_LAYOUT (cell_view),
                                  info->cell, info->expand);

      gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (cell_view),
                                          info->cell,
                                          info->func, info->func_data, NULL);

      for (j = info->attributes; j; j = j->next->next)
        {
          gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (cell_view),
                                         info->cell,
                                         j->data,
                                         GPOINTER_TO_INT (j->next->data));
        }
    }
}

static void
gtk_combo_box_menu_setup (GtkComboBox *combo_box,
                          gboolean     add_children)
{
  GtkWidget *menu;

  if (combo_box->priv->cell_view)
    {
      combo_box->priv->button = gtk_toggle_button_new ();
      g_signal_connect (combo_box->priv->button, "toggled",
                        G_CALLBACK (gtk_combo_box_button_toggled), combo_box);
      g_signal_connect_after (combo_box->priv->button, "key_press_event",
			      G_CALLBACK (gtk_combo_box_key_press), combo_box);
      gtk_widget_set_parent (combo_box->priv->button,
                             GTK_BIN (combo_box)->child->parent);

      combo_box->priv->box = gtk_hbox_new (FALSE, 0);
      gtk_container_add (GTK_CONTAINER (combo_box->priv->button), 
			 combo_box->priv->box);

      combo_box->priv->separator = gtk_vseparator_new ();
      gtk_container_add (GTK_CONTAINER (combo_box->priv->box), 
			 combo_box->priv->separator);

      combo_box->priv->arrow = gtk_arrow_new (GTK_ARROW_DOWN, GTK_SHADOW_NONE);
      gtk_container_add (GTK_CONTAINER (combo_box->priv->box), 
			 combo_box->priv->arrow);

      gtk_widget_show_all (combo_box->priv->button);
    }
  else
    {
      combo_box->priv->button = gtk_toggle_button_new ();
      g_signal_connect (combo_box->priv->button, "toggled",
                        G_CALLBACK (gtk_combo_box_button_toggled), combo_box);
      g_signal_connect_after (combo_box, "key_press_event",
			      G_CALLBACK (gtk_combo_box_key_press), combo_box);
      gtk_widget_set_parent (combo_box->priv->button,
                             GTK_BIN (combo_box)->child->parent);

      combo_box->priv->arrow = gtk_arrow_new (GTK_ARROW_DOWN, GTK_SHADOW_NONE);
      gtk_container_add (GTK_CONTAINER (combo_box->priv->button),
                         combo_box->priv->arrow);
      gtk_widget_show_all (combo_box->priv->button);
    }

  g_signal_connect (combo_box->priv->button, "button_press_event",
                    G_CALLBACK (gtk_combo_box_menu_button_press),
                    combo_box);

  /* create our funky menu */
  menu = gtk_menu_new ();
  g_signal_connect (menu, "key_press_event",
		    G_CALLBACK (gtk_combo_box_menu_key_press), combo_box);
  gtk_combo_box_set_popup_widget (combo_box, menu);

  /* add items */
  if (add_children)
    gtk_combo_box_menu_fill (combo_box);

}

static void
gtk_combo_box_menu_fill (GtkComboBox *combo_box)
{
  gint i, items;
  GtkWidget *menu;
  GtkWidget *tmp;

  if (!combo_box->priv->model)
    return;

  items = gtk_tree_model_iter_n_children (combo_box->priv->model, NULL);
  menu = combo_box->priv->popup_widget;

  for (i = 0; i < items; i++)
    {
      GtkTreePath *path;
#if GTK_CHECK_VERSION(2,2,0)
      path = gtk_tree_path_new_from_indices (i, -1);
#else
      char buf[32];
      g_snprintf(buf, sizeof(buf), "%d", i);
      path = gtk_tree_path_new_from_string(buf);
#endif
      tmp = gtk_cell_view_menu_item_new_from_model (combo_box->priv->model,
                                                    path);
      g_signal_connect (tmp, "activate",
                        G_CALLBACK (gtk_combo_box_menu_item_activate),
                        combo_box);

      cell_view_sync_cells (combo_box,
                            GTK_CELL_VIEW (GTK_BIN (tmp)->child));

      gtk_menu_shell_append (GTK_MENU_SHELL (menu), tmp);

      if (combo_box->priv->wrap_width)
        gtk_combo_box_relayout_item (combo_box, i);

      gtk_widget_show (tmp);

      gtk_tree_path_free (path);
    }
}

static void
gtk_combo_box_menu_destroy (GtkComboBox *combo_box)
{
  g_signal_handlers_disconnect_matched (combo_box->priv->button,
                                        G_SIGNAL_MATCH_DATA,
                                        0, 0, NULL,
                                        gtk_combo_box_menu_button_press, NULL);

  /* unparent will remove our latest ref */
  gtk_widget_unparent (combo_box->priv->button);
  
  combo_box->priv->box = NULL;
  combo_box->priv->button = NULL;
  combo_box->priv->arrow = NULL;
  combo_box->priv->separator = NULL;

  /* changing the popup window will unref the menu and the children */
}

/*
 * grid
 */

static void
gtk_combo_box_item_get_size (GtkComboBox *combo_box,
                             gint         index_,
                             gint        *cols,
                             gint        *rows)
{
  GtkTreeIter iter;

  gtk_tree_model_iter_nth_child (combo_box->priv->model, &iter, NULL, index_);

  if (cols)
    {
      if (combo_box->priv->col_column == -1)
        *cols = 1;
      else
        gtk_tree_model_get (combo_box->priv->model, &iter,
                            combo_box->priv->col_column, cols,
                            -1);
    }

  if (rows)
    {
      if (combo_box->priv->row_column == -1)
        *rows = 1;
      else
        gtk_tree_model_get (combo_box->priv->model, &iter,
                            combo_box->priv->row_column, rows,
                            -1);
    }
}

static gboolean
menu_occupied (GtkMenu *menu,
               guint    left_attach,
               guint    right_attach,
               guint    top_attach,
               guint    bottom_attach)
{
  GList *i;

  g_return_val_if_fail (GTK_IS_MENU (menu), TRUE);
  g_return_val_if_fail (left_attach < right_attach, TRUE);
  g_return_val_if_fail (top_attach < bottom_attach, TRUE);

  for (i = GTK_MENU_SHELL (menu)->children; i; i = i->next)
    {
      guint l, r, b, t;
      gboolean h_intersect = FALSE;
      gboolean v_intersect = FALSE;

      gtk_container_child_get (GTK_CONTAINER (menu), i->data,
                               "left_attach", &l,
                               "right_attach", &r,
                               "bottom_attach", &b,
                               "top_attach", &t,
                               NULL);

      /* look if this item intersects with the given coordinates */
      h_intersect  = left_attach <= l && l <= right_attach;
      h_intersect &= left_attach <= r && r <= right_attach;

      v_intersect  = top_attach <= t && t <= bottom_attach;
      v_intersect &= top_attach <= b && b <= bottom_attach;

      if (h_intersect && v_intersect)
        return TRUE;
    }

  return FALSE;
}

static void
gtk_combo_box_relayout_item (GtkComboBox *combo_box,
                             gint         index)
{
  gint current_col = 0, current_row = 0;
  gint rows, cols;
  GList *list, *nth;
  GtkWidget *item, *last;
  GtkWidget *menu;

  menu = combo_box->priv->popup_widget;
  if (!GTK_IS_MENU_SHELL (menu))
    return;

  list = gtk_container_get_children (GTK_CONTAINER (menu));
  nth = g_list_nth (list, index);
  item = nth->data;
  if (nth->prev)
    last = nth->prev->data;
  else 
    last = NULL;
  g_list_free (list);

  gtk_combo_box_item_get_size (combo_box, index, &cols, &rows);
      
   if (combo_box->priv->col_column == -1 &&
      combo_box->priv->row_column == -1 &&
      last)
    {
      gtk_container_child_get (GTK_CONTAINER (menu), 
			       last,
			       "right_attach", &current_col,
			       "top_attach", &current_row,
			       NULL);
      if (current_col + cols > combo_box->priv->wrap_width)
	{
	  current_col = 0;
	  current_row++;
	}
    }
  else
    {
      /* look for a good spot */
      while (1)
	{
	  if (current_col + cols > combo_box->priv->wrap_width)
	    {
	      current_col = 0;
	      current_row++;
	    }
	  
	  if (!menu_occupied (GTK_MENU (menu),
			      current_col, current_col + cols,
			      current_row, current_row + rows))
	    break;
	  
	  current_col++;
	}
    }

  /* set attach props */
  gtk_menu_attach (GTK_MENU (menu), item,
                   current_col, current_col + cols,
                   current_row, current_row + rows);
}

static void
gtk_combo_box_relayout (GtkComboBox *combo_box)
{
  GList *list, *j;
  GtkWidget *menu;

  menu = combo_box->priv->popup_widget;
  
  /* do nothing unless we are in menu style and realized */
  if (combo_box->priv->tree_view || !GTK_IS_MENU_SHELL (menu))
    return;
  
  /* get rid of all children */
  list = gtk_container_get_children (GTK_CONTAINER (menu));
  
  for (j = g_list_last (list); j; j = j->prev)
    gtk_container_remove (GTK_CONTAINER (menu), j->data);
  
  g_list_free (list);
      
  /* and relayout */
  gtk_combo_box_menu_fill (combo_box);
}

/* callbacks */
static gboolean
gtk_combo_box_menu_button_press (GtkWidget      *widget,
                                 GdkEventButton *event,
                                 gpointer        user_data)
{
  GtkComboBox *combo_box = GTK_COMBO_BOX (user_data);

  if (! GTK_IS_MENU (combo_box->priv->popup_widget))
    return FALSE;

  if (event->type == GDK_BUTTON_PRESS && event->button == 1)
    {
      combo_box->priv->popup_in_progress = TRUE;
      
      gtk_menu_set_active (GTK_MENU (combo_box->priv->popup_widget),
			   combo_box->priv->active_item);

      if (combo_box->priv->wrap_width == 0)
	{
          GtkRequisition requisition;
          gint width;

	  width = GTK_WIDGET (combo_box)->allocation.width;
          gtk_widget_size_request (combo_box->priv->popup_widget, &requisition);

	  gtk_widget_set_size_request (combo_box->priv->popup_widget,
                                       MAX (width, requisition.width), -1);
	}

      gtk_menu_popup (GTK_MENU (combo_box->priv->popup_widget),
                      NULL, NULL,
#if GTK_CHECK_VERSION(2,2,0)
                      gtk_combo_box_menu_position,
#else
		      NULL,
#endif
		      combo_box, event->button, event->time);

      return TRUE;
    }

  return FALSE;
}

static void
gtk_combo_box_menu_item_activate (GtkWidget *item,
                                  gpointer   user_data)
{
  gint index;
  GtkWidget *menu;
  GtkComboBox *combo_box = GTK_COMBO_BOX (user_data);

  menu = combo_box->priv->popup_widget;
  g_return_if_fail (GTK_IS_MENU (menu));

  index = g_list_index (GTK_MENU_SHELL (menu)->children, item);

  gtk_combo_box_set_active (combo_box, index);
}

static void
gtk_combo_box_model_row_inserted (GtkTreeModel     *model,
				  GtkTreePath      *path,
				  GtkTreeIter      *iter,
				  gpointer          user_data)
{
  GtkComboBox *combo_box = GTK_COMBO_BOX (user_data);
  gint index = gtk_tree_path_get_indices (path)[0];

  if (combo_box->priv->active_item >= index)
    combo_box->priv->active_item++;

  if (!combo_box->priv->tree_view)
    gtk_combo_box_menu_row_inserted (model, path, iter, user_data);
}

static void
gtk_combo_box_model_row_deleted (GtkTreeModel     *model,
				 GtkTreePath      *path,
				 gpointer          user_data)
{
  GtkComboBox *combo_box = GTK_COMBO_BOX (user_data);
  gint index = gtk_tree_path_get_indices (path)[0];

  if (!combo_box->priv->tree_view)
    gtk_combo_box_menu_row_deleted (model, path, user_data);
  
  if (index == combo_box->priv->active_item)
    {
      gint items = gtk_tree_model_iter_n_children (model, NULL);

      if (items == 0)
	gtk_combo_box_set_active_internal (combo_box, -1);
      else if (index == items)
	gtk_combo_box_set_active_internal (combo_box, index - 1);
      else
	gtk_combo_box_set_active_internal (combo_box, index);
    }
  else if (combo_box->priv->active_item > index)
    combo_box->priv->active_item--;
}

static void
gtk_combo_box_model_rows_reordered (GtkTreeModel    *model,
				    GtkTreePath     *path,
				    GtkTreeIter     *iter,
				    gint            *new_order,
				    gpointer         user_data)
{
  GtkComboBox *combo_box = GTK_COMBO_BOX (user_data);
  gint items = gtk_tree_model_iter_n_children (model, NULL);
  gint i;

  for (i = 0; i < items; i++)
    if (new_order[i] == combo_box->priv->active_item)
      {
	combo_box->priv->active_item = i;
	break;
      }

  if (!combo_box->priv->tree_view)
    gtk_combo_box_menu_rows_reordered (model, path, iter, new_order, user_data);
}
						    
static void
gtk_combo_box_model_row_changed (GtkTreeModel     *model,
				 GtkTreePath      *path,
				 GtkTreeIter      *iter,
				 gpointer          user_data)
{
  GtkComboBox *combo_box = GTK_COMBO_BOX (user_data);
  gint index = gtk_tree_path_get_indices (path)[0];

  if (index == combo_box->priv->active_item &&
      combo_box->priv->cell_view)
    gtk_widget_queue_resize (GTK_WIDGET (combo_box->priv->cell_view));
  
  if (combo_box->priv->tree_view)
    gtk_combo_box_list_row_changed (model, path, iter, user_data);
  else
    gtk_combo_box_menu_row_changed (model, path, iter, user_data);
}


static void
gtk_combo_box_menu_row_inserted (GtkTreeModel *model,
                                 GtkTreePath  *path,
                                 GtkTreeIter  *iter,
                                 gpointer      user_data)
{
  GtkWidget *menu;
  GtkWidget *item;
  GtkComboBox *combo_box = GTK_COMBO_BOX (user_data);

  if (!combo_box->priv->popup_widget)
    return;

  menu = combo_box->priv->popup_widget;
  g_return_if_fail (GTK_IS_MENU (menu));

  item = gtk_cell_view_menu_item_new_from_model (model, path);
  g_signal_connect (item, "activate",
                    G_CALLBACK (gtk_combo_box_menu_item_activate),
                    combo_box);

  cell_view_sync_cells (combo_box, GTK_CELL_VIEW (GTK_BIN (item)->child));

  gtk_menu_shell_insert (GTK_MENU_SHELL (menu), item,
                         gtk_tree_path_get_indices (path)[0]);
  gtk_widget_show (item);
}

static void
gtk_combo_box_menu_row_deleted (GtkTreeModel *model,
                                GtkTreePath  *path,
                                gpointer      user_data)
{
  gint index;
  GtkWidget *menu;
  GtkWidget *item;
  GtkComboBox *combo_box = GTK_COMBO_BOX (user_data);

  if (!combo_box->priv->popup_widget)
    return;

  index = gtk_tree_path_get_indices (path)[0];

  menu = combo_box->priv->popup_widget;
  g_return_if_fail (GTK_IS_MENU (menu));

  item = g_list_nth_data (GTK_MENU_SHELL (menu)->children, index);
  g_return_if_fail (GTK_IS_MENU_ITEM (item));

  gtk_container_remove (GTK_CONTAINER (menu), item);
}

static void
gtk_combo_box_menu_rows_reordered  (GtkTreeModel     *model,
				    GtkTreePath      *path,
				    GtkTreeIter      *iter,
				    gint             *new_order,
				    gpointer          user_data)
{
  GtkComboBox *combo_box = GTK_COMBO_BOX (user_data);

  gtk_combo_box_relayout (combo_box);
}
				    
static void
gtk_combo_box_menu_row_changed (GtkTreeModel *model,
                                GtkTreePath  *path,
                                GtkTreeIter  *iter,
                                gpointer      user_data)
{
  GtkComboBox *combo_box = GTK_COMBO_BOX (user_data);
  gint width;

  if (!combo_box->priv->popup_widget)
    return;

  if (combo_box->priv->wrap_width)
    gtk_combo_box_relayout_item (combo_box,
                                 gtk_tree_path_get_indices (path)[0]);

  width = gtk_combo_box_calc_requested_width (combo_box, path);

  if (width > combo_box->priv->width)
    {
      if (combo_box->priv->cell_view)
	{
	  gtk_widget_set_size_request (combo_box->priv->cell_view, width, -1);
	  gtk_widget_queue_resize (combo_box->priv->cell_view);
	}
      combo_box->priv->width = width;
    }
}

/*
 * list style
 */

static void
gtk_combo_box_list_setup (GtkComboBox *combo_box)
{
  GSList *i;
  GtkTreeSelection *sel;

  combo_box->priv->button = gtk_toggle_button_new ();
  gtk_widget_set_parent (combo_box->priv->button,
                         GTK_BIN (combo_box)->child->parent);
  g_signal_connect (combo_box->priv->button, "button_press_event",
                    G_CALLBACK (gtk_combo_box_list_button_pressed), combo_box);
  g_signal_connect (combo_box->priv->button, "toggled",
                    G_CALLBACK (gtk_combo_box_button_toggled), combo_box);
  g_signal_connect_after (combo_box, "key_press_event",
			  G_CALLBACK (gtk_combo_box_key_press), combo_box);

  combo_box->priv->arrow = gtk_arrow_new (GTK_ARROW_DOWN, GTK_SHADOW_NONE);
  gtk_container_add (GTK_CONTAINER (combo_box->priv->button),
                     combo_box->priv->arrow);
  combo_box->priv->separator = NULL;
  gtk_widget_show_all (combo_box->priv->button);

  if (combo_box->priv->cell_view)
    {
      combo_box->priv->cell_view_frame = gtk_frame_new (NULL);
      gtk_widget_set_parent (combo_box->priv->cell_view_frame,
                             GTK_BIN (combo_box)->child->parent);
      gtk_frame_set_shadow_type (GTK_FRAME (combo_box->priv->cell_view_frame),
                                 GTK_SHADOW_IN);

      gtk_cell_view_set_background_color (GTK_CELL_VIEW (combo_box->priv->cell_view), 
					  &GTK_WIDGET (combo_box)->style->base[GTK_WIDGET_STATE (combo_box)]);

      combo_box->priv->box = gtk_event_box_new ();
      /*
      gtk_event_box_set_visible_window (GTK_EVENT_BOX (combo_box->priv->box), 
					FALSE);
      */

      gtk_container_add (GTK_CONTAINER (combo_box->priv->cell_view_frame),
			 combo_box->priv->box);

      gtk_widget_show_all (combo_box->priv->cell_view_frame);

      g_signal_connect (combo_box->priv->box, "button_press_event",
			G_CALLBACK (gtk_combo_box_list_button_pressed), 
			combo_box);
    }

  combo_box->priv->tree_view = gtk_tree_view_new ();
  sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (combo_box->priv->tree_view));
  gtk_tree_selection_set_mode (sel, GTK_SELECTION_BROWSE);
  gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (combo_box->priv->tree_view),
                                     FALSE);
  /*
  _gtk_tree_view_set_hover_selection (GTK_TREE_VIEW (combo_box->priv->tree_view),
				      TRUE);
  */
  if (combo_box->priv->model)
    gtk_tree_view_set_model (GTK_TREE_VIEW (combo_box->priv->tree_view),
			     combo_box->priv->model);
    
  g_signal_connect (combo_box->priv->tree_view, "button_press_event",
                    G_CALLBACK (gtk_combo_box_list_button_pressed),
                    combo_box);
  g_signal_connect (combo_box->priv->tree_view, "button_release_event",
                    G_CALLBACK (gtk_combo_box_list_button_released),
                    combo_box);
  g_signal_connect (combo_box->priv->tree_view, "key_press_event",
                    G_CALLBACK (gtk_combo_box_list_key_press),
                    combo_box);

  combo_box->priv->column = gtk_tree_view_column_new ();
  gtk_tree_view_append_column (GTK_TREE_VIEW (combo_box->priv->tree_view),
                               combo_box->priv->column);

  /* sync up */
  for (i = combo_box->priv->cells; i; i = i->next)
    {
      GSList *j;
      ComboCellInfo *info = (ComboCellInfo *)i->data;

      if (info->pack == GTK_PACK_START)
        gtk_tree_view_column_pack_start (combo_box->priv->column,
                                         info->cell, info->expand);
      else if (info->pack == GTK_PACK_END)
        gtk_tree_view_column_pack_end (combo_box->priv->column,
                                       info->cell, info->expand);

      for (j = info->attributes; j; j = j->next->next)
        {
          gtk_tree_view_column_add_attribute (combo_box->priv->column,
                                              info->cell,
                                              j->data,
                                              GPOINTER_TO_INT (j->next->data));
        }
    }

  if (combo_box->priv->active_item != -1)
    {
      GtkTreePath *path;

#if GTK_CHECK_VERSION(2,2,0)
      path = gtk_tree_path_new_from_indices (combo_box->priv->active_item, -1);
#else
      char buf[32];
      g_snprintf(buf, sizeof(buf), "%d", combo_box->priv->active_item);
      path = gtk_tree_path_new_from_string(buf);
#endif
      if (path)
        {
          gtk_tree_view_set_cursor (GTK_TREE_VIEW (combo_box->priv->tree_view),
                                    path, NULL, FALSE);
          gtk_tree_path_free (path);
        }
    }

  /* set sample/popup widgets */
  gtk_combo_box_set_popup_widget (combo_box, combo_box->priv->tree_view);

  gtk_widget_show (combo_box->priv->tree_view);
}

static void
gtk_combo_box_list_destroy (GtkComboBox *combo_box)
{
  /* disconnect signals */
  g_signal_handlers_disconnect_matched (combo_box->priv->tree_view,
                                        G_SIGNAL_MATCH_DATA,
                                        0, 0, NULL, NULL, combo_box);
  g_signal_handlers_disconnect_matched (combo_box->priv->button,
                                        G_SIGNAL_MATCH_DATA,
                                        0, 0, NULL,
                                        gtk_combo_box_list_button_pressed,
                                        NULL);
  if (combo_box->priv->box)
    g_signal_handlers_disconnect_matched (combo_box->priv->box,
					  G_SIGNAL_MATCH_DATA,
					  0, 0, NULL,
					  gtk_combo_box_list_button_pressed,
					  NULL);

  /* destroy things (unparent will kill the latest ref from us)
   * last unref on button will destroy the arrow
   */
  gtk_widget_unparent (combo_box->priv->button);
  combo_box->priv->button = NULL;
  combo_box->priv->arrow = NULL;

  if (combo_box->priv->cell_view)
    {
      g_object_set (G_OBJECT (combo_box->priv->cell_view),
                    "background_set", FALSE,
                    NULL);
    }

  if (combo_box->priv->cell_view_frame)
    {
      gtk_widget_unparent (combo_box->priv->cell_view_frame);
      combo_box->priv->cell_view_frame = NULL;
      combo_box->priv->box = NULL;
    }

  gtk_widget_destroy (combo_box->priv->tree_view);

  combo_box->priv->tree_view = NULL;
  combo_box->priv->popup_widget = NULL;
}

/* callbacks */
static void
gtk_combo_box_list_remove_grabs (GtkComboBox *combo_box)
{
  if (combo_box->priv->tree_view &&
      GTK_WIDGET_HAS_GRAB (combo_box->priv->tree_view))
    {
      gtk_grab_remove (combo_box->priv->tree_view);
    }

  if (combo_box->priv->popup_window &&
      GTK_WIDGET_HAS_GRAB (combo_box->priv->popup_window))
    {
      gtk_grab_remove (combo_box->priv->popup_window);
      gdk_keyboard_ungrab (GDK_CURRENT_TIME);
      gdk_pointer_ungrab (GDK_CURRENT_TIME);
    }
}

static gboolean
gtk_combo_box_list_button_pressed (GtkWidget      *widget,
                                   GdkEventButton *event,
                                   gpointer        data)
{
  GtkComboBox *combo_box = GTK_COMBO_BOX (data);

  GtkWidget *ewidget = gtk_get_event_widget ((GdkEvent *)event);

  if (ewidget == combo_box->priv->tree_view)
    return TRUE;

  if ((ewidget != combo_box->priv->button && ewidget != combo_box->priv->box) ||
      gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (combo_box->priv->button)))
    return FALSE;

  gtk_combo_box_popup (combo_box);

  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (combo_box->priv->button),
                                TRUE);

  combo_box->priv->popup_in_progress = TRUE;

  return TRUE;
}

static gboolean
gtk_combo_box_list_button_released (GtkWidget      *widget,
                                    GdkEventButton *event,
                                    gpointer        data)
{
  gboolean ret;
  GtkTreePath *path = NULL;

  GtkComboBox *combo_box = GTK_COMBO_BOX (data);

  gboolean popup_in_progress = FALSE;

  GtkWidget *ewidget = gtk_get_event_widget ((GdkEvent *)event);

  if (combo_box->priv->popup_in_progress)
    {
      popup_in_progress = TRUE;
      combo_box->priv->popup_in_progress = FALSE;
    }

  if (ewidget != combo_box->priv->tree_view)
    {
      if (ewidget == combo_box->priv->button &&
          !popup_in_progress &&
          gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (combo_box->priv->button)))
        {
          gtk_combo_box_popdown (combo_box);
          return TRUE;
        }

      /* released outside treeview */
      if (ewidget != combo_box->priv->button)
        {
          gtk_combo_box_popdown (combo_box);

          return TRUE;
        }

      return FALSE;
    }

  /* select something cool */
  ret = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
                                       event->x, event->y,
                                       &path,
                                       NULL, NULL, NULL);

  if (!ret)
    return TRUE; /* clicked outside window? */

  gtk_combo_box_set_active (combo_box, gtk_tree_path_get_indices (path)[0]);
  gtk_combo_box_popdown (combo_box);

  gtk_tree_path_free (path);

  return TRUE;
}

static gboolean
gtk_combo_box_key_press (GtkWidget   *widget,
			 GdkEventKey *event,
			 gpointer     data)
{
  GtkComboBox *combo_box = GTK_COMBO_BOX (data);
  guint state = event->state & gtk_accelerator_get_default_mod_mask ();
  gint items = 0;
  gint index = gtk_combo_box_get_active (combo_box);
  gint new_index;

  if (combo_box->priv->model)
    items = gtk_tree_model_iter_n_children (combo_box->priv->model, NULL);

  if ((event->keyval == GDK_Down || event->keyval == GDK_KP_Down) && 
      state == GDK_MOD1_MASK)
    {
      gtk_combo_box_popup (combo_box);

      return TRUE;
    }

  switch (event->keyval) 
    {
    case GDK_Down:
    case GDK_KP_Down:
      new_index = index + 1;
      break;
    case GDK_Up:
    case GDK_KP_Up:
      new_index = index - 1;
      break;
    case GDK_Page_Up:
    case GDK_KP_Page_Up:
    case GDK_Home: 
    case GDK_KP_Home:
      new_index = 0;
      break;
    case GDK_Page_Down:
    case GDK_KP_Page_Down:
    case GDK_End: 
    case GDK_KP_End:
      new_index = items - 1;
      break;
    default:
      return FALSE;
    }
      
  if (items > 0)
    gtk_combo_box_set_active (combo_box, CLAMP (new_index, 0, items - 1));

  return TRUE;
}

static gboolean
gtk_combo_box_menu_key_press (GtkWidget   *widget,
			      GdkEventKey *event,
			      gpointer     data)
{
  GtkComboBox *combo_box = GTK_COMBO_BOX (data);
  guint state = event->state & gtk_accelerator_get_default_mod_mask ();

  if ((event->keyval == GDK_Up || event->keyval == GDK_KP_Up) && 
      state == GDK_MOD1_MASK)
    {
      gtk_combo_box_popdown (combo_box);

      return TRUE;
    }
  
  return FALSE;
}

static gboolean
gtk_combo_box_list_key_press (GtkWidget   *widget,
                              GdkEventKey *event,
                              gpointer     data)
{
  GtkComboBox *combo_box = GTK_COMBO_BOX (data);
  guint state = event->state & gtk_accelerator_get_default_mod_mask ();

  if (event->keyval == GDK_Escape ||
      ((event->keyval == GDK_Up || event->keyval == GDK_KP_Up) && 
       state == GDK_MOD1_MASK))
    {
      /* reset active item -- this is incredibly lame and ugly */
      gtk_combo_box_set_active (combo_box,
				gtk_combo_box_get_active (combo_box));
      
      gtk_combo_box_popdown (combo_box);
      
      return TRUE;
    }
    
  if (event->keyval == GDK_Return || event->keyval == GDK_KP_Enter ||
      event->keyval == GDK_space || event->keyval == GDK_KP_Space) 
  {
    gboolean ret = FALSE;
    GtkTreeIter iter;
    GtkTreeModel *model = NULL;
    
    if (combo_box->priv->model)
      {
	GtkTreeSelection *sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (combo_box->priv->tree_view));
    
	ret = gtk_tree_selection_get_selected (sel, &model, &iter);
      }
    if (ret)
      {
	GtkTreePath *path;
	
	path = gtk_tree_model_get_path (model, &iter);
	if (path)
	  {
	    gtk_combo_box_set_active (combo_box, gtk_tree_path_get_indices (path)[0]);
	    gtk_tree_path_free (path);
	  }
      }

    gtk_combo_box_popdown (combo_box);
    
    return TRUE;
  }

  return FALSE;
}

static void
gtk_combo_box_list_row_changed (GtkTreeModel *model,
                                GtkTreePath  *path,
                                GtkTreeIter  *iter,
                                gpointer      data)
{
  GtkComboBox *combo_box = GTK_COMBO_BOX (data);
  gint width;

  width = gtk_combo_box_calc_requested_width (combo_box, path);

  if (width > combo_box->priv->width)
    {
      if (combo_box->priv->cell_view) 
	{
	  gtk_widget_set_size_request (combo_box->priv->cell_view, width, -1);
	  gtk_widget_queue_resize (combo_box->priv->cell_view);
	}
      combo_box->priv->width = width;
    }
}

/*
 * GtkCellLayout implementation
 */
static void
gtk_combo_box_cell_layout_pack_start (GtkCellLayout   *layout,
                                      GtkCellRenderer *cell,
                                      gboolean         expand)
{
  ComboCellInfo *info;
  GtkComboBox *combo_box;
  GtkWidget *menu;

  g_return_if_fail (GTK_IS_COMBO_BOX (layout));
  g_return_if_fail (GTK_IS_CELL_RENDERER (cell));

  combo_box = GTK_COMBO_BOX (layout);

  g_object_ref (G_OBJECT (cell));
  gtk_object_sink (GTK_OBJECT (cell));

  info = g_new0 (ComboCellInfo, 1);
  info->cell = cell;
  info->expand = expand;
  info->pack = GTK_PACK_START;

  combo_box->priv->cells = g_slist_append (combo_box->priv->cells, info);

  if (combo_box->priv->cell_view)
    gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo_box->priv->cell_view),
                                cell, expand);

  if (combo_box->priv->column)
    gtk_tree_view_column_pack_start (combo_box->priv->column, cell, expand);

  menu = combo_box->priv->popup_widget;
  if (GTK_IS_MENU (menu))
    {
      GList *i, *list;

      list = gtk_container_get_children (GTK_CONTAINER (menu));
      for (i = list; i; i = i->next)
        {
          GtkCellView *view;

          if (GTK_IS_CELL_VIEW_MENU_ITEM (i->data))
            view = GTK_CELL_VIEW (GTK_BIN (i->data)->child);
          else
            view = GTK_CELL_VIEW (i->data);

          gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (view), cell, expand);
        }
      g_list_free (list);
    }
}

static void
gtk_combo_box_cell_layout_pack_end (GtkCellLayout   *layout,
                                    GtkCellRenderer *cell,
                                    gboolean         expand)
{
  ComboCellInfo *info;
  GtkComboBox *combo_box;
  GtkWidget *menu;

  g_return_if_fail (GTK_IS_COMBO_BOX (layout));
  g_return_if_fail (GTK_IS_CELL_RENDERER (cell));

  combo_box = GTK_COMBO_BOX (layout);

  g_object_ref (G_OBJECT (cell));
  gtk_object_sink (GTK_OBJECT (cell));

  info = g_new0 (ComboCellInfo, 1);
  info->cell = cell;
  info->expand = expand;
  info->pack = GTK_PACK_END;

  combo_box->priv->cells = g_slist_append (combo_box->priv->cells, info);

  if (combo_box->priv->cell_view)
    gtk_cell_layout_pack_end (GTK_CELL_LAYOUT (combo_box->priv->cell_view),
                              cell, expand);

  if (combo_box->priv->column)
    gtk_tree_view_column_pack_end (combo_box->priv->column, cell, expand);

  menu = combo_box->priv->popup_widget;
  if (GTK_IS_MENU (menu))
    {
      GList *i, *list;

      list = gtk_container_get_children (GTK_CONTAINER (menu));
      for (i = list; i; i = i->next)
        {
          GtkCellView *view;

          if (GTK_IS_CELL_VIEW_MENU_ITEM (i->data))
            view = GTK_CELL_VIEW (GTK_BIN (i->data)->child);
          else
            view = GTK_CELL_VIEW (i->data);

          gtk_cell_layout_pack_end (GTK_CELL_LAYOUT (view), cell, expand);
        }
      g_list_free (list);
    }
}

static void
gtk_combo_box_cell_layout_clear (GtkCellLayout *layout)
{
  GtkWidget *menu;
  GtkComboBox *combo_box;
  GSList *i;
  
  g_return_if_fail (GTK_IS_COMBO_BOX (layout));

  combo_box = GTK_COMBO_BOX (layout);
 
  if (combo_box->priv->cell_view)
    gtk_cell_layout_clear (GTK_CELL_LAYOUT (combo_box->priv->cell_view));

  if (combo_box->priv->column)
    gtk_tree_view_column_clear (combo_box->priv->column);

  for (i = combo_box->priv->cells; i; i = i->next)
    {
     ComboCellInfo *info = (ComboCellInfo *)i->data;

      gtk_combo_box_cell_layout_clear_attributes (layout, info->cell);
      g_object_unref (G_OBJECT (info->cell));
      g_free (info);
      i->data = NULL;
    }
  g_slist_free (combo_box->priv->cells);
  combo_box->priv->cells = NULL;

  menu = combo_box->priv->popup_widget;
  if (GTK_IS_MENU (menu))
    {
      GList *i, *list;

      list = gtk_container_get_children (GTK_CONTAINER (menu));
      for (i = list; i; i = i->next)
        {
          GtkCellView *view;

          if (GTK_IS_CELL_VIEW_MENU_ITEM (i->data))
            view = GTK_CELL_VIEW (GTK_BIN (i->data)->child);
          else
            view = GTK_CELL_VIEW (i->data);

          gtk_cell_layout_clear (GTK_CELL_LAYOUT (view));
        }
      g_list_free (list);
    }
}

static void
gtk_combo_box_cell_layout_add_attribute (GtkCellLayout   *layout,
                                         GtkCellRenderer *cell,
                                         const gchar     *attribute,
                                         gint             column)
{
  ComboCellInfo *info;
  GtkComboBox *combo_box;
  GtkWidget *menu;

  g_return_if_fail (GTK_IS_COMBO_BOX (layout));
  g_return_if_fail (GTK_IS_CELL_RENDERER (cell));

  combo_box = GTK_COMBO_BOX (layout);

  info = gtk_combo_box_get_cell_info (combo_box, cell);

  info->attributes = g_slist_prepend (info->attributes,
                                      GINT_TO_POINTER (column));
  info->attributes = g_slist_prepend (info->attributes,
                                      g_strdup (attribute));

  if (combo_box->priv->cell_view)
    gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (combo_box->priv->cell_view),
                                   cell, attribute, column);

  if (combo_box->priv->column)
    gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (combo_box->priv->column),
                                   cell, attribute, column);

  menu = combo_box->priv->popup_widget;
  if (GTK_IS_MENU (menu))
    {
      GList *i, *list;

      list = gtk_container_get_children (GTK_CONTAINER (menu));
      for (i = list; i; i = i->next)
        {
          GtkCellView *view;

          if (GTK_IS_CELL_VIEW_MENU_ITEM (i->data))
            view = GTK_CELL_VIEW (GTK_BIN (i->data)->child);
          else
            view = GTK_CELL_VIEW (i->data);

          gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (view), cell,
                                         attribute, column);
        }
      g_list_free (list);
    }

  gtk_widget_queue_resize (GTK_WIDGET (combo_box));
}

static void
gtk_combo_box_cell_layout_set_cell_data_func (GtkCellLayout         *layout,
                                              GtkCellRenderer       *cell,
                                              GtkCellLayoutDataFunc  func,
                                              gpointer               func_data,
                                              GDestroyNotify         destroy)
{
  ComboCellInfo *info;
  GtkComboBox *combo_box;
  GtkWidget *menu;

  g_return_if_fail (GTK_IS_COMBO_BOX (layout));

  combo_box = GTK_COMBO_BOX (layout);

  info = gtk_combo_box_get_cell_info (combo_box, cell);
  g_return_if_fail (info != NULL);

  if (info->destroy)
    {
      GDestroyNotify d = info->destroy;

      info->destroy = NULL;
      d (info->func_data);
    }

  info->func = func;
  info->func_data = func_data;
  info->destroy = destroy;

  if (combo_box->priv->cell_view)
    gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (combo_box->priv->cell_view), cell, func, func_data, NULL);

  if (combo_box->priv->column)
    gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (combo_box->priv->column), cell, func, func_data, NULL);

  menu = combo_box->priv->popup_widget;
  if (GTK_IS_MENU (menu))
    {
      GList *i, *list;

      list = gtk_container_get_children (GTK_CONTAINER (menu));
      for (i = list; i; i = i->next)
        {
          GtkCellView *view;

          if (GTK_IS_CELL_VIEW_MENU_ITEM (i->data))
            view = GTK_CELL_VIEW (GTK_BIN (i->data)->child);
          else
            view = GTK_CELL_VIEW (i->data);

          gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (view), cell,
                                              func, func_data, NULL);
        }
      g_list_free (list);
    }

  gtk_widget_queue_resize (GTK_WIDGET (combo_box));
}

static void
gtk_combo_box_cell_layout_clear_attributes (GtkCellLayout   *layout,
                                            GtkCellRenderer *cell)
{
  ComboCellInfo *info;
  GtkComboBox *combo_box;
  GtkWidget *menu;
  GSList *list;

  g_return_if_fail (GTK_IS_COMBO_BOX (layout));
  g_return_if_fail (GTK_IS_CELL_RENDERER (cell));

  combo_box = GTK_COMBO_BOX (layout);

  info = gtk_combo_box_get_cell_info (combo_box, cell);
  if (info)
    {
      list = info->attributes;
      while (list && list->next)
	{
	  g_free (list->data);
	  list = list->next->next;
	}
      g_slist_free (info->attributes);
      info->attributes = NULL;
    }

  if (combo_box->priv->cell_view)
    gtk_cell_layout_clear_attributes (GTK_CELL_LAYOUT (combo_box->priv->cell_view), cell);

  if (combo_box->priv->column)
    gtk_cell_layout_clear_attributes (GTK_CELL_LAYOUT (combo_box->priv->column), cell);

  menu = combo_box->priv->popup_widget;
  if (GTK_IS_MENU (menu))
    {
      GList *i, *list;

      list = gtk_container_get_children (GTK_CONTAINER (menu));
      for (i = list; i; i = i->next)
        {
          GtkCellView *view;

          if (GTK_IS_CELL_VIEW_MENU_ITEM (i->data))
            view = GTK_CELL_VIEW (GTK_BIN (i->data)->child);
          else
            view = GTK_CELL_VIEW (i->data);

          gtk_cell_layout_clear_attributes (GTK_CELL_LAYOUT (view), cell);
        }
      g_list_free (list);
    }

  gtk_widget_queue_resize (GTK_WIDGET (combo_box));
}

static void
gtk_combo_box_cell_layout_reorder (GtkCellLayout   *layout,
                                   GtkCellRenderer *cell,
                                   gint             position)
{
  ComboCellInfo *info;
  GtkComboBox *combo_box;
  GtkWidget *menu;
  GSList *link;

  g_return_if_fail (GTK_IS_COMBO_BOX (layout));
  g_return_if_fail (GTK_IS_CELL_RENDERER (cell));

  combo_box = GTK_COMBO_BOX (layout);

  info = gtk_combo_box_get_cell_info (combo_box, cell);

  g_return_if_fail (info != NULL);
  g_return_if_fail (position >= 0);

  link = g_slist_find (combo_box->priv->cells, info);

  g_return_if_fail (link != NULL);

  combo_box->priv->cells = g_slist_remove_link (combo_box->priv->cells, link);
  combo_box->priv->cells = g_slist_insert (combo_box->priv->cells, info,
                                           position);

  if (combo_box->priv->cell_view)
    gtk_cell_layout_reorder (GTK_CELL_LAYOUT (combo_box->priv->cell_view),
                             cell, position);

  if (combo_box->priv->column)
    gtk_cell_layout_reorder (GTK_CELL_LAYOUT (combo_box->priv->column),
                             cell, position);

  menu = combo_box->priv->popup_widget;
  if (GTK_IS_MENU (menu))
    {
      GList *i, *list;

      list = gtk_container_get_children (GTK_CONTAINER (menu));
      for (i = list; i; i = i->next)
        {
          GtkCellView *view;

          if (GTK_IS_CELL_VIEW_MENU_ITEM (i->data))
            view = GTK_CELL_VIEW (GTK_BIN (i->data)->child);
          else
            view = GTK_CELL_VIEW (i->data);

          gtk_cell_layout_reorder (GTK_CELL_LAYOUT (view), cell, position);
        }
      g_list_free (list);
    }

  gtk_widget_queue_draw (GTK_WIDGET (combo_box));
}

/*
 * public API
 */

/**
 * gtk_combo_box_new:
 *
 * Creates a new empty #GtkComboBox.
 *
 * Return value: A new #GtkComboBox.
 *
 * Since: 2.4
 */
GtkWidget *
gtk_combo_box_new (void)
{
  return GTK_WIDGET (g_object_new (GTK_TYPE_COMBO_BOX, NULL));
}

/**
 * gtk_combo_box_new_with_model:
 * @model: A #GtkTreeModel.
 *
 * Creates a new #GtkComboBox with the model initialized to @model.
 *
 * Return value: A new #GtkComboBox.
 *
 * Since: 2.4
 */
GtkWidget *
gtk_combo_box_new_with_model (GtkTreeModel *model)
{
  GtkComboBox *combo_box;

  g_return_val_if_fail (GTK_IS_TREE_MODEL (model), NULL);

  combo_box = GTK_COMBO_BOX (g_object_new (GTK_TYPE_COMBO_BOX,
                                           "model", model,
                                           NULL));

  return GTK_WIDGET (combo_box);
}

/**
 * gtk_combo_box_set_wrap_width:
 * @combo_box: A #GtkComboBox.
 * @width: Preferred number of columns.
 *
 * Sets the wrap width of @combo_box to be @width. The wrap width is basically
 * the preferred number of columns when you want to the popup to be layed out
 * in a table.
 *
 * Since: 2.4
 */
void
gtk_combo_box_set_wrap_width (GtkComboBox *combo_box,
                              gint         width)
{
  g_return_if_fail (GTK_IS_COMBO_BOX (combo_box));
  g_return_if_fail (width >= 0);

  if (width != combo_box->priv->wrap_width)
    {
      combo_box->priv->wrap_width = width;

      gtk_combo_box_check_appearance (combo_box);
      gtk_combo_box_relayout (combo_box);
      
      g_object_notify (G_OBJECT (combo_box), "wrap_width");
    }
}

/**
 * gtk_combo_box_set_row_span_column:
 * @combo_box: A #GtkComboBox.
 * @row_span: A column in the model passed during construction.
 *
 * Sets the column with row span information for @combo_box to be @row_span.
 * The row span column contains integers which indicate how many rows
 * an item should span.
 *
 * Since: 2.4
 */
void
gtk_combo_box_set_row_span_column (GtkComboBox *combo_box,
                                   gint         row_span)
{
  gint col;

  g_return_if_fail (GTK_IS_COMBO_BOX (combo_box));

  col = gtk_tree_model_get_n_columns (combo_box->priv->model);
  g_return_if_fail (row_span >= 0 && row_span < col);

  if (row_span != combo_box->priv->row_column)
    {
      combo_box->priv->row_column = row_span;
      
      gtk_combo_box_relayout (combo_box);
 
      g_object_notify (G_OBJECT (combo_box), "row_span_column");
    }
}

/**
 * gtk_combo_box_set_column_span_column:
 * @combo_box: A #GtkComboBox.
 * @column_span: A column in the model passed during construction.
 *
 * Sets the column with column span information for @combo_box to be
 * @column_span. The column span column contains integers which indicate
 * how many columns an item should span.
 *
 * Since: 2.4
 */
void
gtk_combo_box_set_column_span_column (GtkComboBox *combo_box,
                                      gint         column_span)
{
  gint col;

  g_return_if_fail (GTK_IS_COMBO_BOX (combo_box));

  col = gtk_tree_model_get_n_columns (combo_box->priv->model);
  g_return_if_fail (column_span >= 0 && column_span < col);

  if (column_span != combo_box->priv->col_column)
    {
      combo_box->priv->col_column = column_span;
      
      gtk_combo_box_relayout (combo_box);

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

/**
 * gtk_combo_box_get_active:
 * @combo_box: A #GtkComboBox.
 *
 * Returns the index of the currently active item, or -1 if there's no
 * active item.
 *
 * Return value: An integer which is the index of the currently active item, or
 * -1 if there's no active item.
 *
 * Since: 2.4
 */
gint
gtk_combo_box_get_active (GtkComboBox *combo_box)
{
  g_return_val_if_fail (GTK_IS_COMBO_BOX (combo_box), 0);

  return combo_box->priv->active_item;
}

/**
 * gtk_combo_box_set_active:
 * @combo_box: A #GtkComboBox.
 * @index_: An index in the model passed during construction, or -1 to have
 * no active item.
 *
 * Sets the active item of @combo_box to be the item at @index.
 *
 * Since: 2.4
 */
void
gtk_combo_box_set_active (GtkComboBox *combo_box,
                          gint         index_)
{
  g_return_if_fail (GTK_IS_COMBO_BOX (combo_box));
  /* -1 means "no item selected" */
  g_return_if_fail (index_ >= -1);

  if (combo_box->priv->active_item == index_)
    return;
  
  gtk_combo_box_set_active_internal (combo_box, index_);
}

static void
gtk_combo_box_set_active_internal (GtkComboBox *combo_box,
				   gint         index)
{
  GtkTreePath *path;

  combo_box->priv->active_item = index;

  if (index == -1)
    {
      if (combo_box->priv->tree_view)
        gtk_tree_selection_unselect_all (gtk_tree_view_get_selection (GTK_TREE_VIEW (combo_box->priv->tree_view)));
      else
        {
          GtkMenu *menu = GTK_MENU (combo_box->priv->popup_widget);

          if (GTK_IS_MENU (menu))
            gtk_menu_set_active (menu, -1);
        }

      if (combo_box->priv->cell_view)
        gtk_cell_view_set_displayed_row (GTK_CELL_VIEW (combo_box->priv->cell_view), NULL);
    }
  else
    {
#if GTK_CHECK_VERSION(2,2,0)
      path = gtk_tree_path_new_from_indices (index, -1);
#else
      char buf[32];
      g_snprintf(buf, sizeof(buf), "%d", index);
      path = gtk_tree_path_new_from_string(buf);
#endif

      if (combo_box->priv->tree_view)
	gtk_tree_view_set_cursor (GTK_TREE_VIEW (combo_box->priv->tree_view), path, NULL, FALSE);
      else
        {
          GtkMenu *menu = GTK_MENU (combo_box->priv->popup_widget);

          if (GTK_IS_MENU (menu))
            gtk_menu_set_active (GTK_MENU (menu), index);
        }

      if (combo_box->priv->cell_view)
	gtk_cell_view_set_displayed_row (GTK_CELL_VIEW (combo_box->priv->cell_view), path);

      gtk_tree_path_free (path);
    }

  g_signal_emit_by_name (combo_box, "changed", NULL, NULL);
}


/**
 * gtk_combo_box_get_active_iter:
 * @combo_box: A #GtkComboBox
 * @iter: The uninitialized #GtkTreeIter.
 * 
 * Sets @iter to point to the current active item, if it exists.
 * 
 * Return value: %TRUE, if @iter was set
 *
 * Since: 2.4
 **/
gboolean
gtk_combo_box_get_active_iter (GtkComboBox     *combo_box,
                               GtkTreeIter     *iter)
{
  GtkTreePath *path;
  gint active;
  gboolean retval;
#if !GTK_CHECK_VERSION(2,2,0)
  char buf[32];
#endif

  g_return_val_if_fail (GTK_IS_COMBO_BOX (combo_box), FALSE);

  active = gtk_combo_box_get_active (combo_box);
  if (active < 0)
    return FALSE;

#if GTK_CHECK_VERSION(2,2,0)
  path = gtk_tree_path_new_from_indices (active, -1);
#else
  g_snprintf(buf, sizeof(buf), "%d", active);
  path = gtk_tree_path_new_from_string(buf);
#endif
  retval = gtk_tree_model_get_iter (gtk_combo_box_get_model (combo_box),
                                    iter, path);
  gtk_tree_path_free (path);

  return retval;
}

/**
 * gtk_combo_box_set_active_iter:
 * @combo_box: A #GtkComboBox
 * @iter: The #GtkTreeIter.
 * 
 * Sets the current active item to be the one referenced by @iter. 
 * @iter must correspond to a path of depth one.
 * 
 * Since: 2.4
 **/
void
gtk_combo_box_set_active_iter (GtkComboBox     *combo_box,
                               GtkTreeIter     *iter)
{
  GtkTreePath *path;

  g_return_if_fail (GTK_IS_COMBO_BOX (combo_box));

  path = gtk_tree_model_get_path (gtk_combo_box_get_model (combo_box), iter);
  g_return_if_fail (path != NULL);
  g_return_if_fail (gtk_tree_path_get_depth (path) == 1);
  
  gtk_combo_box_set_active (combo_box, gtk_tree_path_get_indices (path)[0]);
  gtk_tree_path_free (path);
}

/**
 * gtk_combo_box_set_model:
 * @combo_box: A #GtkComboBox.
 * @model: A #GtkTreeModel.
 *
 * Sets the model used by @combo_box to be @model. Will unset a previously set 
 * model (if applicable). If @model is %NULL, then it will unset the model.
 * 
 * Note that this function does not clear the cell renderers, you have to 
 * call gtk_combo_box_cell_layout_clear() yourself if you need to set up 
 * different cell renderers for the new model.
 *
 * Since: 2.4
 */
void
gtk_combo_box_set_model (GtkComboBox  *combo_box,
                         GtkTreeModel *model)
{
  g_return_if_fail (GTK_IS_COMBO_BOX (combo_box));

  if (!model)
    {
      gtk_combo_box_unset_model (combo_box);
      return;
    }

  g_return_if_fail (GTK_IS_TREE_MODEL (model));

  if (model == combo_box->priv->model)
    return;
  
  if (combo_box->priv->model)
    gtk_combo_box_unset_model (combo_box);

  combo_box->priv->model = model;
  g_object_ref (G_OBJECT (combo_box->priv->model));

  combo_box->priv->inserted_id =
    g_signal_connect (combo_box->priv->model, "row_inserted",
		      G_CALLBACK (gtk_combo_box_model_row_inserted),
		      combo_box);
  combo_box->priv->deleted_id =
    g_signal_connect (combo_box->priv->model, "row_deleted",
		      G_CALLBACK (gtk_combo_box_model_row_deleted),
		      combo_box);
  combo_box->priv->reordered_id =
    g_signal_connect (combo_box->priv->model, "rows_reordered",
		      G_CALLBACK (gtk_combo_box_model_rows_reordered),
		      combo_box);
  combo_box->priv->changed_id =
    g_signal_connect (combo_box->priv->model, "row_changed",
		      G_CALLBACK (gtk_combo_box_model_row_changed),
		      combo_box);
      
  if (combo_box->priv->tree_view)
    {
      /* list mode */
      gtk_tree_view_set_model (GTK_TREE_VIEW (combo_box->priv->tree_view),
                               combo_box->priv->model);
    }
  else
    {
      /* menu mode */
      if (combo_box->priv->popup_widget)
	gtk_combo_box_menu_fill (combo_box);

    }

  if (combo_box->priv->cell_view)
    gtk_cell_view_set_model (GTK_CELL_VIEW (combo_box->priv->cell_view),
                             combo_box->priv->model);
}

/**
 * gtk_combo_box_get_model
 * @combo_box: A #GtkComboBox.
 *
 * Returns the #GtkTreeModel which is acting as data source for @combo_box.
 *
 * Return value: A #GtkTreeModel which was passed during construction.
 *
 * Since: 2.4
 */
GtkTreeModel *
gtk_combo_box_get_model (GtkComboBox *combo_box)
{
  g_return_val_if_fail (GTK_IS_COMBO_BOX (combo_box), NULL);

  return combo_box->priv->model;
}


/* convenience API for simple text combos */

/**
 * gtk_combo_box_new_text:
 *
 * Convenience function which constructs a new text combo box, which is a
 * #GtkComboBox just displaying strings. If you use this function to create
 * a text combo box, you should only manipulate its data source with the
 * following convenience functions: gtk_combo_box_append_text(),
 * gtk_combo_box_insert_text(), gtk_combo_box_prepend_text() and
 * gtk_combo_box_remove_text().
 *
 * Return value: A new text combo box.
 *
 * Since: 2.4
 */
GtkWidget *
gtk_combo_box_new_text (void)
{
  GtkWidget *combo_box;
  GtkCellRenderer *cell;
  GtkListStore *store;

  store = gtk_list_store_new (1, G_TYPE_STRING);
  combo_box = gtk_combo_box_new_with_model (GTK_TREE_MODEL (store));
  g_object_unref (store);

  cell = gtk_cell_renderer_text_new ();
  gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo_box), cell, TRUE);
  gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combo_box), cell,
                                  "text", 0,
                                  NULL);

  return combo_box;
}

/**
 * gtk_combo_box_append_text:
 * @combo_box: A #GtkComboBox constructed using gtk_combo_box_new_text().
 * @text: A string.
 *
 * Appends @string to the list of strings stored in @combo_box. Note that
 * you can only use this function with combo boxes constructed with
 * gtk_combo_box_new_text().
 *
 * Since: 2.4
 */
void
gtk_combo_box_append_text (GtkComboBox *combo_box,
                           const gchar *text)
{
  GtkTreeIter iter;
  GtkListStore *store;

  g_return_if_fail (GTK_IS_COMBO_BOX (combo_box));
  g_return_if_fail (GTK_IS_LIST_STORE (combo_box->priv->model));
  g_return_if_fail (text != NULL);

  store = GTK_LIST_STORE (combo_box->priv->model);

  gtk_list_store_append (store, &iter);
  gtk_list_store_set (store, &iter, 0, text, -1);
}

/**
 * gtk_combo_box_insert_text:
 * @combo_box: A #GtkComboBox constructed using gtk_combo_box_new_text().
 * @position: An index to insert @text.
 * @text: A string.
 *
 * Inserts @string at @position in the list of strings stored in @combo_box.
 * Note that you can only use this function with combo boxes constructed
 * with gtk_combo_box_new_text().
 *
 * Since: 2.4
 */
void
gtk_combo_box_insert_text (GtkComboBox *combo_box,
                           gint         position,
                           const gchar *text)
{
  GtkTreeIter iter;
  GtkListStore *store;

  g_return_if_fail (GTK_IS_COMBO_BOX (combo_box));
  g_return_if_fail (GTK_IS_LIST_STORE (combo_box->priv->model));
  g_return_if_fail (position >= 0);
  g_return_if_fail (text != NULL);

  store = GTK_LIST_STORE (combo_box->priv->model);

  gtk_list_store_insert (store, &iter, position);
  gtk_list_store_set (store, &iter, 0, text, -1);
}

/**
 * gtk_combo_box_prepend_text:
 * @combo_box: A #GtkComboBox constructed with gtk_combo_box_new_text().
 * @text: A string.
 *
 * Prepends @string to the list of strings stored in @combo_box. Note that
 * you can only use this function with combo boxes constructed with
 * gtk_combo_box_new_text().
 *
 * Since: 2.4
 */
void
gtk_combo_box_prepend_text (GtkComboBox *combo_box,
                            const gchar *text)
{
  GtkTreeIter iter;
  GtkListStore *store;

  g_return_if_fail (GTK_IS_COMBO_BOX (combo_box));
  g_return_if_fail (GTK_IS_LIST_STORE (combo_box->priv->model));
  g_return_if_fail (text != NULL);

  store = GTK_LIST_STORE (combo_box->priv->model);

  gtk_list_store_prepend (store, &iter);
  gtk_list_store_set (store, &iter, 0, text, -1);
}

/**
 * gtk_combo_box_remove_text:
 * @combo_box: A #GtkComboBox constructed with gtk_combo_box_new_text().
 * @position: Index of the item to remove.
 *
 * Removes the string at @position from @combo_box. Note that you can only use
 * this function with combo boxes constructed with gtk_combo_box_new_text().
 *
 * Since: 2.4
 */
void
gtk_combo_box_remove_text (GtkComboBox *combo_box,
                           gint         position)
{
  GtkTreeIter iter;
  GtkListStore *store;

  g_return_if_fail (GTK_IS_COMBO_BOX (combo_box));
  g_return_if_fail (GTK_IS_LIST_STORE (combo_box->priv->model));
  g_return_if_fail (position >= 0);

  store = GTK_LIST_STORE (combo_box->priv->model);

  if (gtk_tree_model_iter_nth_child (combo_box->priv->model, &iter,
                                     NULL, position))
    gtk_list_store_remove (store, &iter);
}


static gboolean
gtk_combo_box_mnemonic_activate (GtkWidget *widget,
				 gboolean   group_cycling)
{
  GtkComboBox *combo_box = GTK_COMBO_BOX (widget);

  gtk_widget_grab_focus (combo_box->priv->button);

  return TRUE;
}

static void
gtk_combo_box_destroy (GtkObject *object)
{
  GtkComboBox *combo_box = GTK_COMBO_BOX (object);

  gtk_combo_box_popdown (combo_box); 

  combo_box->priv->destroying = 1;

  GTK_OBJECT_CLASS (parent_class)->destroy (object);
  combo_box->priv->cell_view = NULL;

  combo_box->priv->destroying = 0;
}

static void
gtk_combo_box_finalize (GObject *object)
{
  GtkComboBox *combo_box = GTK_COMBO_BOX (object);
  GSList *i;
  
  if (GTK_IS_MENU (combo_box->priv->popup_widget))
    {
      gtk_combo_box_menu_destroy (combo_box);
      gtk_menu_detach (GTK_MENU (combo_box->priv->popup_widget));
      combo_box->priv->popup_widget = NULL;
    }
  
  if (GTK_IS_TREE_VIEW (combo_box->priv->tree_view))
    gtk_combo_box_list_destroy (combo_box);

  if (combo_box->priv->popup_window)
    gtk_widget_destroy (combo_box->priv->popup_window);

  gtk_combo_box_unset_model (combo_box);

  for (i = combo_box->priv->cells; i; i = i->next)
    {
      ComboCellInfo *info = (ComboCellInfo *)i->data;
      GSList *list = info->attributes;

      if (info->destroy)
	info->destroy (info->func_data);

      while (list && list->next)
	{
	  g_free (list->data);
	  list = list->next->next;
	}
      g_slist_free (info->attributes);

      g_object_unref (G_OBJECT (info->cell));
      g_free (info);
    }
   g_slist_free (combo_box->priv->cells);

   g_free (combo_box->priv);

   G_OBJECT_CLASS (parent_class)->finalize (object);
}


/**
 * Code below this point has been pulled in from gtkmenu.c in 2.4.14
 * and is needed to provide gtk_menu_attach()
 */

typedef struct
{
  gint left_attach;
  gint right_attach;
  gint top_attach;
  gint bottom_attach;
  gint effective_left_attach;
  gint effective_right_attach;
  gint effective_top_attach;
  gint effective_bottom_attach;
} AttachInfo;

#define ATTACH_INFO_KEY "gtk-menu-child-attach-info-key"

static AttachInfo *
get_attach_info (GtkWidget *child)
{
  GObject *object = G_OBJECT (child);
  AttachInfo *ai = g_object_get_data (object, ATTACH_INFO_KEY);
                                                                                                   
  if (!ai)
    {
      ai = g_new0 (AttachInfo, 1);
      g_object_set_data_full (object, ATTACH_INFO_KEY, ai, g_free);
    }
                                                                                                   
  return ai;
}

/**
 * gtk_menu_attach:
 * @menu: a #GtkMenu.
 * @child: a #GtkMenuItem.
 * @left_attach: The column number to attach the left side of the item to.
 * @right_attach: The column number to attach the right side of the item to.
 * @top_attach: The row number to attach the top of the item to.
 * @bottom_attach: The row number to attach the bottom of the item to.
 *
 * Adds a new #GtkMenuItem to a (table) menu. The number of 'cells' that
 * an item will occupy is specified by @left_attach, @right_attach,
 * @top_attach and @bottom_attach. These each represent the leftmost,
 * rightmost, uppermost and lower column and row numbers of the table.
 * (Columns and rows are indexed from zero).
 *
 * Note that this function is not related to gtk_menu_detach().
 *
 * Since: 2.4
 **/
static void
gtk_menu_attach (GtkMenu   *menu,
                 GtkWidget *child,
                 guint      left_attach,
                 guint      right_attach,
                 guint      top_attach,
                 guint      bottom_attach)
{
  GtkMenuShell *menu_shell;
  
  g_return_if_fail (GTK_IS_MENU (menu));
  g_return_if_fail (GTK_IS_MENU_ITEM (child));
  g_return_if_fail (child->parent == NULL || 
		    child->parent == GTK_WIDGET (menu));
  g_return_if_fail (left_attach < right_attach);
  g_return_if_fail (top_attach < bottom_attach);

  menu_shell = GTK_MENU_SHELL (menu);
  
  if (!child->parent)
    {
      AttachInfo *ai = get_attach_info (child);
      
      ai->left_attach = left_attach;
      ai->right_attach = right_attach;
      ai->top_attach = top_attach;
      ai->bottom_attach = bottom_attach;
      
      menu_shell->children = g_list_append (menu_shell->children, child);

      gtk_widget_set_parent (child, GTK_WIDGET (menu));

      /*
      menu_queue_resize (menu);
      */
    }
  else
    {
      gtk_container_child_set (GTK_CONTAINER (child->parent), child,
			       "left_attach",   left_attach,
			       "right_attach",  right_attach,
			       "top_attach",    top_attach,
			       "bottom_attach", bottom_attach,
			       NULL);
    }
}
#endif /* Gtk 2.4 */