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 gboolean gtk_tree_menu_path_in_menu (GtkTreeMenu *menu,
87 gboolean *header_item);
88 static void row_inserted_cb (GtkTreeModel *model,
92 static void row_deleted_cb (GtkTreeModel *model,
95 static void row_reordered_cb (GtkTreeModel *model,
100 static void row_changed_cb (GtkTreeModel *model,
104 static void context_size_changed_cb (GtkCellAreaContext *context,
107 static void area_apply_attributes_cb (GtkCellArea *area,
108 GtkTreeModel *tree_model,
110 gboolean is_expander,
111 gboolean is_expanded,
113 static void item_activated_cb (GtkMenuItem *item,
115 static void submenu_activated_cb (GtkTreeMenu *submenu,
121 struct _GtkTreeMenuPrivate
123 /* TreeModel and parent for this menu */
125 GtkTreeRowReference *root;
127 /* CellArea and context for this menu */
129 GtkCellAreaContext *context;
132 gulong size_changed_id;
133 gulong apply_attributes_id;
134 gulong row_inserted_id;
135 gulong row_deleted_id;
136 gulong row_reordered_id;
137 gulong row_changed_id;
145 guint32 menu_with_header : 1;
149 GtkTreeViewRowSeparatorFunc row_separator_func;
150 gpointer row_separator_data;
151 GDestroyNotify row_separator_destroy;
153 /* Submenu headers */
154 GtkTreeMenuHeaderFunc header_func;
155 gpointer header_data;
156 GDestroyNotify header_destroy;
171 SIGNAL_MENU_ACTIVATE,
175 static guint tree_menu_signals[N_SIGNALS] = { 0 };
176 static GQuark tree_menu_path_quark = 0;
178 G_DEFINE_TYPE_WITH_CODE (GtkTreeMenu, gtk_tree_menu, GTK_TYPE_MENU,
179 G_IMPLEMENT_INTERFACE (GTK_TYPE_CELL_LAYOUT,
180 gtk_tree_menu_cell_layout_init));
183 gtk_tree_menu_init (GtkTreeMenu *menu)
185 GtkTreeMenuPrivate *priv;
187 menu->priv = G_TYPE_INSTANCE_GET_PRIVATE (menu,
195 priv->context = NULL;
197 priv->size_changed_id = 0;
198 priv->row_inserted_id = 0;
199 priv->row_deleted_id = 0;
200 priv->row_reordered_id = 0;
201 priv->row_changed_id = 0;
203 priv->wrap_width = 0;
204 priv->row_span_col = -1;
205 priv->col_span_col = -1;
207 priv->menu_with_header = FALSE;
208 priv->tearoff = FALSE;
210 priv->row_separator_func = NULL;
211 priv->row_separator_data = NULL;
212 priv->row_separator_destroy = NULL;
214 priv->header_func = NULL;
215 priv->header_data = NULL;
216 priv->header_destroy = NULL;
218 gtk_menu_set_reserve_toggle_size (GTK_MENU (menu), FALSE);
222 gtk_tree_menu_class_init (GtkTreeMenuClass *class)
224 GObjectClass *object_class = G_OBJECT_CLASS (class);
225 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
227 tree_menu_path_quark = g_quark_from_static_string ("gtk-tree-menu-path");
229 object_class->constructor = gtk_tree_menu_constructor;
230 object_class->dispose = gtk_tree_menu_dispose;
231 object_class->finalize = gtk_tree_menu_finalize;
232 object_class->set_property = gtk_tree_menu_set_property;
233 object_class->get_property = gtk_tree_menu_get_property;
235 widget_class->get_preferred_width = gtk_tree_menu_get_preferred_width;
236 widget_class->get_preferred_height = gtk_tree_menu_get_preferred_height;
238 tree_menu_signals[SIGNAL_MENU_ACTIVATE] =
239 g_signal_new (I_("menu-activate"),
240 G_OBJECT_CLASS_TYPE (object_class),
242 0, /* No class closure here */
244 _gtk_marshal_VOID__STRING,
245 G_TYPE_NONE, 1, G_TYPE_STRING);
247 g_object_class_install_property (object_class,
249 g_param_spec_object ("model",
250 P_("TreeMenu model"),
251 P_("The model for the tree menu"),
253 GTK_PARAM_READWRITE));
255 g_object_class_install_property (object_class,
257 g_param_spec_boxed ("root",
258 P_("TreeMenu root row"),
259 P_("The TreeMenu will display children of the "
262 GTK_PARAM_READWRITE));
264 g_object_class_install_property (object_class,
266 g_param_spec_object ("cell-area",
268 P_("The GtkCellArea used to layout cells"),
270 GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
272 g_object_class_install_property (object_class,
274 g_param_spec_boolean ("tearoff",
276 P_("Whether the menu has a tearoff item"),
278 GTK_PARAM_READWRITE));
281 * GtkTreeMenu:wrap-width:
283 * If wrap-width is set to a positive value, the list will be
284 * displayed in multiple columns, the number of columns is
285 * determined by wrap-width.
289 g_object_class_install_property (object_class,
291 g_param_spec_int ("wrap-width",
293 P_("Wrap width for laying out items in a grid"),
297 GTK_PARAM_READWRITE));
300 * GtkTreeMenu:row-span-column:
302 * If this is set to a non-negative value, it must be the index of a column
303 * of type %G_TYPE_INT in the model.
305 * The values of that column are used to determine how many rows a value in
306 * the list will span. Therefore, the values in the model column pointed to
307 * by this property must be greater than zero and not larger than wrap-width.
311 g_object_class_install_property (object_class,
313 g_param_spec_int ("row-span-column",
314 P_("Row span column"),
315 P_("TreeModel column containing the row span values"),
319 GTK_PARAM_READWRITE));
322 * GtkTreeMenu:column-span-column:
324 * If this is set to a non-negative value, it must be the index of a column
325 * of type %G_TYPE_INT in the model.
327 * The values of that column are used to determine how many columns a value
328 * in the list will span.
332 g_object_class_install_property (object_class,
334 g_param_spec_int ("column-span-column",
335 P_("Column span column"),
336 P_("TreeModel column containing the column span values"),
340 GTK_PARAM_READWRITE));
342 g_type_class_add_private (object_class, sizeof (GtkTreeMenuPrivate));
345 /****************************************************************
347 ****************************************************************/
349 gtk_tree_menu_constructor (GType type,
350 guint n_construct_properties,
351 GObjectConstructParam *construct_properties)
355 GtkTreeMenuPrivate *priv;
357 object = G_OBJECT_CLASS (gtk_tree_menu_parent_class)->constructor
358 (type, n_construct_properties, construct_properties);
360 menu = GTK_TREE_MENU (object);
365 GtkCellArea *area = gtk_cell_area_box_new ();
367 gtk_tree_menu_set_area (menu, area);
370 priv->context = gtk_cell_area_create_context (priv->area);
372 priv->size_changed_id =
373 g_signal_connect (priv->context, "notify",
374 G_CALLBACK (context_size_changed_cb), menu);
380 gtk_tree_menu_dispose (GObject *object)
383 GtkTreeMenuPrivate *priv;
385 menu = GTK_TREE_MENU (object);
388 gtk_tree_menu_set_model (menu, NULL);
389 gtk_tree_menu_set_area (menu, NULL);
393 /* Disconnect signals */
394 g_signal_handler_disconnect (priv->context, priv->size_changed_id);
396 g_object_unref (priv->context);
397 priv->context = NULL;
398 priv->size_changed_id = 0;
401 G_OBJECT_CLASS (gtk_tree_menu_parent_class)->dispose (object);
405 gtk_tree_menu_finalize (GObject *object)
408 GtkTreeMenuPrivate *priv;
410 menu = GTK_TREE_MENU (object);
413 gtk_tree_menu_set_row_separator_func (menu, NULL, NULL, NULL);
414 gtk_tree_menu_set_header_func (menu, NULL, NULL, NULL);
417 gtk_tree_row_reference_free (priv->root);
419 G_OBJECT_CLASS (gtk_tree_menu_parent_class)->finalize (object);
423 gtk_tree_menu_set_property (GObject *object,
428 GtkTreeMenu *menu = GTK_TREE_MENU (object);
433 gtk_tree_menu_set_model (menu, g_value_get_object (value));
437 gtk_tree_menu_set_root (menu, g_value_get_boxed (value));
441 /* Construct-only, can only be assigned once */
442 gtk_tree_menu_set_area (menu, (GtkCellArea *)g_value_get_object (value));
446 gtk_tree_menu_set_tearoff (menu, g_value_get_boolean (value));
449 case PROP_WRAP_WIDTH:
450 gtk_tree_menu_set_wrap_width (menu, g_value_get_int (value));
453 case PROP_ROW_SPAN_COL:
454 gtk_tree_menu_set_row_span_column (menu, g_value_get_int (value));
457 case PROP_COL_SPAN_COL:
458 gtk_tree_menu_set_column_span_column (menu, g_value_get_int (value));
462 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
468 gtk_tree_menu_get_property (GObject *object,
473 GtkTreeMenu *menu = GTK_TREE_MENU (object);
474 GtkTreeMenuPrivate *priv = menu->priv;
479 g_value_set_object (value, priv->model);
483 g_value_set_boxed (value, priv->root);
487 g_value_set_object (value, priv->area);
491 g_value_set_boolean (value, priv->tearoff);
495 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
500 /****************************************************************
502 ****************************************************************/
504 /* We tell all the menu items to reserve space for the submenu
505 * indicator if there is at least one submenu, this way we ensure
506 * that every internal cell area gets allocated the
507 * same width (and requested height for the same appropriate width).
510 sync_reserve_submenu_size (GtkTreeMenu *menu)
513 gboolean has_submenu = FALSE;
515 children = gtk_container_get_children (GTK_CONTAINER (menu));
516 for (l = children; l; l = l->next)
518 GtkMenuItem *item = l->data;
520 if (gtk_menu_item_get_submenu (item) != NULL)
527 for (l = children; l; l = l->next)
529 GtkMenuItem *item = l->data;
531 gtk_menu_item_set_reserve_indicator (item, has_submenu);
534 g_list_free (children);
538 gtk_tree_menu_get_preferred_width (GtkWidget *widget,
542 GtkTreeMenu *menu = GTK_TREE_MENU (widget);
543 GtkTreeMenuPrivate *priv = menu->priv;
545 /* We leave the requesting work up to the cellviews which operate in the same
546 * context, reserving space for the submenu indicator if any of the items have
547 * submenus ensures that every cellview will receive the same allocated width.
549 * Since GtkMenu does hieght-for-width correctly, we know that the width of
550 * every cell will be requested before the height-for-widths are requested.
552 g_signal_handler_block (priv->context, priv->size_changed_id);
554 sync_reserve_submenu_size (menu);
556 GTK_WIDGET_CLASS (gtk_tree_menu_parent_class)->get_preferred_width (widget, minimum_size, natural_size);
558 g_signal_handler_unblock (priv->context, priv->size_changed_id);
562 gtk_tree_menu_get_preferred_height (GtkWidget *widget,
566 GtkTreeMenu *menu = GTK_TREE_MENU (widget);
567 GtkTreeMenuPrivate *priv = menu->priv;
569 g_signal_handler_block (priv->context, priv->size_changed_id);
571 sync_reserve_submenu_size (menu);
573 GTK_WIDGET_CLASS (gtk_tree_menu_parent_class)->get_preferred_height (widget, minimum_size, natural_size);
575 g_signal_handler_unblock (priv->context, priv->size_changed_id);
578 /****************************************************************
579 * GtkCellLayoutIface *
580 ****************************************************************/
582 gtk_tree_menu_cell_layout_init (GtkCellLayoutIface *iface)
584 iface->get_area = gtk_tree_menu_cell_layout_get_area;
588 gtk_tree_menu_cell_layout_get_area (GtkCellLayout *layout)
590 GtkTreeMenu *menu = GTK_TREE_MENU (layout);
591 GtkTreeMenuPrivate *priv = menu->priv;
597 /****************************************************************
598 * TreeModel callbacks/populating menus *
599 ****************************************************************/
601 gtk_tree_menu_get_path_item (GtkTreeMenu *menu,
604 GtkWidget *item = NULL;
607 children = gtk_container_get_children (GTK_CONTAINER (menu));
609 for (l = children; item == NULL && l != NULL; l = l->next)
611 GtkWidget *child = l->data;
612 GtkTreePath *path = NULL;
614 if (GTK_IS_SEPARATOR_MENU_ITEM (child))
616 GtkTreeRowReference *row =
617 g_object_get_qdata (G_OBJECT (child), tree_menu_path_quark);
619 if (row && gtk_tree_row_reference_valid (row))
620 path = gtk_tree_row_reference_get_path (row);
624 GtkWidget *view = gtk_bin_get_child (GTK_BIN (child));
626 /* It's always a cellview */
627 if (GTK_IS_CELL_VIEW (view))
628 path = gtk_cell_view_get_displayed_row (GTK_CELL_VIEW (view));
633 if (gtk_tree_path_compare (search, path) == 0)
636 gtk_tree_path_free (path);
640 g_list_free (children);
646 gtk_tree_menu_path_in_menu (GtkTreeMenu *menu,
648 gboolean *header_item)
650 GtkTreeMenuPrivate *priv = menu->priv;
651 GtkTreePath *parent_path = gtk_tree_path_copy (path);
652 gboolean in_menu = FALSE;
653 gboolean is_header = FALSE;
655 /* Check if the is in root of the model */
656 if (gtk_tree_path_get_depth (parent_path) == 1)
661 /* If we are a submenu, compare the parent path */
662 else if (priv->root && gtk_tree_path_up (parent_path))
664 GtkTreePath *root_path =
665 gtk_tree_row_reference_get_path (priv->root);
667 if (gtk_tree_path_compare (root_path, parent_path) == 0)
670 if (!in_menu && priv->menu_with_header &&
671 gtk_tree_path_compare (root_path, path) == 0)
677 gtk_tree_path_free (root_path);
680 gtk_tree_path_free (parent_path);
683 *header_item = is_header;
689 row_inserted_cb (GtkTreeModel *model,
694 GtkTreeMenuPrivate *priv = menu->priv;
695 gint *indices, index, depth;
697 /* If the iter should be in this menu then go ahead and insert it */
698 if (gtk_tree_menu_path_in_menu (menu, path, NULL))
702 if (priv->wrap_width > 0)
706 /* Get the index of the path for this depth */
707 indices = gtk_tree_path_get_indices (path);
708 depth = gtk_tree_path_get_depth (path);
709 index = indices[depth -1];
711 /* Menus with a header include a menuitem for it's root node
712 * and a separator menu item */
713 if (priv->menu_with_header)
716 /* Index after the tearoff item for the root menu if
717 * there is a tearoff item
719 if (priv->root == NULL && priv->tearoff)
722 item = gtk_tree_menu_create_item (menu, iter, FALSE);
723 gtk_menu_shell_insert (GTK_MENU_SHELL (menu), item, index);
725 /* Resize everything */
726 gtk_cell_area_context_flush (menu->priv->context);
732 row_deleted_cb (GtkTreeModel *model,
736 GtkTreeMenuPrivate *priv = menu->priv;
738 gboolean header_item;
741 in_menu = gtk_tree_menu_path_in_menu (menu, path, &header_item);
743 /* If it's the header item we leave it to the parent menu
744 * to remove us from its menu
746 if (!header_item && in_menu)
748 item = gtk_tree_menu_get_path_item (menu, path);
751 if (priv->wrap_width > 0)
755 /* Get rid of the deleted item */
756 gtk_widget_destroy (item);
758 /* Resize everything */
759 gtk_cell_area_context_flush (menu->priv->context);
766 row_reordered_cb (GtkTreeModel *model,
772 GtkTreeMenuPrivate *priv = menu->priv;
773 gboolean this_menu = FALSE;
775 if (path == NULL && priv->root == NULL)
779 GtkTreePath *root_path =
780 gtk_tree_row_reference_get_path (priv->root);
782 if (gtk_tree_path_compare (root_path, path) == 0)
785 gtk_tree_path_free (root_path);
793 menu_item_position (GtkTreeMenu *menu,
799 children = gtk_container_get_children (GTK_CONTAINER (menu));
800 for (position = 0, l = children; l; position++, l = l->next)
802 GtkWidget *iitem = l->data;
808 g_list_free (children);
814 row_changed_cb (GtkTreeModel *model,
819 GtkTreeMenuPrivate *priv = menu->priv;
820 gboolean is_separator = FALSE;
821 gboolean has_header = FALSE;
824 item = gtk_tree_menu_get_path_item (menu, path);
828 GtkTreePath *root_path =
829 gtk_tree_row_reference_get_path (priv->root);
831 if (gtk_tree_path_compare (root_path, path) == 0)
833 if (priv->header_func)
835 priv->header_func (priv->model, iter, priv->header_data);
837 if (has_header && !item)
839 item = gtk_separator_menu_item_new ();
840 gtk_widget_show (item);
841 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
843 item = gtk_tree_menu_create_item (menu, iter, TRUE);
844 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
846 priv->menu_with_header = TRUE;
848 else if (!has_header && item)
850 /* Destroy the header item and then the following separator */
851 gtk_widget_destroy (item);
852 gtk_widget_destroy (GTK_MENU_SHELL (menu)->children->data);
854 priv->menu_with_header = FALSE;
858 gtk_tree_path_free (root_path);
863 if (priv->wrap_width > 0)
864 /* Ugly, we need to rebuild the menu here if
865 * the row-span/row-column values change
870 if (priv->row_separator_func)
872 priv->row_separator_func (model, iter,
873 priv->row_separator_data);
876 if (is_separator != GTK_IS_SEPARATOR_MENU_ITEM (item))
878 gint position = menu_item_position (menu, item);
880 gtk_widget_destroy (item);
881 item = gtk_tree_menu_create_item (menu, iter, FALSE);
882 gtk_menu_shell_insert (GTK_MENU_SHELL (menu), item, position);
889 context_size_changed_cb (GtkCellAreaContext *context,
893 if (!strcmp (pspec->name, "minimum-width") ||
894 !strcmp (pspec->name, "natural-width") ||
895 !strcmp (pspec->name, "minimum-height") ||
896 !strcmp (pspec->name, "natural-height"))
897 gtk_widget_queue_resize (menu);
901 area_is_sensitive (GtkCellArea *area)
904 gboolean sensitive = FALSE;
906 cells = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (area));
908 for (list = cells; list; list = list->next)
910 g_object_get (list->data, "sensitive", &sensitive, NULL);
921 area_apply_attributes_cb (GtkCellArea *area,
922 GtkTreeModel *tree_model,
924 gboolean is_expander,
925 gboolean is_expanded,
928 /* If the menu for this iter has a submenu */
929 GtkTreeMenuPrivate *priv = menu->priv;
935 path = gtk_tree_model_get_path (tree_model, iter);
937 if (gtk_tree_menu_path_in_menu (menu, path, &is_header))
939 item = gtk_tree_menu_get_path_item (menu, path);
941 /* If there is no submenu, go ahead and update item sensitivity,
942 * items with submenus are always sensitive */
943 if (!gtk_menu_item_get_submenu (GTK_MENU_ITEM (item)))
945 sensitive = area_is_sensitive (priv->area);
947 gtk_widget_set_sensitive (item, sensitive);
951 /* For header items we need to set the sensitivity
952 * of the following separator item
954 if (GTK_MENU_SHELL (menu)->children &&
955 GTK_MENU_SHELL (menu)->children->next)
957 GtkWidget *separator =
958 GTK_MENU_SHELL (menu)->children->next->data;
960 gtk_widget_set_sensitive (separator, sensitive);
966 gtk_tree_path_free (path);
970 gtk_tree_menu_set_area (GtkTreeMenu *menu,
973 GtkTreeMenuPrivate *priv = menu->priv;
977 g_signal_handler_disconnect (priv->area,
978 priv->apply_attributes_id);
979 priv->apply_attributes_id = 0;
981 g_object_unref (priv->area);
988 g_object_ref_sink (priv->area);
990 priv->apply_attributes_id =
991 g_signal_connect (priv->area, "apply-attributes",
992 G_CALLBACK (area_apply_attributes_cb), menu);
997 menu_occupied (GtkTreeMenu *menu,
1001 guint bottom_attach)
1005 for (i = GTK_MENU_SHELL (menu)->children; i; i = i->next)
1009 gtk_container_child_get (GTK_CONTAINER (menu),
1013 "bottom-attach", &b,
1017 /* look if this item intersects with the given coordinates */
1018 if (right_attach > l && left_attach < r && bottom_attach > t && top_attach < b)
1026 relayout_item (GtkTreeMenu *menu,
1031 GtkTreeMenuPrivate *priv = menu->priv;
1032 gint current_col = 0, current_row = 0;
1033 gint rows = 1, cols = 1;
1035 if (priv->col_span_col == -1 &&
1036 priv->row_span_col == -1 &&
1039 gtk_container_child_get (GTK_CONTAINER (menu), prev,
1040 "right-attach", ¤t_col,
1041 "top-attach", ¤t_row,
1043 if (current_col + cols > priv->wrap_width)
1051 if (priv->col_span_col != -1)
1052 gtk_tree_model_get (priv->model, iter,
1053 priv->col_span_col, &cols,
1055 if (priv->row_span_col != -1)
1056 gtk_tree_model_get (priv->model, iter,
1057 priv->row_span_col, &rows,
1062 if (current_col + cols > priv->wrap_width)
1068 if (!menu_occupied (menu,
1069 current_col, current_col + cols,
1070 current_row, current_row + rows))
1077 /* set attach props */
1078 gtk_menu_attach (GTK_MENU (menu), item,
1079 current_col, current_col + cols,
1080 current_row, current_row + rows);
1084 gtk_tree_menu_create_item (GtkTreeMenu *menu,
1086 gboolean header_item)
1088 GtkTreeMenuPrivate *priv = menu->priv;
1089 GtkWidget *item, *view;
1091 gboolean is_separator = FALSE;
1093 path = gtk_tree_model_get_path (priv->model, iter);
1095 if (priv->row_separator_func)
1097 priv->row_separator_func (priv->model, iter,
1098 priv->row_separator_data);
1102 item = gtk_separator_menu_item_new ();
1104 g_object_set_qdata_full (G_OBJECT (item),
1105 tree_menu_path_quark,
1106 gtk_tree_row_reference_new (priv->model, path),
1107 (GDestroyNotify)gtk_tree_row_reference_free);
1111 view = gtk_cell_view_new_with_context (priv->area, priv->context);
1112 item = gtk_menu_item_new ();
1113 gtk_widget_show (view);
1114 gtk_widget_show (item);
1116 gtk_cell_view_set_model (GTK_CELL_VIEW (view), priv->model);
1117 gtk_cell_view_set_displayed_row (GTK_CELL_VIEW (view), path);
1119 gtk_widget_show (view);
1120 gtk_container_add (GTK_CONTAINER (item), view);
1122 g_signal_connect (item, "activate", G_CALLBACK (item_activated_cb), menu);
1124 /* Add a GtkTreeMenu submenu to render the children of this row */
1125 if (header_item == FALSE &&
1126 gtk_tree_model_iter_has_child (priv->model, iter))
1130 submenu = gtk_tree_menu_new_with_area (priv->area);
1132 gtk_tree_menu_set_row_separator_func (GTK_TREE_MENU (submenu),
1133 priv->row_separator_func,
1134 priv->row_separator_data,
1135 priv->row_separator_destroy);
1136 gtk_tree_menu_set_header_func (GTK_TREE_MENU (submenu),
1139 priv->header_destroy);
1141 gtk_tree_menu_set_model (GTK_TREE_MENU (submenu), priv->model);
1142 gtk_tree_menu_set_root (GTK_TREE_MENU (submenu), path);
1144 gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), submenu);
1146 g_signal_connect (submenu, "menu-activate",
1147 G_CALLBACK (submenu_activated_cb), menu);
1151 gtk_tree_path_free (path);
1157 rebuild_menu (GtkTreeMenu *menu)
1159 GtkTreeMenuPrivate *priv = menu->priv;
1161 /* Destroy all the menu items */
1162 gtk_container_foreach (GTK_CONTAINER (menu),
1163 (GtkCallback) gtk_widget_destroy, NULL);
1167 gtk_tree_menu_populate (menu);
1172 gtk_tree_menu_populate (GtkTreeMenu *menu)
1174 GtkTreeMenuPrivate *priv = menu->priv;
1175 GtkTreePath *path = NULL;
1178 gboolean valid = FALSE;
1179 GtkWidget *menu_item, *prev = NULL;
1185 path = gtk_tree_row_reference_get_path (priv->root);
1189 if (gtk_tree_model_get_iter (priv->model, &parent, path))
1191 valid = gtk_tree_model_iter_children (priv->model, &iter, &parent);
1193 if (priv->header_func &&
1194 priv->header_func (priv->model, &parent, priv->header_data))
1196 /* Add a submenu header for rows which desire one, used for
1197 * combo boxes to allow all rows to be activatable/selectable
1199 menu_item = gtk_tree_menu_create_item (menu, &parent, TRUE);
1200 gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);
1202 menu_item = gtk_separator_menu_item_new ();
1203 gtk_widget_show (menu_item);
1204 gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);
1207 priv->menu_with_header = TRUE;
1210 gtk_tree_path_free (path);
1214 /* Tearoff menu items only go in the root menu */
1217 menu_item = gtk_tearoff_menu_item_new ();
1218 gtk_widget_show (menu_item);
1220 if (priv->wrap_width > 0)
1221 gtk_menu_attach (GTK_MENU (menu), menu_item, 0, priv->wrap_width, 0, 1);
1223 gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);
1228 valid = gtk_tree_model_iter_children (priv->model, &iter, NULL);
1231 /* Create a menu item for every row at the current depth, add a GtkTreeMenu
1232 * submenu for iters/items that have children */
1235 menu_item = gtk_tree_menu_create_item (menu, &iter, FALSE);
1237 gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);
1239 if (priv->wrap_width > 0)
1240 relayout_item (menu, menu_item, &iter, prev);
1243 valid = gtk_tree_model_iter_next (priv->model, &iter);
1248 item_activated_cb (GtkMenuItem *item,
1255 /* Only activate leafs, not parents */
1256 if (!gtk_menu_item_get_submenu (item))
1258 view = GTK_CELL_VIEW (gtk_bin_get_child (GTK_BIN (item)));
1259 path = gtk_cell_view_get_displayed_row (view);
1260 path_str = gtk_tree_path_to_string (path);
1262 g_signal_emit (menu, tree_menu_signals[SIGNAL_MENU_ACTIVATE], 0, path_str);
1265 gtk_tree_path_free (path);
1270 submenu_activated_cb (GtkTreeMenu *submenu,
1274 g_signal_emit (menu, tree_menu_signals[SIGNAL_MENU_ACTIVATE], 0, path);
1277 /****************************************************************
1279 ****************************************************************/
1281 gtk_tree_menu_new (void)
1283 return (GtkWidget *)g_object_new (GTK_TYPE_TREE_MENU, NULL);
1287 gtk_tree_menu_new_with_area (GtkCellArea *area)
1289 return (GtkWidget *)g_object_new (GTK_TYPE_TREE_MENU,
1295 gtk_tree_menu_new_full (GtkCellArea *area,
1296 GtkTreeModel *model,
1299 return (GtkWidget *)g_object_new (GTK_TYPE_TREE_MENU,
1307 gtk_tree_menu_set_model (GtkTreeMenu *menu,
1308 GtkTreeModel *model)
1310 GtkTreeMenuPrivate *priv;
1312 g_return_if_fail (GTK_IS_TREE_MENU (menu));
1313 g_return_if_fail (model == NULL || GTK_IS_TREE_MODEL (model));
1317 if (priv->model != model)
1321 /* Disconnect signals */
1322 g_signal_handler_disconnect (priv->model,
1323 priv->row_inserted_id);
1324 g_signal_handler_disconnect (priv->model,
1325 priv->row_deleted_id);
1326 g_signal_handler_disconnect (priv->model,
1327 priv->row_reordered_id);
1328 g_signal_handler_disconnect (priv->model,
1329 priv->row_changed_id);
1330 priv->row_inserted_id = 0;
1331 priv->row_deleted_id = 0;
1332 priv->row_reordered_id = 0;
1333 priv->row_changed_id = 0;
1335 g_object_unref (priv->model);
1338 priv->model = model;
1342 g_object_ref (priv->model);
1344 /* Connect signals */
1345 priv->row_inserted_id = g_signal_connect (priv->model, "row-inserted",
1346 G_CALLBACK (row_inserted_cb), menu);
1347 priv->row_deleted_id = g_signal_connect (priv->model, "row-deleted",
1348 G_CALLBACK (row_deleted_cb), menu);
1349 priv->row_reordered_id = g_signal_connect (priv->model, "rows-reordered",
1350 G_CALLBACK (row_reordered_cb), menu);
1351 priv->row_changed_id = g_signal_connect (priv->model, "row-changed",
1352 G_CALLBACK (row_changed_cb), menu);
1358 gtk_tree_menu_get_model (GtkTreeMenu *menu)
1360 GtkTreeMenuPrivate *priv;
1362 g_return_val_if_fail (GTK_IS_TREE_MENU (menu), NULL);
1370 gtk_tree_menu_set_root (GtkTreeMenu *menu,
1373 GtkTreeMenuPrivate *priv;
1375 g_return_if_fail (GTK_IS_TREE_MENU (menu));
1376 g_return_if_fail (menu->priv->model != NULL || path == NULL);
1381 gtk_tree_row_reference_free (priv->root);
1384 priv->root = gtk_tree_row_reference_new (priv->model, path);
1388 rebuild_menu (menu);
1392 gtk_tree_menu_get_root (GtkTreeMenu *menu)
1394 GtkTreeMenuPrivate *priv;
1396 g_return_val_if_fail (GTK_IS_TREE_MENU (menu), NULL);
1401 return gtk_tree_row_reference_get_path (priv->root);
1407 gtk_tree_menu_get_tearoff (GtkTreeMenu *menu)
1409 GtkTreeMenuPrivate *priv;
1411 g_return_val_if_fail (GTK_IS_TREE_MENU (menu), FALSE);
1415 return priv->tearoff;
1419 gtk_tree_menu_set_tearoff (GtkTreeMenu *menu,
1422 GtkTreeMenuPrivate *priv;
1424 g_return_if_fail (GTK_IS_TREE_MENU (menu));
1428 if (priv->tearoff != tearoff)
1430 priv->tearoff = tearoff;
1432 rebuild_menu (menu);
1434 g_object_notify (G_OBJECT (menu), "tearoff");
1439 gtk_tree_menu_get_wrap_width (GtkTreeMenu *menu)
1441 GtkTreeMenuPrivate *priv;
1443 g_return_val_if_fail (GTK_IS_TREE_MENU (menu), FALSE);
1447 return priv->wrap_width;
1451 gtk_tree_menu_set_wrap_width (GtkTreeMenu *menu,
1454 GtkTreeMenuPrivate *priv;
1456 g_return_if_fail (GTK_IS_TREE_MENU (menu));
1457 g_return_if_fail (width >= 0);
1461 if (priv->wrap_width != width)
1463 priv->wrap_width = width;
1465 rebuild_menu (menu);
1467 g_object_notify (G_OBJECT (menu), "wrap-width");
1472 gtk_tree_menu_get_row_span_column (GtkTreeMenu *menu)
1474 GtkTreeMenuPrivate *priv;
1476 g_return_val_if_fail (GTK_IS_TREE_MENU (menu), FALSE);
1480 return priv->row_span_col;
1484 gtk_tree_menu_set_row_span_column (GtkTreeMenu *menu,
1487 GtkTreeMenuPrivate *priv;
1489 g_return_if_fail (GTK_IS_TREE_MENU (menu));
1493 if (priv->row_span_col != row_span)
1495 priv->row_span_col = row_span;
1497 if (priv->wrap_width > 0)
1498 rebuild_menu (menu);
1500 g_object_notify (G_OBJECT (menu), "row-span-column");
1505 gtk_tree_menu_get_column_span_column (GtkTreeMenu *menu)
1507 GtkTreeMenuPrivate *priv;
1509 g_return_val_if_fail (GTK_IS_TREE_MENU (menu), FALSE);
1513 return priv->col_span_col;
1517 gtk_tree_menu_set_column_span_column (GtkTreeMenu *menu,
1520 GtkTreeMenuPrivate *priv;
1522 g_return_if_fail (GTK_IS_TREE_MENU (menu));
1526 if (priv->col_span_col != column_span)
1528 priv->col_span_col = column_span;
1530 if (priv->wrap_width > 0)
1531 rebuild_menu (menu);
1533 g_object_notify (G_OBJECT (menu), "column-span-column");
1537 GtkTreeViewRowSeparatorFunc
1538 gtk_tree_menu_get_row_separator_func (GtkTreeMenu *menu)
1540 GtkTreeMenuPrivate *priv;
1542 g_return_val_if_fail (GTK_IS_TREE_MENU (menu), NULL);
1546 return priv->row_separator_func;
1550 gtk_tree_menu_set_row_separator_func (GtkTreeMenu *menu,
1551 GtkTreeViewRowSeparatorFunc func,
1553 GDestroyNotify destroy)
1555 GtkTreeMenuPrivate *priv;
1557 g_return_if_fail (GTK_IS_TREE_MENU (menu));
1561 if (priv->row_separator_destroy)
1562 priv->row_separator_destroy (priv->row_separator_data);
1564 priv->row_separator_func = func;
1565 priv->row_separator_data = data;
1566 priv->row_separator_destroy = destroy;
1568 rebuild_menu (menu);
1571 GtkTreeMenuHeaderFunc
1572 gtk_tree_menu_get_header_func (GtkTreeMenu *menu)
1574 GtkTreeMenuPrivate *priv;
1576 g_return_val_if_fail (GTK_IS_TREE_MENU (menu), NULL);
1580 return priv->header_func;
1584 gtk_tree_menu_set_header_func (GtkTreeMenu *menu,
1585 GtkTreeMenuHeaderFunc func,
1587 GDestroyNotify destroy)
1589 GtkTreeMenuPrivate *priv;
1591 g_return_if_fail (GTK_IS_TREE_MENU (menu));
1595 if (priv->header_destroy)
1596 priv->header_destroy (priv->header_data);
1598 priv->header_func = func;
1599 priv->header_data = data;
1600 priv->header_destroy = destroy;
1602 rebuild_menu (menu);