diff options
author | Tristan Van Berkom <tristan.van.berkom@gmail.com> | 2010-11-18 17:33:23 +0900 |
---|---|---|
committer | Tristan Van Berkom <tristan.van.berkom@gmail.com> | 2011-01-04 23:37:06 +0900 |
commit | 26c3f1a26d1f282743513fb437eb3e79b2436690 (patch) | |
tree | 5adf4f5a21a842cf30a17c08c7b7d3e460d587bf /gtk/gtktreemenu.c | |
parent | f15a589651889fae1c2436f052b9f960d46b482a (diff) | |
download | gtk+-26c3f1a26d1f282743513fb437eb3e79b2436690.tar.gz |
Adding GtkTreeMenu class.
Added GtkTreeMenu class to automatically render
a GtkTreeModel into a GtkMenu hierarchy (will be
used by GtkComboBox for its dropdown menus). Included
an accompanying testcase tests/testtreemenu
Diffstat (limited to 'gtk/gtktreemenu.c')
-rw-r--r-- | gtk/gtktreemenu.c | 909 |
1 files changed, 909 insertions, 0 deletions
diff --git a/gtk/gtktreemenu.c b/gtk/gtktreemenu.c new file mode 100644 index 0000000000..cff45419d2 --- /dev/null +++ b/gtk/gtktreemenu.c @@ -0,0 +1,909 @@ +/* gtktreemenu.c + * + * Copyright (C) 2010 Openismus GmbH + * + * Authors: + * Tristan Van Berkom <tristanvb@openismus.com> + * + * 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 "gtkintl.h" +#include "gtktreemenu.h" +#include "gtkmenuitem.h" +#include "gtkseparatormenuitem.h" +#include "gtkcellareabox.h" +#include "gtkcellareacontext.h" +#include "gtkcelllayout.h" +#include "gtkcellview.h" +#include "gtkprivate.h" + + +/* GObjectClass */ +static GObject *gtk_tree_menu_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_properties); +static void gtk_tree_menu_dispose (GObject *object); +static void gtk_tree_menu_finalize (GObject *object); +static void gtk_tree_menu_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec); +static void gtk_tree_menu_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec); + +/* GtkWidgetClass */ +static void gtk_tree_menu_get_preferred_width (GtkWidget *widget, + gint *minimum_size, + gint *natural_size); +static void gtk_tree_menu_get_preferred_height (GtkWidget *widget, + gint *minimum_size, + gint *natural_size); +static void gtk_tree_menu_size_allocate (GtkWidget *widget, + GtkAllocation *allocation); + +/* GtkCellLayoutIface */ +static void gtk_tree_menu_cell_layout_init (GtkCellLayoutIface *iface); +static void gtk_tree_menu_cell_layout_pack_start (GtkCellLayout *layout, + GtkCellRenderer *cell, + gboolean expand); +static void gtk_tree_menu_cell_layout_pack_end (GtkCellLayout *layout, + GtkCellRenderer *cell, + gboolean expand); +static GList *gtk_tree_menu_cell_layout_get_cells (GtkCellLayout *layout); +static void gtk_tree_menu_cell_layout_clear (GtkCellLayout *layout); +static void gtk_tree_menu_cell_layout_add_attribute (GtkCellLayout *layout, + GtkCellRenderer *cell, + const gchar *attribute, + gint column); +static void gtk_tree_menu_cell_layout_set_cell_data_func (GtkCellLayout *layout, + GtkCellRenderer *cell, + GtkCellLayoutDataFunc func, + gpointer func_data, + GDestroyNotify destroy); +static void gtk_tree_menu_cell_layout_clear_attributes (GtkCellLayout *layout, + GtkCellRenderer *cell); +static void gtk_tree_menu_cell_layout_reorder (GtkCellLayout *layout, + GtkCellRenderer *cell, + gint position); +static GtkCellArea *gtk_tree_menu_cell_layout_get_area (GtkCellLayout *layout); + + +/* TreeModel/DrawingArea callbacks and building menus/submenus */ +static void gtk_tree_menu_populate (GtkTreeMenu *menu); +static GtkWidget *gtk_tree_menu_create_item (GtkTreeMenu *menu, + GtkTreeIter *iter); +static void gtk_tree_menu_set_area (GtkTreeMenu *menu, + GtkCellArea *area); +static void queue_resize_all (GtkWidget *menu); +static void context_size_changed_cb (GtkCellAreaContext *context, + GParamSpec *pspec, + GtkWidget *menu); + +struct _GtkTreeMenuPrivate +{ + /* TreeModel and parent for this menu */ + GtkTreeModel *model; + GtkTreeRowReference *root; + + /* CellArea and context for this menu */ + GtkCellArea *area; + GtkCellAreaContext *context; + + gint last_alloc_width; + gint last_alloc_height; + + /* Signals */ + gulong size_changed_id; + + /* Row separators */ + GtkTreeViewRowSeparatorFunc row_separator_func; + gpointer row_separator_data; + GDestroyNotify row_separator_destroy; +}; + +enum { + PROP_0, + PROP_MODEL, + PROP_ROOT, + PROP_CELL_AREA +}; + +G_DEFINE_TYPE_WITH_CODE (GtkTreeMenu, gtk_tree_menu, GTK_TYPE_MENU, + G_IMPLEMENT_INTERFACE (GTK_TYPE_CELL_LAYOUT, + gtk_tree_menu_cell_layout_init)); + +static void +gtk_tree_menu_init (GtkTreeMenu *menu) +{ + GtkTreeMenuPrivate *priv; + + menu->priv = G_TYPE_INSTANCE_GET_PRIVATE (menu, + GTK_TYPE_TREE_MENU, + GtkTreeMenuPrivate); + priv = menu->priv; +} + +static void +gtk_tree_menu_class_init (GtkTreeMenuClass *class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (class); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class); + + object_class->constructor = gtk_tree_menu_constructor; + object_class->dispose = gtk_tree_menu_dispose; + object_class->finalize = gtk_tree_menu_finalize; + object_class->set_property = gtk_tree_menu_set_property; + object_class->get_property = gtk_tree_menu_get_property; + + widget_class->get_preferred_width = gtk_tree_menu_get_preferred_width; + widget_class->get_preferred_height = gtk_tree_menu_get_preferred_height; + widget_class->size_allocate = gtk_tree_menu_size_allocate; + + g_object_class_install_property (object_class, + PROP_MODEL, + g_param_spec_object ("model", + P_("TreeMenu model"), + P_("The model for the tree menu"), + GTK_TYPE_TREE_MODEL, + GTK_PARAM_READWRITE)); + + g_object_class_install_property (object_class, + PROP_ROOT, + g_param_spec_boxed ("root", + P_("TreeMenu root row"), + P_("The TreeMenu will display children of the " + "specified root"), + GTK_TYPE_TREE_ROW_REFERENCE, + GTK_PARAM_READWRITE)); + + g_object_class_install_property (object_class, + PROP_CELL_AREA, + g_param_spec_object ("cell-area", + P_("Cell Area"), + P_("The GtkCellArea used to layout cells"), + GTK_TYPE_CELL_AREA, + GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + + g_type_class_add_private (object_class, sizeof (GtkTreeMenuPrivate)); +} + +/**************************************************************** + * GObjectClass * + ****************************************************************/ +static GObject * +gtk_tree_menu_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_properties) +{ + GObject *object; + GtkTreeMenu *menu; + GtkTreeMenuPrivate *priv; + + object = G_OBJECT_CLASS (gtk_tree_menu_parent_class)->constructor + (type, n_construct_properties, construct_properties); + + menu = GTK_TREE_MENU (object); + priv = menu->priv; + + if (!priv->area) + { + GtkCellArea *area = gtk_cell_area_box_new (); + + gtk_tree_menu_set_area (menu, area); + } + + priv->context = gtk_cell_area_create_context (priv->area); + priv->size_changed_id = + g_signal_connect (priv->context, "notify", + G_CALLBACK (context_size_changed_cb), menu); + + + return object; +} + +static void +gtk_tree_menu_dispose (GObject *object) +{ + GtkTreeMenu *menu; + GtkTreeMenuPrivate *priv; + + menu = GTK_TREE_MENU (object); + priv = menu->priv; + + gtk_tree_menu_set_model (menu, NULL); + gtk_tree_menu_set_area (menu, NULL); + + if (priv->context) + { + /* Disconnect signals */ + g_signal_handler_disconnect (priv->context, priv->size_changed_id); + + g_object_unref (priv->context); + priv->context = NULL; + priv->size_changed_id = 0; + } + + G_OBJECT_CLASS (gtk_tree_menu_parent_class)->dispose (object); +} + +static void +gtk_tree_menu_finalize (GObject *object) +{ + + G_OBJECT_CLASS (gtk_tree_menu_parent_class)->finalize (object); +} + +static void +gtk_tree_menu_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GtkTreeMenu *menu = GTK_TREE_MENU (object); + + switch (prop_id) + { + case PROP_MODEL: + gtk_tree_menu_set_model (menu, g_value_get_object (value)); + break; + + case PROP_ROOT: + gtk_tree_menu_set_root (menu, g_value_get_boxed (value)); + break; + + case PROP_CELL_AREA: + /* Construct-only, can only be assigned once */ + gtk_tree_menu_set_area (menu, (GtkCellArea *)g_value_get_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gtk_tree_menu_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GtkTreeMenu *menu = GTK_TREE_MENU (object); + GtkTreeMenuPrivate *priv = menu->priv; + + switch (prop_id) + { + case PROP_MODEL: + g_value_set_object (value, priv->model); + break; + + case PROP_ROOT: + g_value_set_boxed (value, priv->root); + break; + + case PROP_CELL_AREA: + g_value_set_object (value, priv->area); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +/**************************************************************** + * GtkWidgetClass * + ****************************************************************/ + +/* We tell all the menu items to reserve space for the submenu + * indicator if there is at least one submenu, this way we ensure + * that every internal cell area gets allocated the + * same width (and requested height for the same appropriate width). + */ +static void +sync_reserve_submenu_size (GtkTreeMenu *menu) +{ + GList *children, *l; + gboolean has_submenu = FALSE; + + children = gtk_container_get_children (GTK_CONTAINER (menu)); + for (l = children; l; l = l->next) + { + GtkMenuItem *item = l->data; + + if (gtk_menu_item_get_submenu (item) != NULL) + { + has_submenu = TRUE; + break; + } + } + + for (l = children; l; l = l->next) + { + GtkMenuItem *item = l->data; + + gtk_menu_item_set_reserve_indicator (item, has_submenu); + } + + g_list_free (children); +} + +static void +gtk_tree_menu_get_preferred_width (GtkWidget *widget, + gint *minimum_size, + gint *natural_size) +{ + GtkTreeMenu *menu = GTK_TREE_MENU (widget); + GtkTreeMenuPrivate *priv = menu->priv; + GtkTreePath *path = NULL; + GtkTreeIter iter; + gboolean valid = FALSE; + + g_signal_handler_block (priv->context, priv->size_changed_id); + + /* Before chaining up to the parent class and requesting the + * menu item/cell view sizes, we need to request the size of + * each row for this menu and make sure all the cellviews + * request enough space + */ + gtk_cell_area_context_flush_preferred_width (priv->context); + + sync_reserve_submenu_size (menu); + + if (priv->model) + { + if (priv->root) + path = gtk_tree_row_reference_get_path (priv->root); + + if (path) + { + GtkTreeIter parent; + + if (gtk_tree_model_get_iter (priv->model, &parent, path)) + valid = gtk_tree_model_iter_children (priv->model, &iter, &parent); + + gtk_tree_path_free (path); + } + else + valid = gtk_tree_model_iter_children (priv->model, &iter, NULL); + + while (valid) + { + gboolean is_separator = FALSE; + + if (priv->row_separator_func) + is_separator = + priv->row_separator_func (priv->model, &iter, priv->row_separator_data); + + if (!is_separator) + { + gtk_cell_area_apply_attributes (priv->area, priv->model, &iter, FALSE, FALSE); + gtk_cell_area_get_preferred_width (priv->area, priv->context, widget, NULL, NULL); + } + + valid = gtk_tree_model_iter_next (priv->model, &iter); + } + } + + gtk_cell_area_context_sum_preferred_width (priv->context); + + g_signal_handler_unblock (priv->context, priv->size_changed_id); + + /* Now that we've requested all the row's and updated priv->context properly, we can go ahead + * and calculate the sizes by requesting the menu items and thier cell views */ + GTK_WIDGET_CLASS (gtk_tree_menu_parent_class)->get_preferred_width (widget, minimum_size, natural_size); +} + +static void +gtk_tree_menu_get_preferred_height (GtkWidget *widget, + gint *minimum_size, + gint *natural_size) +{ + GtkTreeMenu *menu = GTK_TREE_MENU (widget); + GtkTreeMenuPrivate *priv = menu->priv; + GtkTreePath *path = NULL; + GtkTreeIter iter; + gboolean valid = FALSE; + + g_signal_handler_block (priv->context, priv->size_changed_id); + + /* Before chaining up to the parent class and requesting the + * menu item/cell view sizes, we need to request the size of + * each row for this menu and make sure all the cellviews + * request enough space + */ + gtk_cell_area_context_flush_preferred_height (priv->context); + + sync_reserve_submenu_size (menu); + + if (priv->model) + { + if (priv->root) + path = gtk_tree_row_reference_get_path (priv->root); + + if (path) + { + GtkTreeIter parent; + + if (gtk_tree_model_get_iter (priv->model, &parent, path)) + valid = gtk_tree_model_iter_children (priv->model, &iter, &parent); + + gtk_tree_path_free (path); + } + else + valid = gtk_tree_model_iter_children (priv->model, &iter, NULL); + + while (valid) + { + gboolean is_separator = FALSE; + + if (priv->row_separator_func) + is_separator = + priv->row_separator_func (priv->model, &iter, priv->row_separator_data); + + if (!is_separator) + { + gtk_cell_area_apply_attributes (priv->area, priv->model, &iter, FALSE, FALSE); + gtk_cell_area_get_preferred_height (priv->area, priv->context, widget, NULL, NULL); + } + + valid = gtk_tree_model_iter_next (priv->model, &iter); + } + } + + gtk_cell_area_context_sum_preferred_height (priv->context); + + g_signal_handler_unblock (priv->context, priv->size_changed_id); + + /* Now that we've requested all the row's and updated priv->context properly, we can go ahead + * and calculate the sizes by requesting the menu items and thier cell views */ + GTK_WIDGET_CLASS (gtk_tree_menu_parent_class)->get_preferred_height (widget, minimum_size, natural_size); +} + +static void +gtk_tree_menu_size_allocate (GtkWidget *widget, + GtkAllocation *allocation) +{ + GtkTreeMenu *menu = GTK_TREE_MENU (widget); + GtkTreeMenuPrivate *priv = menu->priv; + gint new_width, new_height; + + /* flush the context allocation */ + gtk_cell_area_context_flush_allocation (priv->context); + + /* Leave it to the first cell area to allocate the size of priv->context, since + * we configure the menu to allocate all children the same width this should work fine */ + GTK_WIDGET_CLASS (gtk_tree_menu_parent_class)->size_allocate (widget, allocation); + + /* In alot of cases the menu gets allocated while the children dont need + * any reallocation, in this case we need to restore the context allocation */ + gtk_cell_area_context_get_allocation (priv->context, &new_width, &new_height); + + if (new_width <= 0 && new_height <= 0) + { + gtk_cell_area_context_allocate_width (priv->context, priv->last_alloc_width); + gtk_cell_area_context_allocate_height (priv->context, priv->last_alloc_height); + } + + /* Save the allocation for the next round */ + gtk_cell_area_context_get_allocation (priv->context, + &priv->last_alloc_width, + &priv->last_alloc_height); +} + +/**************************************************************** + * GtkCellLayoutIface * + ****************************************************************/ +/* Just forward all the GtkCellLayoutIface methods to the + * underlying GtkCellArea + */ +static void +gtk_tree_menu_cell_layout_init (GtkCellLayoutIface *iface) +{ + iface->pack_start = gtk_tree_menu_cell_layout_pack_start; + iface->pack_end = gtk_tree_menu_cell_layout_pack_end; + iface->get_cells = gtk_tree_menu_cell_layout_get_cells; + iface->clear = gtk_tree_menu_cell_layout_clear; + iface->add_attribute = gtk_tree_menu_cell_layout_add_attribute; + iface->set_cell_data_func = gtk_tree_menu_cell_layout_set_cell_data_func; + iface->clear_attributes = gtk_tree_menu_cell_layout_clear_attributes; + iface->reorder = gtk_tree_menu_cell_layout_reorder; + iface->get_area = gtk_tree_menu_cell_layout_get_area; +} + +static void +gtk_tree_menu_cell_layout_pack_start (GtkCellLayout *layout, + GtkCellRenderer *cell, + gboolean expand) +{ + GtkTreeMenu *menu = GTK_TREE_MENU (layout); + GtkTreeMenuPrivate *priv = menu->priv; + + gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (priv->area), cell, expand); +} + +static void +gtk_tree_menu_cell_layout_pack_end (GtkCellLayout *layout, + GtkCellRenderer *cell, + gboolean expand) +{ + GtkTreeMenu *menu = GTK_TREE_MENU (layout); + GtkTreeMenuPrivate *priv = menu->priv; + + gtk_cell_layout_pack_end (GTK_CELL_LAYOUT (priv->area), cell, expand); +} + +static GList * +gtk_tree_menu_cell_layout_get_cells (GtkCellLayout *layout) +{ + GtkTreeMenu *menu = GTK_TREE_MENU (layout); + GtkTreeMenuPrivate *priv = menu->priv; + + return gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (priv->area)); +} + +static void +gtk_tree_menu_cell_layout_clear (GtkCellLayout *layout) +{ + GtkTreeMenu *menu = GTK_TREE_MENU (layout); + GtkTreeMenuPrivate *priv = menu->priv; + + gtk_cell_layout_clear (GTK_CELL_LAYOUT (priv->area)); +} + +static void +gtk_tree_menu_cell_layout_add_attribute (GtkCellLayout *layout, + GtkCellRenderer *cell, + const gchar *attribute, + gint column) +{ + GtkTreeMenu *menu = GTK_TREE_MENU (layout); + GtkTreeMenuPrivate *priv = menu->priv; + + gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (priv->area), cell, attribute, column); +} + +static void +gtk_tree_menu_cell_layout_set_cell_data_func (GtkCellLayout *layout, + GtkCellRenderer *cell, + GtkCellLayoutDataFunc func, + gpointer func_data, + GDestroyNotify destroy) +{ + GtkTreeMenu *menu = GTK_TREE_MENU (layout); + GtkTreeMenuPrivate *priv = menu->priv; + + gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (priv->area), cell, func, func_data, destroy); +} + +static void +gtk_tree_menu_cell_layout_clear_attributes (GtkCellLayout *layout, + GtkCellRenderer *cell) +{ + GtkTreeMenu *menu = GTK_TREE_MENU (layout); + GtkTreeMenuPrivate *priv = menu->priv; + + gtk_cell_layout_clear_attributes (GTK_CELL_LAYOUT (priv->area), cell); +} + +static void +gtk_tree_menu_cell_layout_reorder (GtkCellLayout *layout, + GtkCellRenderer *cell, + gint position) +{ + GtkTreeMenu *menu = GTK_TREE_MENU (layout); + GtkTreeMenuPrivate *priv = menu->priv; + + gtk_cell_layout_reorder (GTK_CELL_LAYOUT (priv->area), cell, position); +} + +static GtkCellArea * +gtk_tree_menu_cell_layout_get_area (GtkCellLayout *layout) +{ + GtkTreeMenu *menu = GTK_TREE_MENU (layout); + GtkTreeMenuPrivate *priv = menu->priv; + + return priv->area; +} + + +/**************************************************************** + * TreeModel callbacks/populating menus * + ****************************************************************/ +static void +context_size_changed_cb (GtkCellAreaContext *context, + GParamSpec *pspec, + GtkWidget *menu) +{ + if (!strcmp (pspec->name, "minimum-width") || + !strcmp (pspec->name, "natural-width") || + !strcmp (pspec->name, "minimum-height") || + !strcmp (pspec->name, "natural-height")) + queue_resize_all (menu); +} + +static void +queue_resize_all (GtkWidget *menu) +{ + GList *children, *l; + + children = gtk_container_get_children (GTK_CONTAINER (menu)); + for (l = children; l; l = l->next) + { + GtkWidget *widget = l->data; + + gtk_widget_queue_resize (widget); + } + + g_list_free (children); + + gtk_widget_queue_resize (menu); +} + + +static void +gtk_tree_menu_set_area (GtkTreeMenu *menu, + GtkCellArea *area) +{ + GtkTreeMenuPrivate *priv = menu->priv; + + if (priv->area) + g_object_unref (area); + + priv->area = area; + + if (priv->area) + g_object_ref_sink (area); +} + + +static GtkWidget * +gtk_tree_menu_create_item (GtkTreeMenu *menu, + GtkTreeIter *iter) +{ + GtkTreeMenuPrivate *priv = menu->priv; + GtkWidget *item, *view; + GtkTreePath *path; + + view = gtk_cell_view_new_with_context (priv->area, priv->context); + item = gtk_menu_item_new (); + gtk_widget_show (view); + gtk_widget_show (item); + + path = gtk_tree_model_get_path (priv->model, iter); + + gtk_cell_view_set_model (GTK_CELL_VIEW (view), priv->model); + gtk_cell_view_set_displayed_row (GTK_CELL_VIEW (view), path); + + gtk_tree_path_free (path); + + gtk_widget_show (view); + gtk_container_add (GTK_CONTAINER (item), view); + + return item; +} + +static void +gtk_tree_menu_populate (GtkTreeMenu *menu) +{ + GtkTreeMenuPrivate *priv = menu->priv; + GtkTreePath *path = NULL; + GtkTreeIter parent; + GtkTreeIter iter; + gboolean valid = FALSE; + GtkWidget *menu_item; + + if (!priv->model) + return; + + if (priv->root) + path = gtk_tree_row_reference_get_path (priv->root); + + if (path) + { + if (gtk_tree_model_get_iter (priv->model, &parent, path)) + valid = gtk_tree_model_iter_children (priv->model, &iter, &parent); + + gtk_tree_path_free (path); + } + else + valid = gtk_tree_model_iter_children (priv->model, &iter, NULL); + + /* Create a menu item for every row at the current depth, add a GtkTreeMenu + * submenu for iters/items that have children */ + while (valid) + { + gboolean is_separator = FALSE; + + if (priv->row_separator_func) + is_separator = + priv->row_separator_func (priv->model, &iter, + priv->row_separator_data); + + if (is_separator) + menu_item = gtk_separator_menu_item_new (); + else + { + menu_item = gtk_tree_menu_create_item (menu, &iter); + + /* Add a GtkTreeMenu submenu to render the children of this row */ + if (gtk_tree_model_iter_has_child (priv->model, &iter)) + { + GtkTreePath *row_path; + GtkWidget *submenu; + + row_path = gtk_tree_model_get_path (priv->model, &iter); + submenu = gtk_tree_menu_new_with_area (priv->area); + + gtk_tree_menu_set_model (GTK_TREE_MENU (submenu), priv->model); + gtk_tree_menu_set_root (GTK_TREE_MENU (submenu), row_path); + + gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu_item), submenu); + + gtk_tree_path_free (row_path); + } + } + + gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item); + + valid = gtk_tree_model_iter_next (priv->model, &iter); + } +} + +/**************************************************************** + * API * + ****************************************************************/ +GtkWidget * +gtk_tree_menu_new (void) +{ + return (GtkWidget *)g_object_new (GTK_TYPE_TREE_MENU, NULL); +} + +GtkWidget * +gtk_tree_menu_new_with_area (GtkCellArea *area) +{ + return (GtkWidget *)g_object_new (GTK_TYPE_TREE_MENU, + "area", area, + NULL); +} + +void +gtk_tree_menu_set_model (GtkTreeMenu *menu, + GtkTreeModel *model) +{ + GtkTreeMenuPrivate *priv; + + g_return_if_fail (GTK_IS_TREE_MENU (menu)); + g_return_if_fail (model == NULL || GTK_IS_TREE_MODEL (model)); + + priv = menu->priv; + + if (priv->model != model) + { + if (priv->model) + { + /* Disconnect signals */ + + g_object_unref (priv->model); + } + + priv->model = model; + + if (priv->model) + { + /* Connect signals */ + + g_object_ref (priv->model); + } + + /* Changing the model in any way invalidates the currently set root, + * so we implicitly reset it to NULL here */ + gtk_tree_menu_set_root (menu, NULL); + + g_object_notify (G_OBJECT (menu), "model"); + } +} + +GtkTreeModel * +gtk_tree_menu_get_model (GtkTreeMenu *menu) +{ + GtkTreeMenuPrivate *priv; + + g_return_val_if_fail (GTK_IS_TREE_MENU (menu), NULL); + + priv = menu->priv; + + return priv->model; +} + +void +gtk_tree_menu_set_root (GtkTreeMenu *menu, + GtkTreePath *path) +{ + GtkTreeMenuPrivate *priv; + + g_return_if_fail (GTK_IS_TREE_MENU (menu)); + g_return_if_fail (path == NULL || menu->priv->model != NULL); + + priv = menu->priv; + + if (priv->root) + gtk_tree_row_reference_free (priv->root); + + if (path) + priv->root = gtk_tree_row_reference_new (priv->model, path); + else + priv->root = NULL; + + /* Destroy all the menu items for the previous root */ + gtk_container_foreach (GTK_CONTAINER (menu), + (GtkCallback) gtk_widget_destroy, NULL); + + /* Populate for the new root */ + gtk_tree_menu_populate (menu); +} + +GtkTreePath * +gtk_tree_menu_get_root (GtkTreeMenu *menu) +{ + GtkTreeMenuPrivate *priv; + + g_return_val_if_fail (GTK_IS_TREE_MENU (menu), NULL); + + priv = menu->priv; + + if (priv->root) + return gtk_tree_row_reference_get_path (priv->root); + + return NULL; +} + +void +gtk_tree_menu_set_row_separator_func (GtkTreeMenu *menu, + GtkTreeViewRowSeparatorFunc func, + gpointer data, + GDestroyNotify destroy) +{ + GtkTreeMenuPrivate *priv; + + g_return_if_fail (GTK_IS_TREE_MENU (menu)); + + priv = menu->priv; + + if (priv->row_separator_destroy) + priv->row_separator_destroy (priv->row_separator_data); + + priv->row_separator_func = func; + priv->row_separator_data = data; + priv->row_separator_destroy = destroy; +} + +GtkTreeViewRowSeparatorFunc +gtk_tree_menu_get_row_separator_func (GtkTreeMenu *menu) +{ + GtkTreeMenuPrivate *priv; + + g_return_val_if_fail (GTK_IS_TREE_MENU (menu), NULL); + + priv = menu->priv; + + return priv->row_separator_func; +} |