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_set_area (GtkTreeMenu *menu,
83 static GtkWidget *gtk_tree_menu_get_path_item (GtkTreeMenu *menu,
85 static void row_inserted_cb (GtkTreeModel *model,
89 static void row_deleted_cb (GtkTreeModel *model,
92 static void row_reordered_cb (GtkTreeModel *model,
97 static void row_changed_cb (GtkTreeModel *model,
102 static void context_size_changed_cb (GtkCellAreaContext *context,
105 static void item_activated_cb (GtkMenuItem *item,
107 static void submenu_activated_cb (GtkTreeMenu *submenu,
113 struct _GtkTreeMenuPrivate
115 /* TreeModel and parent for this menu */
117 GtkTreeRowReference *root;
119 /* CellArea and context for this menu */
121 GtkCellAreaContext *context;
124 gulong size_changed_id;
125 gulong row_inserted_id;
126 gulong row_deleted_id;
127 gulong row_reordered_id;
128 gulong row_changed_id;
136 guint32 menu_with_header : 1;
140 GtkTreeViewRowSeparatorFunc row_separator_func;
141 gpointer row_separator_data;
142 GDestroyNotify row_separator_destroy;
144 /* Submenu headers */
145 GtkTreeMenuHeaderFunc header_func;
146 gpointer header_data;
147 GDestroyNotify header_destroy;
162 SIGNAL_MENU_ACTIVATE,
166 static guint tree_menu_signals[N_SIGNALS] = { 0 };
167 static GQuark tree_menu_path_quark = 0;
169 G_DEFINE_TYPE_WITH_CODE (GtkTreeMenu, gtk_tree_menu, GTK_TYPE_MENU,
170 G_IMPLEMENT_INTERFACE (GTK_TYPE_CELL_LAYOUT,
171 gtk_tree_menu_cell_layout_init));
174 gtk_tree_menu_init (GtkTreeMenu *menu)
176 GtkTreeMenuPrivate *priv;
178 menu->priv = G_TYPE_INSTANCE_GET_PRIVATE (menu,
186 priv->context = NULL;
188 priv->size_changed_id = 0;
189 priv->row_inserted_id = 0;
190 priv->row_deleted_id = 0;
191 priv->row_reordered_id = 0;
192 priv->row_changed_id = 0;
194 priv->wrap_width = 0;
195 priv->row_span_col = -1;
196 priv->col_span_col = -1;
198 priv->menu_with_header = FALSE;
199 priv->tearoff = FALSE;
201 priv->row_separator_func = NULL;
202 priv->row_separator_data = NULL;
203 priv->row_separator_destroy = NULL;
205 priv->header_func = NULL;
206 priv->header_data = NULL;
207 priv->header_destroy = NULL;
209 gtk_menu_set_reserve_toggle_size (GTK_MENU (menu), FALSE);
213 gtk_tree_menu_class_init (GtkTreeMenuClass *class)
215 GObjectClass *object_class = G_OBJECT_CLASS (class);
216 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
218 tree_menu_path_quark = g_quark_from_static_string ("gtk-tree-menu-path");
220 object_class->constructor = gtk_tree_menu_constructor;
221 object_class->dispose = gtk_tree_menu_dispose;
222 object_class->finalize = gtk_tree_menu_finalize;
223 object_class->set_property = gtk_tree_menu_set_property;
224 object_class->get_property = gtk_tree_menu_get_property;
226 widget_class->get_preferred_width = gtk_tree_menu_get_preferred_width;
227 widget_class->get_preferred_height = gtk_tree_menu_get_preferred_height;
229 tree_menu_signals[SIGNAL_MENU_ACTIVATE] =
230 g_signal_new (I_("menu-activate"),
231 G_OBJECT_CLASS_TYPE (object_class),
233 0, /* No class closure here */
235 _gtk_marshal_VOID__STRING,
236 G_TYPE_NONE, 1, G_TYPE_STRING);
238 g_object_class_install_property (object_class,
240 g_param_spec_object ("model",
241 P_("TreeMenu model"),
242 P_("The model for the tree menu"),
244 GTK_PARAM_READWRITE));
246 g_object_class_install_property (object_class,
248 g_param_spec_boxed ("root",
249 P_("TreeMenu root row"),
250 P_("The TreeMenu will display children of the "
253 GTK_PARAM_READWRITE));
255 g_object_class_install_property (object_class,
257 g_param_spec_object ("cell-area",
259 P_("The GtkCellArea used to layout cells"),
261 GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
263 g_object_class_install_property (object_class,
265 g_param_spec_boolean ("tearoff",
267 P_("Whether the menu has a tearoff item"),
269 GTK_PARAM_READWRITE));
272 * GtkTreeMenu:wrap-width:
274 * If wrap-width is set to a positive value, the list will be
275 * displayed in multiple columns, the number of columns is
276 * determined by wrap-width.
280 g_object_class_install_property (object_class,
282 g_param_spec_int ("wrap-width",
284 P_("Wrap width for laying out items in a grid"),
288 GTK_PARAM_READWRITE));
291 * GtkTreeMenu:row-span-column:
293 * If this is set to a non-negative value, it must be the index of a column
294 * of type %G_TYPE_INT in the model.
296 * The values of that column are used to determine how many rows a value in
297 * the list will span. Therefore, the values in the model column pointed to
298 * by this property must be greater than zero and not larger than wrap-width.
302 g_object_class_install_property (object_class,
304 g_param_spec_int ("row-span-column",
305 P_("Row span column"),
306 P_("TreeModel column containing the row span values"),
310 GTK_PARAM_READWRITE));
313 * GtkTreeMenu:column-span-column:
315 * If this is set to a non-negative value, it must be the index of a column
316 * of type %G_TYPE_INT in the model.
318 * The values of that column are used to determine how many columns a value
319 * in the list will span.
323 g_object_class_install_property (object_class,
325 g_param_spec_int ("column-span-column",
326 P_("Column span column"),
327 P_("TreeModel column containing the column span values"),
331 GTK_PARAM_READWRITE));
333 g_type_class_add_private (object_class, sizeof (GtkTreeMenuPrivate));
336 /****************************************************************
338 ****************************************************************/
340 gtk_tree_menu_constructor (GType type,
341 guint n_construct_properties,
342 GObjectConstructParam *construct_properties)
346 GtkTreeMenuPrivate *priv;
348 object = G_OBJECT_CLASS (gtk_tree_menu_parent_class)->constructor
349 (type, n_construct_properties, construct_properties);
351 menu = GTK_TREE_MENU (object);
356 GtkCellArea *area = gtk_cell_area_box_new ();
358 gtk_tree_menu_set_area (menu, area);
361 priv->context = gtk_cell_area_create_context (priv->area);
362 priv->size_changed_id =
363 g_signal_connect (priv->context, "notify",
364 G_CALLBACK (context_size_changed_cb), menu);
371 gtk_tree_menu_dispose (GObject *object)
374 GtkTreeMenuPrivate *priv;
376 menu = GTK_TREE_MENU (object);
379 gtk_tree_menu_set_model (menu, NULL);
380 gtk_tree_menu_set_area (menu, NULL);
384 /* Disconnect signals */
385 g_signal_handler_disconnect (priv->context, priv->size_changed_id);
387 g_object_unref (priv->context);
388 priv->context = NULL;
389 priv->size_changed_id = 0;
392 G_OBJECT_CLASS (gtk_tree_menu_parent_class)->dispose (object);
396 gtk_tree_menu_finalize (GObject *object)
399 GtkTreeMenuPrivate *priv;
401 menu = GTK_TREE_MENU (object);
404 gtk_tree_menu_set_row_separator_func (menu, NULL, NULL, NULL);
405 gtk_tree_menu_set_header_func (menu, NULL, NULL, NULL);
408 gtk_tree_row_reference_free (priv->root);
410 G_OBJECT_CLASS (gtk_tree_menu_parent_class)->finalize (object);
414 gtk_tree_menu_set_property (GObject *object,
419 GtkTreeMenu *menu = GTK_TREE_MENU (object);
424 gtk_tree_menu_set_model (menu, g_value_get_object (value));
428 gtk_tree_menu_set_root (menu, g_value_get_boxed (value));
432 /* Construct-only, can only be assigned once */
433 gtk_tree_menu_set_area (menu, (GtkCellArea *)g_value_get_object (value));
437 gtk_tree_menu_set_tearoff (menu, g_value_get_boolean (value));
440 case PROP_WRAP_WIDTH:
441 gtk_tree_menu_set_wrap_width (menu, g_value_get_int (value));
444 case PROP_ROW_SPAN_COL:
445 gtk_tree_menu_set_row_span_column (menu, g_value_get_int (value));
448 case PROP_COL_SPAN_COL:
449 gtk_tree_menu_set_column_span_column (menu, g_value_get_int (value));
453 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
459 gtk_tree_menu_get_property (GObject *object,
464 GtkTreeMenu *menu = GTK_TREE_MENU (object);
465 GtkTreeMenuPrivate *priv = menu->priv;
470 g_value_set_object (value, priv->model);
474 g_value_set_boxed (value, priv->root);
478 g_value_set_object (value, priv->area);
482 g_value_set_boolean (value, priv->tearoff);
486 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
491 /****************************************************************
493 ****************************************************************/
495 /* We tell all the menu items to reserve space for the submenu
496 * indicator if there is at least one submenu, this way we ensure
497 * that every internal cell area gets allocated the
498 * same width (and requested height for the same appropriate width).
501 sync_reserve_submenu_size (GtkTreeMenu *menu)
504 gboolean has_submenu = FALSE;
506 children = gtk_container_get_children (GTK_CONTAINER (menu));
507 for (l = children; l; l = l->next)
509 GtkMenuItem *item = l->data;
511 if (gtk_menu_item_get_submenu (item) != NULL)
518 for (l = children; l; l = l->next)
520 GtkMenuItem *item = l->data;
522 gtk_menu_item_set_reserve_indicator (item, has_submenu);
525 g_list_free (children);
529 gtk_tree_menu_get_preferred_width (GtkWidget *widget,
533 GtkTreeMenu *menu = GTK_TREE_MENU (widget);
534 GtkTreeMenuPrivate *priv = menu->priv;
536 /* We leave the requesting work up to the cellviews which operate in the same
537 * context, reserving space for the submenu indicator if any of the items have
538 * submenus ensures that every cellview will receive the same allocated width.
540 * Since GtkMenu does hieght-for-width correctly, we know that the width of
541 * every cell will be requested before the height-for-widths are requested.
543 g_signal_handler_block (priv->context, priv->size_changed_id);
545 sync_reserve_submenu_size (menu);
547 GTK_WIDGET_CLASS (gtk_tree_menu_parent_class)->get_preferred_width (widget, minimum_size, natural_size);
549 g_signal_handler_unblock (priv->context, priv->size_changed_id);
553 gtk_tree_menu_get_preferred_height (GtkWidget *widget,
557 GtkTreeMenu *menu = GTK_TREE_MENU (widget);
558 GtkTreeMenuPrivate *priv = menu->priv;
560 g_signal_handler_block (priv->context, priv->size_changed_id);
562 sync_reserve_submenu_size (menu);
564 GTK_WIDGET_CLASS (gtk_tree_menu_parent_class)->get_preferred_height (widget, minimum_size, natural_size);
566 g_signal_handler_unblock (priv->context, priv->size_changed_id);
569 /****************************************************************
570 * GtkCellLayoutIface *
571 ****************************************************************/
573 gtk_tree_menu_cell_layout_init (GtkCellLayoutIface *iface)
575 iface->get_area = gtk_tree_menu_cell_layout_get_area;
579 gtk_tree_menu_cell_layout_get_area (GtkCellLayout *layout)
581 GtkTreeMenu *menu = GTK_TREE_MENU (layout);
582 GtkTreeMenuPrivate *priv = menu->priv;
588 /****************************************************************
589 * TreeModel callbacks/populating menus *
590 ****************************************************************/
592 gtk_tree_menu_get_path_item (GtkTreeMenu *menu,
595 GtkWidget *item = NULL;
598 children = gtk_container_get_children (GTK_CONTAINER (menu));
600 for (l = children; item == NULL && l != NULL; l = l->next)
602 GtkWidget *child = l->data;
603 GtkTreePath *path = NULL;
605 if (GTK_IS_SEPARATOR_MENU_ITEM (child))
607 GtkTreeRowReference *row =
608 g_object_get_qdata (G_OBJECT (child), tree_menu_path_quark);
610 if (row && gtk_tree_row_reference_valid (row))
611 path = gtk_tree_row_reference_get_path (row);
615 GtkWidget *view = gtk_bin_get_child (GTK_BIN (child));
617 /* It's always a cellview */
618 if (GTK_IS_CELL_VIEW (view))
619 path = gtk_cell_view_get_displayed_row (GTK_CELL_VIEW (view));
624 if (gtk_tree_path_compare (search, path) == 0)
627 gtk_tree_path_free (path);
631 g_list_free (children);
637 row_inserted_cb (GtkTreeModel *model,
642 GtkTreeMenuPrivate *priv = menu->priv;
643 GtkTreePath *parent_path;
644 gboolean this_menu = FALSE;
645 gint *indices, index, depth;
647 parent_path = gtk_tree_path_copy (path);
649 /* Check if the menu and the added iter are in root of the model */
650 if (gtk_tree_path_get_depth (parent_path) <= 1)
655 /* If we are a submenu, compare the path */
658 GtkTreePath *root_path =
659 gtk_tree_row_reference_get_path (priv->root);
661 if (gtk_tree_path_compare (root_path, parent_path) == 0)
664 gtk_tree_path_free (root_path);
667 gtk_tree_path_free (parent_path);
669 /* If the iter should be in this menu then go ahead and insert it */
674 if (priv->wrap_width > 0)
678 /* Get the index of the path for this depth */
679 indices = gtk_tree_path_get_indices (path);
680 depth = gtk_tree_path_get_depth (path);
681 index = indices[depth -1];
683 /* Menus with a header include a menuitem for it's root node
684 * and a separator menu item */
685 if (priv->menu_with_header)
688 /* Index after the tearoff item for the root menu if
689 * there is a tearoff item
691 if (priv->root == NULL && priv->tearoff)
694 item = gtk_tree_menu_create_item (menu, iter, FALSE);
695 gtk_menu_shell_insert (GTK_MENU_SHELL (menu), item, index);
697 /* Resize everything */
698 gtk_cell_area_context_flush (menu->priv->context);
704 row_deleted_cb (GtkTreeModel *model,
708 GtkTreeMenuPrivate *priv = menu->priv;
709 GtkTreePath *root_path;
712 /* If it's the root node we leave it to the parent menu to remove us
716 root_path = gtk_tree_row_reference_get_path (priv->root);
718 if (gtk_tree_path_compare (root_path, path) == 0)
720 gtk_tree_path_free (root_path);
725 item = gtk_tree_menu_get_path_item (menu, path);
728 if (priv->wrap_width > 0)
732 /* Get rid of the deleted item */
733 gtk_widget_destroy (item);
735 /* Resize everything */
736 gtk_cell_area_context_flush (menu->priv->context);
742 row_reordered_cb (GtkTreeModel *model,
748 GtkTreeMenuPrivate *priv = menu->priv;
749 gboolean this_menu = FALSE;
751 if (iter == NULL && priv->root == NULL)
755 GtkTreePath *root_path =
756 gtk_tree_row_reference_get_path (priv->root);
758 if (gtk_tree_path_compare (root_path, path) == 0)
761 gtk_tree_path_free (root_path);
769 menu_item_position (GtkTreeMenu *menu,
775 children = gtk_container_get_children (GTK_CONTAINER (menu));
776 for (position = 0, l = children; l; position++, l = l->next)
778 GtkWidget *iitem = l->data;
784 g_list_free (children);
790 row_changed_cb (GtkTreeModel *model,
795 GtkTreeMenuPrivate *priv = menu->priv;
796 gboolean is_separator = FALSE;
797 gboolean has_header = FALSE;
800 item = gtk_tree_menu_get_path_item (menu, path);
804 GtkTreePath *root_path =
805 gtk_tree_row_reference_get_path (priv->root);
807 if (gtk_tree_path_compare (root_path, path) == 0)
809 if (priv->header_func)
811 priv->header_func (priv->model, iter, priv->header_data);
813 if (has_header && !item)
815 item = gtk_separator_menu_item_new ();
816 gtk_widget_show (item);
817 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
819 item = gtk_tree_menu_create_item (menu, iter, TRUE);
820 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
822 priv->menu_with_header = TRUE;
824 else if (!has_header && item)
826 /* Destroy the header item and the following separator */
827 gtk_widget_destroy (item);
828 gtk_widget_destroy (GTK_MENU_SHELL (menu)->children->data);
830 priv->menu_with_header = FALSE;
834 gtk_tree_path_free (root_path);
839 if (priv->wrap_width > 0)
840 /* Ugly, we need to rebuild the menu here if
841 * the row-span/row-column values change
846 if (priv->row_separator_func)
848 priv->row_separator_func (model, iter,
849 priv->row_separator_data);
852 if (is_separator != GTK_IS_SEPARATOR_MENU_ITEM (item))
854 gint position = menu_item_position (menu, item);
856 gtk_widget_destroy (item);
857 item = gtk_tree_menu_create_item (menu, iter, FALSE);
858 gtk_menu_shell_insert (GTK_MENU_SHELL (menu), item, position);
865 context_size_changed_cb (GtkCellAreaContext *context,
869 if (!strcmp (pspec->name, "minimum-width") ||
870 !strcmp (pspec->name, "natural-width") ||
871 !strcmp (pspec->name, "minimum-height") ||
872 !strcmp (pspec->name, "natural-height"))
873 gtk_widget_queue_resize (menu);
877 gtk_tree_menu_set_area (GtkTreeMenu *menu,
880 GtkTreeMenuPrivate *priv = menu->priv;
883 g_object_unref (priv->area);
888 g_object_ref_sink (priv->area);
892 menu_occupied (GtkTreeMenu *menu,
900 for (i = GTK_MENU_SHELL (menu)->children; i; i = i->next)
904 gtk_container_child_get (GTK_CONTAINER (menu),
912 /* look if this item intersects with the given coordinates */
913 if (right_attach > l && left_attach < r && bottom_attach > t && top_attach < b)
921 relayout_item (GtkTreeMenu *menu,
926 GtkTreeMenuPrivate *priv = menu->priv;
927 gint current_col = 0, current_row = 0;
928 gint rows = 1, cols = 1;
930 if (priv->col_span_col == -1 &&
931 priv->row_span_col == -1 &&
934 gtk_container_child_get (GTK_CONTAINER (menu), prev,
935 "right-attach", ¤t_col,
936 "top-attach", ¤t_row,
938 if (current_col + cols > priv->wrap_width)
946 if (priv->col_span_col != -1)
947 gtk_tree_model_get (priv->model, iter,
948 priv->col_span_col, &cols,
950 if (priv->row_span_col != -1)
951 gtk_tree_model_get (priv->model, iter,
952 priv->row_span_col, &rows,
957 if (current_col + cols > priv->wrap_width)
963 if (!menu_occupied (menu,
964 current_col, current_col + cols,
965 current_row, current_row + rows))
972 /* set attach props */
973 gtk_menu_attach (GTK_MENU (menu), item,
974 current_col, current_col + cols,
975 current_row, current_row + rows);
979 gtk_tree_menu_create_item (GtkTreeMenu *menu,
981 gboolean header_item)
983 GtkTreeMenuPrivate *priv = menu->priv;
984 GtkWidget *item, *view;
986 gboolean is_separator = FALSE;
988 path = gtk_tree_model_get_path (priv->model, iter);
990 if (priv->row_separator_func)
992 priv->row_separator_func (priv->model, iter,
993 priv->row_separator_data);
997 item = gtk_separator_menu_item_new ();
999 g_object_set_qdata_full (G_OBJECT (item),
1000 tree_menu_path_quark,
1001 gtk_tree_row_reference_new (priv->model, path),
1002 (GDestroyNotify)gtk_tree_row_reference_free);
1006 view = gtk_cell_view_new_with_context (priv->area, priv->context);
1007 item = gtk_menu_item_new ();
1008 gtk_widget_show (view);
1009 gtk_widget_show (item);
1011 gtk_cell_view_set_model (GTK_CELL_VIEW (view), priv->model);
1012 gtk_cell_view_set_displayed_row (GTK_CELL_VIEW (view), path);
1014 gtk_widget_show (view);
1015 gtk_container_add (GTK_CONTAINER (item), view);
1017 g_signal_connect (item, "activate", G_CALLBACK (item_activated_cb), menu);
1019 /* Add a GtkTreeMenu submenu to render the children of this row */
1020 if (header_item == FALSE &&
1021 gtk_tree_model_iter_has_child (priv->model, iter))
1025 submenu = gtk_tree_menu_new_with_area (priv->area);
1027 gtk_tree_menu_set_row_separator_func (GTK_TREE_MENU (submenu),
1028 priv->row_separator_func,
1029 priv->row_separator_data,
1030 priv->row_separator_destroy);
1031 gtk_tree_menu_set_header_func (GTK_TREE_MENU (submenu),
1034 priv->header_destroy);
1036 gtk_tree_menu_set_model (GTK_TREE_MENU (submenu), priv->model);
1037 gtk_tree_menu_set_root (GTK_TREE_MENU (submenu), path);
1039 gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), submenu);
1041 g_signal_connect (submenu, "menu-activate",
1042 G_CALLBACK (submenu_activated_cb), menu);
1046 gtk_tree_path_free (path);
1052 rebuild_menu (GtkTreeMenu *menu)
1054 GtkTreeMenuPrivate *priv = menu->priv;
1056 /* Destroy all the menu items */
1057 gtk_container_foreach (GTK_CONTAINER (menu),
1058 (GtkCallback) gtk_widget_destroy, NULL);
1062 gtk_tree_menu_populate (menu);
1067 gtk_tree_menu_populate (GtkTreeMenu *menu)
1069 GtkTreeMenuPrivate *priv = menu->priv;
1070 GtkTreePath *path = NULL;
1073 gboolean valid = FALSE;
1074 GtkWidget *menu_item, *prev = NULL;
1080 path = gtk_tree_row_reference_get_path (priv->root);
1084 if (gtk_tree_model_get_iter (priv->model, &parent, path))
1086 valid = gtk_tree_model_iter_children (priv->model, &iter, &parent);
1088 if (priv->header_func &&
1089 priv->header_func (priv->model, &parent, priv->header_data))
1091 /* Add a submenu header for rows which desire one, used for
1092 * combo boxes to allow all rows to be activatable/selectable
1094 menu_item = gtk_tree_menu_create_item (menu, &parent, TRUE);
1095 gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);
1097 menu_item = gtk_separator_menu_item_new ();
1098 gtk_widget_show (menu_item);
1099 gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);
1102 priv->menu_with_header = TRUE;
1105 gtk_tree_path_free (path);
1109 /* Tearoff menu items only go in the root menu */
1112 menu_item = gtk_tearoff_menu_item_new ();
1113 gtk_widget_show (menu_item);
1115 if (priv->wrap_width > 0)
1116 gtk_menu_attach (GTK_MENU (menu), menu_item, 0, priv->wrap_width, 0, 1);
1118 gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);
1123 valid = gtk_tree_model_iter_children (priv->model, &iter, NULL);
1126 /* Create a menu item for every row at the current depth, add a GtkTreeMenu
1127 * submenu for iters/items that have children */
1130 menu_item = gtk_tree_menu_create_item (menu, &iter, FALSE);
1132 gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);
1134 if (priv->wrap_width > 0)
1135 relayout_item (menu, menu_item, &iter, prev);
1138 valid = gtk_tree_model_iter_next (priv->model, &iter);
1143 item_activated_cb (GtkMenuItem *item,
1150 /* Only activate leafs, not parents */
1151 if (!gtk_menu_item_get_submenu (item))
1153 view = GTK_CELL_VIEW (gtk_bin_get_child (GTK_BIN (item)));
1154 path = gtk_cell_view_get_displayed_row (view);
1155 path_str = gtk_tree_path_to_string (path);
1157 g_signal_emit (menu, tree_menu_signals[SIGNAL_MENU_ACTIVATE], 0, path_str);
1160 gtk_tree_path_free (path);
1165 submenu_activated_cb (GtkTreeMenu *submenu,
1169 g_signal_emit (menu, tree_menu_signals[SIGNAL_MENU_ACTIVATE], 0, path);
1172 /****************************************************************
1174 ****************************************************************/
1176 gtk_tree_menu_new (void)
1178 return (GtkWidget *)g_object_new (GTK_TYPE_TREE_MENU, NULL);
1182 gtk_tree_menu_new_with_area (GtkCellArea *area)
1184 return (GtkWidget *)g_object_new (GTK_TYPE_TREE_MENU,
1190 gtk_tree_menu_new_full (GtkCellArea *area,
1191 GtkTreeModel *model,
1194 return (GtkWidget *)g_object_new (GTK_TYPE_TREE_MENU,
1202 gtk_tree_menu_set_model (GtkTreeMenu *menu,
1203 GtkTreeModel *model)
1205 GtkTreeMenuPrivate *priv;
1207 g_return_if_fail (GTK_IS_TREE_MENU (menu));
1208 g_return_if_fail (model == NULL || GTK_IS_TREE_MODEL (model));
1212 if (priv->model != model)
1216 /* Disconnect signals */
1217 g_signal_handler_disconnect (priv->model,
1218 priv->row_inserted_id);
1219 g_signal_handler_disconnect (priv->model,
1220 priv->row_deleted_id);
1221 g_signal_handler_disconnect (priv->model,
1222 priv->row_reordered_id);
1223 g_signal_handler_disconnect (priv->model,
1224 priv->row_changed_id);
1225 priv->row_inserted_id = 0;
1226 priv->row_deleted_id = 0;
1227 priv->row_reordered_id = 0;
1228 priv->row_changed_id = 0;
1230 g_object_unref (priv->model);
1233 priv->model = model;
1237 g_object_ref (priv->model);
1239 /* Connect signals */
1240 priv->row_inserted_id = g_signal_connect (priv->model, "row-inserted",
1241 G_CALLBACK (row_inserted_cb), menu);
1242 priv->row_deleted_id = g_signal_connect (priv->model, "row-deleted",
1243 G_CALLBACK (row_deleted_cb), menu);
1244 priv->row_reordered_id = g_signal_connect (priv->model, "rows-reordered",
1245 G_CALLBACK (row_reordered_cb), menu);
1246 priv->row_changed_id = g_signal_connect (priv->model, "row-changed",
1247 G_CALLBACK (row_changed_cb), menu);
1253 gtk_tree_menu_get_model (GtkTreeMenu *menu)
1255 GtkTreeMenuPrivate *priv;
1257 g_return_val_if_fail (GTK_IS_TREE_MENU (menu), NULL);
1265 gtk_tree_menu_set_root (GtkTreeMenu *menu,
1268 GtkTreeMenuPrivate *priv;
1270 g_return_if_fail (GTK_IS_TREE_MENU (menu));
1271 g_return_if_fail (menu->priv->model != NULL || path == NULL);
1276 gtk_tree_row_reference_free (priv->root);
1279 priv->root = gtk_tree_row_reference_new (priv->model, path);
1283 rebuild_menu (menu);
1287 gtk_tree_menu_get_root (GtkTreeMenu *menu)
1289 GtkTreeMenuPrivate *priv;
1291 g_return_val_if_fail (GTK_IS_TREE_MENU (menu), NULL);
1296 return gtk_tree_row_reference_get_path (priv->root);
1302 gtk_tree_menu_get_tearoff (GtkTreeMenu *menu)
1304 GtkTreeMenuPrivate *priv;
1306 g_return_val_if_fail (GTK_IS_TREE_MENU (menu), FALSE);
1310 return priv->tearoff;
1314 gtk_tree_menu_set_tearoff (GtkTreeMenu *menu,
1317 GtkTreeMenuPrivate *priv;
1319 g_return_if_fail (GTK_IS_TREE_MENU (menu));
1323 if (priv->tearoff != tearoff)
1325 priv->tearoff = tearoff;
1327 rebuild_menu (menu);
1329 g_object_notify (G_OBJECT (menu), "tearoff");
1334 gtk_tree_menu_get_wrap_width (GtkTreeMenu *menu)
1336 GtkTreeMenuPrivate *priv;
1338 g_return_val_if_fail (GTK_IS_TREE_MENU (menu), FALSE);
1342 return priv->wrap_width;
1346 gtk_tree_menu_set_wrap_width (GtkTreeMenu *menu,
1349 GtkTreeMenuPrivate *priv;
1351 g_return_if_fail (GTK_IS_TREE_MENU (menu));
1352 g_return_if_fail (width >= 0);
1356 if (priv->wrap_width != width)
1358 priv->wrap_width = width;
1360 rebuild_menu (menu);
1362 g_object_notify (G_OBJECT (menu), "wrap-width");
1367 gtk_tree_menu_get_row_span_column (GtkTreeMenu *menu)
1369 GtkTreeMenuPrivate *priv;
1371 g_return_val_if_fail (GTK_IS_TREE_MENU (menu), FALSE);
1375 return priv->row_span_col;
1379 gtk_tree_menu_set_row_span_column (GtkTreeMenu *menu,
1382 GtkTreeMenuPrivate *priv;
1384 g_return_if_fail (GTK_IS_TREE_MENU (menu));
1388 if (priv->row_span_col != row_span)
1390 priv->row_span_col = row_span;
1392 if (priv->wrap_width > 0)
1393 rebuild_menu (menu);
1395 g_object_notify (G_OBJECT (menu), "row-span-column");
1400 gtk_tree_menu_get_column_span_column (GtkTreeMenu *menu)
1402 GtkTreeMenuPrivate *priv;
1404 g_return_val_if_fail (GTK_IS_TREE_MENU (menu), FALSE);
1408 return priv->col_span_col;
1412 gtk_tree_menu_set_column_span_column (GtkTreeMenu *menu,
1415 GtkTreeMenuPrivate *priv;
1417 g_return_if_fail (GTK_IS_TREE_MENU (menu));
1421 if (priv->col_span_col != column_span)
1423 priv->col_span_col = column_span;
1425 if (priv->wrap_width > 0)
1426 rebuild_menu (menu);
1428 g_object_notify (G_OBJECT (menu), "column-span-column");
1432 GtkTreeViewRowSeparatorFunc
1433 gtk_tree_menu_get_row_separator_func (GtkTreeMenu *menu)
1435 GtkTreeMenuPrivate *priv;
1437 g_return_val_if_fail (GTK_IS_TREE_MENU (menu), NULL);
1441 return priv->row_separator_func;
1445 gtk_tree_menu_set_row_separator_func (GtkTreeMenu *menu,
1446 GtkTreeViewRowSeparatorFunc func,
1448 GDestroyNotify destroy)
1450 GtkTreeMenuPrivate *priv;
1452 g_return_if_fail (GTK_IS_TREE_MENU (menu));
1456 if (priv->row_separator_destroy)
1457 priv->row_separator_destroy (priv->row_separator_data);
1459 priv->row_separator_func = func;
1460 priv->row_separator_data = data;
1461 priv->row_separator_destroy = destroy;
1463 rebuild_menu (menu);
1466 GtkTreeMenuHeaderFunc
1467 gtk_tree_menu_get_header_func (GtkTreeMenu *menu)
1469 GtkTreeMenuPrivate *priv;
1471 g_return_val_if_fail (GTK_IS_TREE_MENU (menu), NULL);
1475 return priv->header_func;
1479 gtk_tree_menu_set_header_func (GtkTreeMenu *menu,
1480 GtkTreeMenuHeaderFunc func,
1482 GDestroyNotify destroy)
1484 GtkTreeMenuPrivate *priv;
1486 g_return_if_fail (GTK_IS_TREE_MENU (menu));
1490 if (priv->header_destroy)
1491 priv->header_destroy (priv->header_data);
1493 priv->header_func = func;
1494 priv->header_data = data;
1495 priv->header_destroy = destroy;
1497 rebuild_menu (menu);