3 * Copyright (C) 2010 Openismus GmbH
6 * Tristan Van Berkom <tristanvb@openismus.com>
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Library General Public
10 * License as published by the Free Software Foundation; either
11 * version 2 of the License, or (at your option) any later version.
13 * This library is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Library General Public License for more details.
18 * You should have received a copy of the GNU Library General Public
19 * License along with this library; if not, write to the
20 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
21 * Boston, MA 02111-1307, USA.
26 #include "gtktreemenu.h"
27 #include "gtkmarshalers.h"
28 #include "gtkmenuitem.h"
29 #include "gtktearoffmenuitem.h"
30 #include "gtkseparatormenuitem.h"
31 #include "gtkcellareabox.h"
32 #include "gtkcellareacontext.h"
33 #include "gtkcelllayout.h"
34 #include "gtkcellview.h"
35 #include "gtkprivate.h"
39 static GObject *gtk_tree_menu_constructor (GType type,
40 guint n_construct_properties,
41 GObjectConstructParam *construct_properties);
42 static void gtk_tree_menu_dispose (GObject *object);
43 static void gtk_tree_menu_finalize (GObject *object);
44 static void gtk_tree_menu_set_property (GObject *object,
48 static void gtk_tree_menu_get_property (GObject *object,
54 static void gtk_tree_menu_get_preferred_width (GtkWidget *widget,
57 static void gtk_tree_menu_get_preferred_height (GtkWidget *widget,
61 /* GtkCellLayoutIface */
62 static void gtk_tree_menu_cell_layout_init (GtkCellLayoutIface *iface);
63 static GtkCellArea *gtk_tree_menu_cell_layout_get_area (GtkCellLayout *layout);
66 /* TreeModel/DrawingArea callbacks and building menus/submenus */
67 static inline void rebuild_menu (GtkTreeMenu *menu);
68 static gboolean menu_occupied (GtkTreeMenu *menu,
73 static void relayout_item (GtkTreeMenu *menu,
77 static void gtk_tree_menu_populate (GtkTreeMenu *menu);
78 static GtkWidget *gtk_tree_menu_create_item (GtkTreeMenu *menu,
80 gboolean header_item);
81 static void gtk_tree_menu_create_submenu (GtkTreeMenu *menu,
84 static void gtk_tree_menu_set_area (GtkTreeMenu *menu,
86 static GtkWidget *gtk_tree_menu_get_path_item (GtkTreeMenu *menu,
88 static gboolean gtk_tree_menu_path_in_menu (GtkTreeMenu *menu,
90 gboolean *header_item);
91 static void row_inserted_cb (GtkTreeModel *model,
95 static void row_deleted_cb (GtkTreeModel *model,
98 static void row_reordered_cb (GtkTreeModel *model,
103 static void row_changed_cb (GtkTreeModel *model,
107 static void context_size_changed_cb (GtkCellAreaContext *context,
110 static void area_apply_attributes_cb (GtkCellArea *area,
111 GtkTreeModel *tree_model,
113 gboolean is_expander,
114 gboolean is_expanded,
116 static void item_activated_cb (GtkMenuItem *item,
118 static void submenu_activated_cb (GtkTreeMenu *submenu,
121 static void gtk_tree_menu_set_model_internal (GtkTreeMenu *menu,
122 GtkTreeModel *model);
126 struct _GtkTreeMenuPrivate
128 /* TreeModel and parent for this menu */
130 GtkTreeRowReference *root;
132 /* CellArea and context for this menu */
134 GtkCellAreaContext *context;
137 gulong size_changed_id;
138 gulong apply_attributes_id;
139 gulong row_inserted_id;
140 gulong row_deleted_id;
141 gulong row_reordered_id;
142 gulong row_changed_id;
150 guint32 menu_with_header : 1;
154 GtkTreeViewRowSeparatorFunc row_separator_func;
155 gpointer row_separator_data;
156 GDestroyNotify row_separator_destroy;
158 /* Submenu headers */
159 GtkTreeMenuHeaderFunc header_func;
160 gpointer header_data;
161 GDestroyNotify header_destroy;
176 SIGNAL_MENU_ACTIVATE,
180 static guint tree_menu_signals[N_SIGNALS] = { 0 };
181 static GQuark tree_menu_path_quark = 0;
183 G_DEFINE_TYPE_WITH_CODE (GtkTreeMenu, gtk_tree_menu, GTK_TYPE_MENU,
184 G_IMPLEMENT_INTERFACE (GTK_TYPE_CELL_LAYOUT,
185 gtk_tree_menu_cell_layout_init));
188 gtk_tree_menu_init (GtkTreeMenu *menu)
190 GtkTreeMenuPrivate *priv;
192 menu->priv = G_TYPE_INSTANCE_GET_PRIVATE (menu,
200 priv->context = NULL;
202 priv->size_changed_id = 0;
203 priv->row_inserted_id = 0;
204 priv->row_deleted_id = 0;
205 priv->row_reordered_id = 0;
206 priv->row_changed_id = 0;
208 priv->wrap_width = 0;
209 priv->row_span_col = -1;
210 priv->col_span_col = -1;
212 priv->menu_with_header = FALSE;
213 priv->tearoff = FALSE;
215 priv->row_separator_func = NULL;
216 priv->row_separator_data = NULL;
217 priv->row_separator_destroy = NULL;
219 priv->header_func = NULL;
220 priv->header_data = NULL;
221 priv->header_destroy = NULL;
223 gtk_menu_set_reserve_toggle_size (GTK_MENU (menu), FALSE);
227 gtk_tree_menu_class_init (GtkTreeMenuClass *class)
229 GObjectClass *object_class = G_OBJECT_CLASS (class);
230 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
232 tree_menu_path_quark = g_quark_from_static_string ("gtk-tree-menu-path");
234 object_class->constructor = gtk_tree_menu_constructor;
235 object_class->dispose = gtk_tree_menu_dispose;
236 object_class->finalize = gtk_tree_menu_finalize;
237 object_class->set_property = gtk_tree_menu_set_property;
238 object_class->get_property = gtk_tree_menu_get_property;
240 widget_class->get_preferred_width = gtk_tree_menu_get_preferred_width;
241 widget_class->get_preferred_height = gtk_tree_menu_get_preferred_height;
243 tree_menu_signals[SIGNAL_MENU_ACTIVATE] =
244 g_signal_new (I_("menu-activate"),
245 G_OBJECT_CLASS_TYPE (object_class),
247 0, /* No class closure here */
249 _gtk_marshal_VOID__STRING,
250 G_TYPE_NONE, 1, G_TYPE_STRING);
252 g_object_class_install_property (object_class,
254 g_param_spec_object ("model",
255 P_("TreeMenu model"),
256 P_("The model for the tree menu"),
258 GTK_PARAM_READWRITE));
260 g_object_class_install_property (object_class,
262 g_param_spec_boxed ("root",
263 P_("TreeMenu root row"),
264 P_("The TreeMenu will display children of the "
267 GTK_PARAM_READWRITE));
269 g_object_class_install_property (object_class,
271 g_param_spec_object ("cell-area",
273 P_("The GtkCellArea used to layout cells"),
275 GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
277 g_object_class_install_property (object_class,
279 g_param_spec_boolean ("tearoff",
281 P_("Whether the menu has a tearoff item"),
283 GTK_PARAM_READWRITE));
286 * GtkTreeMenu:wrap-width:
288 * If wrap-width is set to a positive value, the list will be
289 * displayed in multiple columns, the number of columns is
290 * determined by wrap-width.
294 g_object_class_install_property (object_class,
296 g_param_spec_int ("wrap-width",
298 P_("Wrap width for laying out items in a grid"),
302 GTK_PARAM_READWRITE));
305 * GtkTreeMenu:row-span-column:
307 * If this is set to a non-negative value, it must be the index of a column
308 * of type %G_TYPE_INT in the model.
310 * The values of that column are used to determine how many rows a value in
311 * the list will span. Therefore, the values in the model column pointed to
312 * by this property must be greater than zero and not larger than wrap-width.
316 g_object_class_install_property (object_class,
318 g_param_spec_int ("row-span-column",
319 P_("Row span column"),
320 P_("TreeModel column containing the row span values"),
324 GTK_PARAM_READWRITE));
327 * GtkTreeMenu:column-span-column:
329 * If this is set to a non-negative value, it must be the index of a column
330 * of type %G_TYPE_INT in the model.
332 * The values of that column are used to determine how many columns a value
333 * in the list will span.
337 g_object_class_install_property (object_class,
339 g_param_spec_int ("column-span-column",
340 P_("Column span column"),
341 P_("TreeModel column containing the column span values"),
345 GTK_PARAM_READWRITE));
347 g_type_class_add_private (object_class, sizeof (GtkTreeMenuPrivate));
350 /****************************************************************
352 ****************************************************************/
354 gtk_tree_menu_constructor (GType type,
355 guint n_construct_properties,
356 GObjectConstructParam *construct_properties)
360 GtkTreeMenuPrivate *priv;
362 object = G_OBJECT_CLASS (gtk_tree_menu_parent_class)->constructor
363 (type, n_construct_properties, construct_properties);
365 menu = GTK_TREE_MENU (object);
370 GtkCellArea *area = gtk_cell_area_box_new ();
372 gtk_tree_menu_set_area (menu, area);
375 priv->context = gtk_cell_area_create_context (priv->area);
377 priv->size_changed_id =
378 g_signal_connect (priv->context, "notify",
379 G_CALLBACK (context_size_changed_cb), menu);
385 gtk_tree_menu_dispose (GObject *object)
388 GtkTreeMenuPrivate *priv;
390 menu = GTK_TREE_MENU (object);
393 gtk_tree_menu_set_model (menu, NULL);
394 gtk_tree_menu_set_area (menu, NULL);
398 /* Disconnect signals */
399 g_signal_handler_disconnect (priv->context, priv->size_changed_id);
401 g_object_unref (priv->context);
402 priv->context = NULL;
403 priv->size_changed_id = 0;
406 G_OBJECT_CLASS (gtk_tree_menu_parent_class)->dispose (object);
410 gtk_tree_menu_finalize (GObject *object)
413 GtkTreeMenuPrivate *priv;
415 menu = GTK_TREE_MENU (object);
418 gtk_tree_menu_set_row_separator_func (menu, NULL, NULL, NULL);
419 gtk_tree_menu_set_header_func (menu, NULL, NULL, NULL);
422 gtk_tree_row_reference_free (priv->root);
424 G_OBJECT_CLASS (gtk_tree_menu_parent_class)->finalize (object);
428 gtk_tree_menu_set_property (GObject *object,
433 GtkTreeMenu *menu = GTK_TREE_MENU (object);
438 gtk_tree_menu_set_model (menu, g_value_get_object (value));
442 gtk_tree_menu_set_root (menu, g_value_get_boxed (value));
446 /* Construct-only, can only be assigned once */
447 gtk_tree_menu_set_area (menu, (GtkCellArea *)g_value_get_object (value));
451 gtk_tree_menu_set_tearoff (menu, g_value_get_boolean (value));
454 case PROP_WRAP_WIDTH:
455 gtk_tree_menu_set_wrap_width (menu, g_value_get_int (value));
458 case PROP_ROW_SPAN_COL:
459 gtk_tree_menu_set_row_span_column (menu, g_value_get_int (value));
462 case PROP_COL_SPAN_COL:
463 gtk_tree_menu_set_column_span_column (menu, g_value_get_int (value));
467 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
473 gtk_tree_menu_get_property (GObject *object,
478 GtkTreeMenu *menu = GTK_TREE_MENU (object);
479 GtkTreeMenuPrivate *priv = menu->priv;
484 g_value_set_object (value, priv->model);
488 g_value_set_boxed (value, priv->root);
492 g_value_set_object (value, priv->area);
496 g_value_set_boolean (value, priv->tearoff);
500 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
505 /****************************************************************
507 ****************************************************************/
509 /* We tell all the menu items to reserve space for the submenu
510 * indicator if there is at least one submenu, this way we ensure
511 * that every internal cell area gets allocated the
512 * same width (and requested height for the same appropriate width).
515 sync_reserve_submenu_size (GtkTreeMenu *menu)
518 gboolean has_submenu = FALSE;
520 children = gtk_container_get_children (GTK_CONTAINER (menu));
521 for (l = children; l; l = l->next)
523 GtkMenuItem *item = l->data;
525 if (gtk_menu_item_get_submenu (item) != NULL)
532 for (l = children; l; l = l->next)
534 GtkMenuItem *item = l->data;
536 gtk_menu_item_set_reserve_indicator (item, has_submenu);
539 g_list_free (children);
543 gtk_tree_menu_get_preferred_width (GtkWidget *widget,
547 GtkTreeMenu *menu = GTK_TREE_MENU (widget);
548 GtkTreeMenuPrivate *priv = menu->priv;
550 /* We leave the requesting work up to the cellviews which operate in the same
551 * context, reserving space for the submenu indicator if any of the items have
552 * submenus ensures that every cellview will receive the same allocated width.
554 * Since GtkMenu does hieght-for-width correctly, we know that the width of
555 * every cell will be requested before the height-for-widths are requested.
557 g_signal_handler_block (priv->context, priv->size_changed_id);
559 sync_reserve_submenu_size (menu);
561 GTK_WIDGET_CLASS (gtk_tree_menu_parent_class)->get_preferred_width (widget, minimum_size, natural_size);
563 g_signal_handler_unblock (priv->context, priv->size_changed_id);
567 gtk_tree_menu_get_preferred_height (GtkWidget *widget,
571 GtkTreeMenu *menu = GTK_TREE_MENU (widget);
572 GtkTreeMenuPrivate *priv = menu->priv;
574 g_signal_handler_block (priv->context, priv->size_changed_id);
576 sync_reserve_submenu_size (menu);
578 GTK_WIDGET_CLASS (gtk_tree_menu_parent_class)->get_preferred_height (widget, minimum_size, natural_size);
580 g_signal_handler_unblock (priv->context, priv->size_changed_id);
583 /****************************************************************
584 * GtkCellLayoutIface *
585 ****************************************************************/
587 gtk_tree_menu_cell_layout_init (GtkCellLayoutIface *iface)
589 iface->get_area = gtk_tree_menu_cell_layout_get_area;
593 gtk_tree_menu_cell_layout_get_area (GtkCellLayout *layout)
595 GtkTreeMenu *menu = GTK_TREE_MENU (layout);
596 GtkTreeMenuPrivate *priv = menu->priv;
602 /****************************************************************
603 * TreeModel callbacks/populating menus *
604 ****************************************************************/
606 gtk_tree_menu_get_path_item (GtkTreeMenu *menu,
609 GtkWidget *item = NULL;
612 children = gtk_container_get_children (GTK_CONTAINER (menu));
614 for (l = children; item == NULL && l != NULL; l = l->next)
616 GtkWidget *child = l->data;
617 GtkTreePath *path = NULL;
619 if (GTK_IS_SEPARATOR_MENU_ITEM (child))
621 GtkTreeRowReference *row =
622 g_object_get_qdata (G_OBJECT (child), tree_menu_path_quark);
626 path = gtk_tree_row_reference_get_path (row);
629 /* Return any first child where it's row-reference became invalid,
630 * this is because row-references get null paths before we recieve
631 * the "row-deleted" signal.
638 GtkWidget *view = gtk_bin_get_child (GTK_BIN (child));
640 /* It's always a cellview */
641 if (GTK_IS_CELL_VIEW (view))
642 path = gtk_cell_view_get_displayed_row (GTK_CELL_VIEW (view));
645 /* Return any first child where it's row-reference became invalid,
646 * this is because row-references get null paths before we recieve
647 * the "row-deleted" signal.
654 if (gtk_tree_path_compare (search, path) == 0)
657 gtk_tree_path_free (path);
661 g_list_free (children);
667 gtk_tree_menu_path_in_menu (GtkTreeMenu *menu,
669 gboolean *header_item)
671 GtkTreeMenuPrivate *priv = menu->priv;
672 gboolean in_menu = FALSE;
673 gboolean is_header = FALSE;
675 /* Check if the is in root of the model */
676 if (gtk_tree_path_get_depth (path) == 1)
680 g_print ("gtk_tree_menu_path_in_menu: Found in root menu !\n");
684 /* If we are a submenu, compare the parent path */
685 else if (priv->root && gtk_tree_path_get_depth (path) > 1)
687 GtkTreePath *root_path = gtk_tree_row_reference_get_path (priv->root);
688 GtkTreePath *parent_path = gtk_tree_path_copy (path);
690 gtk_tree_path_up (parent_path);
692 if (gtk_tree_path_compare (root_path, parent_path) == 0)
695 g_print ("gtk_tree_menu_path_in_menu: Found in this menu !\n");
698 if (!in_menu && priv->menu_with_header &&
699 gtk_tree_path_compare (root_path, path) == 0)
701 g_print ("gtk_tree_menu_path_in_menu: Found as menu header !\n");
705 gtk_tree_path_free (root_path);
706 gtk_tree_path_free (parent_path);
710 *header_item = is_header;
716 gtk_tree_menu_path_needs_submenu (GtkTreeMenu *menu,
719 GtkWidget *item = NULL;
721 GtkTreePath *parent_path;
723 if (gtk_tree_path_get_depth (search) <= 1)
726 parent_path = gtk_tree_path_copy (search);
727 gtk_tree_path_up (parent_path);
729 children = gtk_container_get_children (GTK_CONTAINER (menu));
731 for (l = children; item == NULL && l != NULL; l = l->next)
733 GtkWidget *child = l->data;
734 GtkTreePath *path = NULL;
736 /* Separators dont get submenus, if it already has a submenu then let
737 * the submenu handle inserted rows */
738 if (!GTK_IS_SEPARATOR_MENU_ITEM (child) &&
739 !gtk_menu_item_get_submenu (GTK_MENU_ITEM (child)))
741 GtkWidget *view = gtk_bin_get_child (GTK_BIN (child));
743 /* It's always a cellview */
744 if (GTK_IS_CELL_VIEW (view))
745 path = gtk_cell_view_get_displayed_row (GTK_CELL_VIEW (view));
750 if (gtk_tree_path_compare (parent_path, path) == 0)
753 gtk_tree_path_free (path);
757 g_list_free (children);
758 gtk_tree_path_free (parent_path);
764 row_inserted_cb (GtkTreeModel *model,
769 GtkTreeMenuPrivate *priv = menu->priv;
770 gint *indices, index, depth;
773 /* If the iter should be in this menu then go ahead and insert it */
774 if (gtk_tree_menu_path_in_menu (menu, path, NULL))
777 if (priv->wrap_width > 0)
781 /* Get the index of the path for this depth */
782 indices = gtk_tree_path_get_indices (path);
783 depth = gtk_tree_path_get_depth (path);
784 index = indices[depth -1];
786 /* Menus with a header include a menuitem for it's root node
787 * and a separator menu item */
788 if (priv->menu_with_header)
791 /* Index after the tearoff item for the root menu if
792 * there is a tearoff item
794 if (priv->root == NULL && priv->tearoff)
797 item = gtk_tree_menu_create_item (menu, iter, FALSE);
798 gtk_menu_shell_insert (GTK_MENU_SHELL (menu), item, index);
800 /* Resize everything */
801 gtk_cell_area_context_flush (menu->priv->context);
806 /* Create submenus for iters if we need to */
807 item = gtk_tree_menu_path_needs_submenu (menu, path);
810 GtkTreePath *item_path = gtk_tree_path_copy (path);
812 gtk_tree_path_up (item_path);
813 gtk_tree_menu_create_submenu (menu, item, item_path);
814 gtk_tree_path_free (item_path);
820 row_deleted_cb (GtkTreeModel *model,
824 GtkTreeMenuPrivate *priv = menu->priv;
827 /* If it's the header item we leave it to the parent menu
828 * to remove us from its menu
830 item = gtk_tree_menu_get_path_item (menu, path);
832 g_print ("Deleting item %p\n", item);
836 if (priv->wrap_width > 0)
840 /* Get rid of the deleted item */
841 gtk_widget_destroy (item);
843 /* Resize everything */
844 gtk_cell_area_context_flush (menu->priv->context);
850 row_reordered_cb (GtkTreeModel *model,
856 GtkTreeMenuPrivate *priv = menu->priv;
857 gboolean this_menu = FALSE;
859 if (path == NULL && priv->root == NULL)
863 GtkTreePath *root_path =
864 gtk_tree_row_reference_get_path (priv->root);
866 if (gtk_tree_path_compare (root_path, path) == 0)
869 gtk_tree_path_free (root_path);
877 menu_item_position (GtkTreeMenu *menu,
883 children = gtk_container_get_children (GTK_CONTAINER (menu));
884 for (position = 0, l = children; l; position++, l = l->next)
886 GtkWidget *iitem = l->data;
892 g_list_free (children);
898 row_changed_cb (GtkTreeModel *model,
903 GtkTreeMenuPrivate *priv = menu->priv;
904 gboolean is_separator = FALSE;
905 gboolean has_header = FALSE;
908 item = gtk_tree_menu_get_path_item (menu, path);
912 GtkTreePath *root_path =
913 gtk_tree_row_reference_get_path (priv->root);
915 if (root_path && gtk_tree_path_compare (root_path, path) == 0)
917 if (priv->header_func)
919 priv->header_func (priv->model, iter, priv->header_data);
921 if (has_header && !item)
923 item = gtk_separator_menu_item_new ();
924 gtk_widget_show (item);
925 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
927 item = gtk_tree_menu_create_item (menu, iter, TRUE);
928 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
930 priv->menu_with_header = TRUE;
932 else if (!has_header && item)
934 /* Destroy the header item and then the following separator */
935 gtk_widget_destroy (item);
936 gtk_widget_destroy (GTK_MENU_SHELL (menu)->children->data);
938 priv->menu_with_header = FALSE;
941 gtk_tree_path_free (root_path);
947 if (priv->wrap_width > 0)
948 /* Ugly, we need to rebuild the menu here if
949 * the row-span/row-column values change
954 if (priv->row_separator_func)
956 priv->row_separator_func (model, iter,
957 priv->row_separator_data);
960 if (is_separator != GTK_IS_SEPARATOR_MENU_ITEM (item))
962 gint position = menu_item_position (menu, item);
964 gtk_widget_destroy (item);
965 item = gtk_tree_menu_create_item (menu, iter, FALSE);
966 gtk_menu_shell_insert (GTK_MENU_SHELL (menu), item, position);
973 context_size_changed_cb (GtkCellAreaContext *context,
977 if (!strcmp (pspec->name, "minimum-width") ||
978 !strcmp (pspec->name, "natural-width") ||
979 !strcmp (pspec->name, "minimum-height") ||
980 !strcmp (pspec->name, "natural-height"))
981 gtk_widget_queue_resize (menu);
985 area_is_sensitive (GtkCellArea *area)
988 gboolean sensitive = FALSE;
990 cells = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (area));
992 for (list = cells; list; list = list->next)
994 g_object_get (list->data, "sensitive", &sensitive, NULL);
1005 area_apply_attributes_cb (GtkCellArea *area,
1006 GtkTreeModel *tree_model,
1008 gboolean is_expander,
1009 gboolean is_expanded,
1012 /* If the menu for this iter has a submenu */
1013 GtkTreeMenuPrivate *priv = menu->priv;
1019 path = gtk_tree_model_get_path (tree_model, iter);
1021 if (gtk_tree_menu_path_in_menu (menu, path, &is_header))
1023 item = gtk_tree_menu_get_path_item (menu, path);
1025 /* If there is no submenu, go ahead and update item sensitivity,
1026 * items with submenus are always sensitive */
1027 if (item && !gtk_menu_item_get_submenu (GTK_MENU_ITEM (item)))
1029 sensitive = area_is_sensitive (priv->area);
1031 gtk_widget_set_sensitive (item, sensitive);
1035 /* For header items we need to set the sensitivity
1036 * of the following separator item
1038 if (GTK_MENU_SHELL (menu)->children &&
1039 GTK_MENU_SHELL (menu)->children->next)
1041 GtkWidget *separator =
1042 GTK_MENU_SHELL (menu)->children->next->data;
1044 gtk_widget_set_sensitive (separator, sensitive);
1050 gtk_tree_path_free (path);
1054 gtk_tree_menu_set_area (GtkTreeMenu *menu,
1057 GtkTreeMenuPrivate *priv = menu->priv;
1061 g_signal_handler_disconnect (priv->area,
1062 priv->apply_attributes_id);
1063 priv->apply_attributes_id = 0;
1065 g_object_unref (priv->area);
1072 g_object_ref_sink (priv->area);
1074 priv->apply_attributes_id =
1075 g_signal_connect (priv->area, "apply-attributes",
1076 G_CALLBACK (area_apply_attributes_cb), menu);
1081 menu_occupied (GtkTreeMenu *menu,
1085 guint bottom_attach)
1089 for (i = GTK_MENU_SHELL (menu)->children; i; i = i->next)
1093 gtk_container_child_get (GTK_CONTAINER (menu),
1097 "bottom-attach", &b,
1101 /* look if this item intersects with the given coordinates */
1102 if (right_attach > l && left_attach < r && bottom_attach > t && top_attach < b)
1110 relayout_item (GtkTreeMenu *menu,
1115 GtkTreeMenuPrivate *priv = menu->priv;
1116 gint current_col = 0, current_row = 0;
1117 gint rows = 1, cols = 1;
1119 if (priv->col_span_col == -1 &&
1120 priv->row_span_col == -1 &&
1123 gtk_container_child_get (GTK_CONTAINER (menu), prev,
1124 "right-attach", ¤t_col,
1125 "top-attach", ¤t_row,
1127 if (current_col + cols > priv->wrap_width)
1135 if (priv->col_span_col != -1)
1136 gtk_tree_model_get (priv->model, iter,
1137 priv->col_span_col, &cols,
1139 if (priv->row_span_col != -1)
1140 gtk_tree_model_get (priv->model, iter,
1141 priv->row_span_col, &rows,
1146 if (current_col + cols > priv->wrap_width)
1152 if (!menu_occupied (menu,
1153 current_col, current_col + cols,
1154 current_row, current_row + rows))
1161 /* set attach props */
1162 gtk_menu_attach (GTK_MENU (menu), item,
1163 current_col, current_col + cols,
1164 current_row, current_row + rows);
1168 gtk_tree_menu_create_submenu (GtkTreeMenu *menu,
1172 GtkTreeMenuPrivate *priv = menu->priv;
1176 view = gtk_bin_get_child (GTK_BIN (item));
1177 gtk_cell_view_set_draw_sensitive (GTK_CELL_VIEW (view), TRUE);
1179 submenu = gtk_tree_menu_new_with_area (priv->area);
1181 gtk_tree_menu_set_row_separator_func (GTK_TREE_MENU (submenu),
1182 priv->row_separator_func,
1183 priv->row_separator_data,
1184 priv->row_separator_destroy);
1185 gtk_tree_menu_set_header_func (GTK_TREE_MENU (submenu),
1188 priv->header_destroy);
1190 gtk_tree_menu_set_wrap_width (GTK_TREE_MENU (submenu), priv->wrap_width);
1191 gtk_tree_menu_set_row_span_column (GTK_TREE_MENU (submenu), priv->row_span_col);
1192 gtk_tree_menu_set_column_span_column (GTK_TREE_MENU (submenu), priv->col_span_col);
1194 gtk_tree_menu_set_model_internal (GTK_TREE_MENU (submenu), priv->model);
1195 gtk_tree_menu_set_root (GTK_TREE_MENU (submenu), path);
1196 gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), submenu);
1198 g_signal_connect (submenu, "menu-activate",
1199 G_CALLBACK (submenu_activated_cb), menu);
1203 gtk_tree_menu_create_item (GtkTreeMenu *menu,
1205 gboolean header_item)
1207 GtkTreeMenuPrivate *priv = menu->priv;
1208 GtkWidget *item, *view;
1210 gboolean is_separator = FALSE;
1212 path = gtk_tree_model_get_path (priv->model, iter);
1214 if (priv->row_separator_func)
1216 priv->row_separator_func (priv->model, iter,
1217 priv->row_separator_data);
1221 item = gtk_separator_menu_item_new ();
1222 gtk_widget_show (item);
1224 g_object_set_qdata_full (G_OBJECT (item),
1225 tree_menu_path_quark,
1226 gtk_tree_row_reference_new (priv->model, path),
1227 (GDestroyNotify)gtk_tree_row_reference_free);
1231 view = gtk_cell_view_new_with_context (priv->area, priv->context);
1232 item = gtk_menu_item_new ();
1233 gtk_widget_show (view);
1234 gtk_widget_show (item);
1236 gtk_cell_view_set_model (GTK_CELL_VIEW (view), priv->model);
1237 gtk_cell_view_set_displayed_row (GTK_CELL_VIEW (view), path);
1239 gtk_widget_show (view);
1240 gtk_container_add (GTK_CONTAINER (item), view);
1242 g_signal_connect (item, "activate", G_CALLBACK (item_activated_cb), menu);
1244 /* Add a GtkTreeMenu submenu to render the children of this row */
1245 if (header_item == FALSE &&
1246 gtk_tree_model_iter_has_child (priv->model, iter))
1247 gtk_tree_menu_create_submenu (menu, item, path);
1250 gtk_tree_path_free (path);
1256 rebuild_menu (GtkTreeMenu *menu)
1258 GtkTreeMenuPrivate *priv = menu->priv;
1260 /* Destroy all the menu items */
1261 gtk_container_foreach (GTK_CONTAINER (menu),
1262 (GtkCallback) gtk_widget_destroy, NULL);
1266 gtk_tree_menu_populate (menu);
1271 gtk_tree_menu_populate (GtkTreeMenu *menu)
1273 GtkTreeMenuPrivate *priv = menu->priv;
1274 GtkTreePath *path = NULL;
1277 gboolean valid = FALSE;
1278 GtkWidget *menu_item, *prev = NULL;
1284 path = gtk_tree_row_reference_get_path (priv->root);
1288 if (gtk_tree_model_get_iter (priv->model, &parent, path))
1290 valid = gtk_tree_model_iter_children (priv->model, &iter, &parent);
1292 if (priv->header_func &&
1293 priv->header_func (priv->model, &parent, priv->header_data))
1295 /* Add a submenu header for rows which desire one, used for
1296 * combo boxes to allow all rows to be activatable/selectable
1298 menu_item = gtk_tree_menu_create_item (menu, &parent, TRUE);
1299 gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);
1301 menu_item = gtk_separator_menu_item_new ();
1302 gtk_widget_show (menu_item);
1303 gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);
1306 priv->menu_with_header = TRUE;
1309 gtk_tree_path_free (path);
1313 /* Tearoff menu items only go in the root menu */
1316 menu_item = gtk_tearoff_menu_item_new ();
1317 gtk_widget_show (menu_item);
1319 if (priv->wrap_width > 0)
1320 gtk_menu_attach (GTK_MENU (menu), menu_item, 0, priv->wrap_width, 0, 1);
1322 gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);
1327 valid = gtk_tree_model_iter_children (priv->model, &iter, NULL);
1330 /* Create a menu item for every row at the current depth, add a GtkTreeMenu
1331 * submenu for iters/items that have children */
1334 menu_item = gtk_tree_menu_create_item (menu, &iter, FALSE);
1336 gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);
1338 if (priv->wrap_width > 0)
1339 relayout_item (menu, menu_item, &iter, prev);
1342 valid = gtk_tree_model_iter_next (priv->model, &iter);
1347 item_activated_cb (GtkMenuItem *item,
1354 /* Only activate leafs, not parents */
1355 if (!gtk_menu_item_get_submenu (item))
1357 view = GTK_CELL_VIEW (gtk_bin_get_child (GTK_BIN (item)));
1358 path = gtk_cell_view_get_displayed_row (view);
1359 path_str = gtk_tree_path_to_string (path);
1361 g_signal_emit (menu, tree_menu_signals[SIGNAL_MENU_ACTIVATE], 0, path_str);
1364 gtk_tree_path_free (path);
1369 submenu_activated_cb (GtkTreeMenu *submenu,
1373 g_signal_emit (menu, tree_menu_signals[SIGNAL_MENU_ACTIVATE], 0, path);
1376 /* Sets the model without rebuilding the menu, prevents
1377 * infinite recursion while building submenus (we wait
1378 * until the root is set and then build the menu) */
1380 gtk_tree_menu_set_model_internal (GtkTreeMenu *menu,
1381 GtkTreeModel *model)
1383 GtkTreeMenuPrivate *priv;
1387 if (priv->model != model)
1391 /* Disconnect signals */
1392 g_signal_handler_disconnect (priv->model,
1393 priv->row_inserted_id);
1394 g_signal_handler_disconnect (priv->model,
1395 priv->row_deleted_id);
1396 g_signal_handler_disconnect (priv->model,
1397 priv->row_reordered_id);
1398 g_signal_handler_disconnect (priv->model,
1399 priv->row_changed_id);
1400 priv->row_inserted_id = 0;
1401 priv->row_deleted_id = 0;
1402 priv->row_reordered_id = 0;
1403 priv->row_changed_id = 0;
1405 g_object_unref (priv->model);
1408 priv->model = model;
1412 g_object_ref (priv->model);
1414 /* Connect signals */
1415 priv->row_inserted_id = g_signal_connect (priv->model, "row-inserted",
1416 G_CALLBACK (row_inserted_cb), menu);
1417 priv->row_deleted_id = g_signal_connect (priv->model, "row-deleted",
1418 G_CALLBACK (row_deleted_cb), menu);
1419 priv->row_reordered_id = g_signal_connect (priv->model, "rows-reordered",
1420 G_CALLBACK (row_reordered_cb), menu);
1421 priv->row_changed_id = g_signal_connect (priv->model, "row-changed",
1422 G_CALLBACK (row_changed_cb), menu);
1427 /****************************************************************
1429 ****************************************************************/
1431 gtk_tree_menu_new (void)
1433 return (GtkWidget *)g_object_new (GTK_TYPE_TREE_MENU, NULL);
1437 gtk_tree_menu_new_with_area (GtkCellArea *area)
1439 return (GtkWidget *)g_object_new (GTK_TYPE_TREE_MENU,
1445 gtk_tree_menu_new_full (GtkCellArea *area,
1446 GtkTreeModel *model,
1449 return (GtkWidget *)g_object_new (GTK_TYPE_TREE_MENU,
1457 gtk_tree_menu_set_model (GtkTreeMenu *menu,
1458 GtkTreeModel *model)
1460 g_return_if_fail (GTK_IS_TREE_MENU (menu));
1461 g_return_if_fail (model == NULL || GTK_IS_TREE_MODEL (model));
1463 gtk_tree_menu_set_model_internal (menu, model);
1465 rebuild_menu (menu);
1469 gtk_tree_menu_get_model (GtkTreeMenu *menu)
1471 GtkTreeMenuPrivate *priv;
1473 g_return_val_if_fail (GTK_IS_TREE_MENU (menu), NULL);
1481 gtk_tree_menu_set_root (GtkTreeMenu *menu,
1484 GtkTreeMenuPrivate *priv;
1486 g_return_if_fail (GTK_IS_TREE_MENU (menu));
1487 g_return_if_fail (menu->priv->model != NULL || path == NULL);
1492 gtk_tree_row_reference_free (priv->root);
1495 priv->root = gtk_tree_row_reference_new (priv->model, path);
1499 rebuild_menu (menu);
1503 gtk_tree_menu_get_root (GtkTreeMenu *menu)
1505 GtkTreeMenuPrivate *priv;
1507 g_return_val_if_fail (GTK_IS_TREE_MENU (menu), NULL);
1512 return gtk_tree_row_reference_get_path (priv->root);
1518 gtk_tree_menu_get_tearoff (GtkTreeMenu *menu)
1520 GtkTreeMenuPrivate *priv;
1522 g_return_val_if_fail (GTK_IS_TREE_MENU (menu), FALSE);
1526 return priv->tearoff;
1530 gtk_tree_menu_set_tearoff (GtkTreeMenu *menu,
1533 GtkTreeMenuPrivate *priv;
1535 g_return_if_fail (GTK_IS_TREE_MENU (menu));
1539 if (priv->tearoff != tearoff)
1541 priv->tearoff = tearoff;
1543 rebuild_menu (menu);
1545 g_object_notify (G_OBJECT (menu), "tearoff");
1550 gtk_tree_menu_get_wrap_width (GtkTreeMenu *menu)
1552 GtkTreeMenuPrivate *priv;
1554 g_return_val_if_fail (GTK_IS_TREE_MENU (menu), FALSE);
1558 return priv->wrap_width;
1562 gtk_tree_menu_set_wrap_width (GtkTreeMenu *menu,
1565 GtkTreeMenuPrivate *priv;
1567 g_return_if_fail (GTK_IS_TREE_MENU (menu));
1568 g_return_if_fail (width >= 0);
1572 if (priv->wrap_width != width)
1574 priv->wrap_width = width;
1576 rebuild_menu (menu);
1578 g_object_notify (G_OBJECT (menu), "wrap-width");
1583 gtk_tree_menu_get_row_span_column (GtkTreeMenu *menu)
1585 GtkTreeMenuPrivate *priv;
1587 g_return_val_if_fail (GTK_IS_TREE_MENU (menu), FALSE);
1591 return priv->row_span_col;
1595 gtk_tree_menu_set_row_span_column (GtkTreeMenu *menu,
1598 GtkTreeMenuPrivate *priv;
1600 g_return_if_fail (GTK_IS_TREE_MENU (menu));
1604 if (priv->row_span_col != row_span)
1606 priv->row_span_col = row_span;
1608 if (priv->wrap_width > 0)
1609 rebuild_menu (menu);
1611 g_object_notify (G_OBJECT (menu), "row-span-column");
1616 gtk_tree_menu_get_column_span_column (GtkTreeMenu *menu)
1618 GtkTreeMenuPrivate *priv;
1620 g_return_val_if_fail (GTK_IS_TREE_MENU (menu), FALSE);
1624 return priv->col_span_col;
1628 gtk_tree_menu_set_column_span_column (GtkTreeMenu *menu,
1631 GtkTreeMenuPrivate *priv;
1633 g_return_if_fail (GTK_IS_TREE_MENU (menu));
1637 if (priv->col_span_col != column_span)
1639 priv->col_span_col = column_span;
1641 if (priv->wrap_width > 0)
1642 rebuild_menu (menu);
1644 g_object_notify (G_OBJECT (menu), "column-span-column");
1648 GtkTreeViewRowSeparatorFunc
1649 gtk_tree_menu_get_row_separator_func (GtkTreeMenu *menu)
1651 GtkTreeMenuPrivate *priv;
1653 g_return_val_if_fail (GTK_IS_TREE_MENU (menu), NULL);
1657 return priv->row_separator_func;
1661 gtk_tree_menu_set_row_separator_func (GtkTreeMenu *menu,
1662 GtkTreeViewRowSeparatorFunc func,
1664 GDestroyNotify destroy)
1666 GtkTreeMenuPrivate *priv;
1668 g_return_if_fail (GTK_IS_TREE_MENU (menu));
1672 if (priv->row_separator_destroy)
1673 priv->row_separator_destroy (priv->row_separator_data);
1675 priv->row_separator_func = func;
1676 priv->row_separator_data = data;
1677 priv->row_separator_destroy = destroy;
1679 rebuild_menu (menu);
1682 GtkTreeMenuHeaderFunc
1683 gtk_tree_menu_get_header_func (GtkTreeMenu *menu)
1685 GtkTreeMenuPrivate *priv;
1687 g_return_val_if_fail (GTK_IS_TREE_MENU (menu), NULL);
1691 return priv->header_func;
1695 gtk_tree_menu_set_header_func (GtkTreeMenu *menu,
1696 GtkTreeMenuHeaderFunc func,
1698 GDestroyNotify destroy)
1700 GtkTreeMenuPrivate *priv;
1702 g_return_if_fail (GTK_IS_TREE_MENU (menu));
1706 if (priv->header_destroy)
1707 priv->header_destroy (priv->header_data);
1709 priv->header_func = func;
1710 priv->header_data = data;
1711 priv->header_destroy = destroy;
1713 rebuild_menu (menu);