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,
118 static void gtk_tree_menu_set_model_internal (GtkTreeMenu *menu,
119 GtkTreeModel *model);
123 struct _GtkTreeMenuPrivate
125 /* TreeModel and parent for this menu */
127 GtkTreeRowReference *root;
129 /* CellArea and context for this menu */
131 GtkCellAreaContext *context;
134 gulong size_changed_id;
135 gulong apply_attributes_id;
136 gulong row_inserted_id;
137 gulong row_deleted_id;
138 gulong row_reordered_id;
139 gulong row_changed_id;
147 guint32 menu_with_header : 1;
151 GtkTreeViewRowSeparatorFunc row_separator_func;
152 gpointer row_separator_data;
153 GDestroyNotify row_separator_destroy;
155 /* Submenu headers */
156 GtkTreeMenuHeaderFunc header_func;
157 gpointer header_data;
158 GDestroyNotify header_destroy;
173 SIGNAL_MENU_ACTIVATE,
177 static guint tree_menu_signals[N_SIGNALS] = { 0 };
178 static GQuark tree_menu_path_quark = 0;
180 G_DEFINE_TYPE_WITH_CODE (GtkTreeMenu, gtk_tree_menu, GTK_TYPE_MENU,
181 G_IMPLEMENT_INTERFACE (GTK_TYPE_CELL_LAYOUT,
182 gtk_tree_menu_cell_layout_init));
185 gtk_tree_menu_init (GtkTreeMenu *menu)
187 GtkTreeMenuPrivate *priv;
189 menu->priv = G_TYPE_INSTANCE_GET_PRIVATE (menu,
197 priv->context = NULL;
199 priv->size_changed_id = 0;
200 priv->row_inserted_id = 0;
201 priv->row_deleted_id = 0;
202 priv->row_reordered_id = 0;
203 priv->row_changed_id = 0;
205 priv->wrap_width = 0;
206 priv->row_span_col = -1;
207 priv->col_span_col = -1;
209 priv->menu_with_header = FALSE;
210 priv->tearoff = FALSE;
212 priv->row_separator_func = NULL;
213 priv->row_separator_data = NULL;
214 priv->row_separator_destroy = NULL;
216 priv->header_func = NULL;
217 priv->header_data = NULL;
218 priv->header_destroy = NULL;
220 gtk_menu_set_reserve_toggle_size (GTK_MENU (menu), FALSE);
224 gtk_tree_menu_class_init (GtkTreeMenuClass *class)
226 GObjectClass *object_class = G_OBJECT_CLASS (class);
227 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
229 tree_menu_path_quark = g_quark_from_static_string ("gtk-tree-menu-path");
231 object_class->constructor = gtk_tree_menu_constructor;
232 object_class->dispose = gtk_tree_menu_dispose;
233 object_class->finalize = gtk_tree_menu_finalize;
234 object_class->set_property = gtk_tree_menu_set_property;
235 object_class->get_property = gtk_tree_menu_get_property;
237 widget_class->get_preferred_width = gtk_tree_menu_get_preferred_width;
238 widget_class->get_preferred_height = gtk_tree_menu_get_preferred_height;
240 tree_menu_signals[SIGNAL_MENU_ACTIVATE] =
241 g_signal_new (I_("menu-activate"),
242 G_OBJECT_CLASS_TYPE (object_class),
244 0, /* No class closure here */
246 _gtk_marshal_VOID__STRING,
247 G_TYPE_NONE, 1, G_TYPE_STRING);
249 g_object_class_install_property (object_class,
251 g_param_spec_object ("model",
252 P_("TreeMenu model"),
253 P_("The model for the tree menu"),
255 GTK_PARAM_READWRITE));
257 g_object_class_install_property (object_class,
259 g_param_spec_boxed ("root",
260 P_("TreeMenu root row"),
261 P_("The TreeMenu will display children of the "
264 GTK_PARAM_READWRITE));
266 g_object_class_install_property (object_class,
268 g_param_spec_object ("cell-area",
270 P_("The GtkCellArea used to layout cells"),
272 GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
274 g_object_class_install_property (object_class,
276 g_param_spec_boolean ("tearoff",
278 P_("Whether the menu has a tearoff item"),
280 GTK_PARAM_READWRITE));
283 * GtkTreeMenu:wrap-width:
285 * If wrap-width is set to a positive value, the list will be
286 * displayed in multiple columns, the number of columns is
287 * determined by wrap-width.
291 g_object_class_install_property (object_class,
293 g_param_spec_int ("wrap-width",
295 P_("Wrap width for laying out items in a grid"),
299 GTK_PARAM_READWRITE));
302 * GtkTreeMenu:row-span-column:
304 * If this is set to a non-negative value, it must be the index of a column
305 * of type %G_TYPE_INT in the model.
307 * The values of that column are used to determine how many rows a value in
308 * the list will span. Therefore, the values in the model column pointed to
309 * by this property must be greater than zero and not larger than wrap-width.
313 g_object_class_install_property (object_class,
315 g_param_spec_int ("row-span-column",
316 P_("Row span column"),
317 P_("TreeModel column containing the row span values"),
321 GTK_PARAM_READWRITE));
324 * GtkTreeMenu:column-span-column:
326 * If this is set to a non-negative value, it must be the index of a column
327 * of type %G_TYPE_INT in the model.
329 * The values of that column are used to determine how many columns a value
330 * in the list will span.
334 g_object_class_install_property (object_class,
336 g_param_spec_int ("column-span-column",
337 P_("Column span column"),
338 P_("TreeModel column containing the column span values"),
342 GTK_PARAM_READWRITE));
344 g_type_class_add_private (object_class, sizeof (GtkTreeMenuPrivate));
347 /****************************************************************
349 ****************************************************************/
351 gtk_tree_menu_constructor (GType type,
352 guint n_construct_properties,
353 GObjectConstructParam *construct_properties)
357 GtkTreeMenuPrivate *priv;
359 object = G_OBJECT_CLASS (gtk_tree_menu_parent_class)->constructor
360 (type, n_construct_properties, construct_properties);
362 menu = GTK_TREE_MENU (object);
367 GtkCellArea *area = gtk_cell_area_box_new ();
369 gtk_tree_menu_set_area (menu, area);
372 priv->context = gtk_cell_area_create_context (priv->area);
374 priv->size_changed_id =
375 g_signal_connect (priv->context, "notify",
376 G_CALLBACK (context_size_changed_cb), menu);
382 gtk_tree_menu_dispose (GObject *object)
385 GtkTreeMenuPrivate *priv;
387 menu = GTK_TREE_MENU (object);
390 gtk_tree_menu_set_model (menu, NULL);
391 gtk_tree_menu_set_area (menu, NULL);
395 /* Disconnect signals */
396 g_signal_handler_disconnect (priv->context, priv->size_changed_id);
398 g_object_unref (priv->context);
399 priv->context = NULL;
400 priv->size_changed_id = 0;
403 G_OBJECT_CLASS (gtk_tree_menu_parent_class)->dispose (object);
407 gtk_tree_menu_finalize (GObject *object)
410 GtkTreeMenuPrivate *priv;
412 menu = GTK_TREE_MENU (object);
415 gtk_tree_menu_set_row_separator_func (menu, NULL, NULL, NULL);
416 gtk_tree_menu_set_header_func (menu, NULL, NULL, NULL);
419 gtk_tree_row_reference_free (priv->root);
421 G_OBJECT_CLASS (gtk_tree_menu_parent_class)->finalize (object);
425 gtk_tree_menu_set_property (GObject *object,
430 GtkTreeMenu *menu = GTK_TREE_MENU (object);
435 gtk_tree_menu_set_model (menu, g_value_get_object (value));
439 gtk_tree_menu_set_root (menu, g_value_get_boxed (value));
443 /* Construct-only, can only be assigned once */
444 gtk_tree_menu_set_area (menu, (GtkCellArea *)g_value_get_object (value));
448 gtk_tree_menu_set_tearoff (menu, g_value_get_boolean (value));
451 case PROP_WRAP_WIDTH:
452 gtk_tree_menu_set_wrap_width (menu, g_value_get_int (value));
455 case PROP_ROW_SPAN_COL:
456 gtk_tree_menu_set_row_span_column (menu, g_value_get_int (value));
459 case PROP_COL_SPAN_COL:
460 gtk_tree_menu_set_column_span_column (menu, g_value_get_int (value));
464 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
470 gtk_tree_menu_get_property (GObject *object,
475 GtkTreeMenu *menu = GTK_TREE_MENU (object);
476 GtkTreeMenuPrivate *priv = menu->priv;
481 g_value_set_object (value, priv->model);
485 g_value_set_boxed (value, priv->root);
489 g_value_set_object (value, priv->area);
493 g_value_set_boolean (value, priv->tearoff);
497 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
502 /****************************************************************
504 ****************************************************************/
506 /* We tell all the menu items to reserve space for the submenu
507 * indicator if there is at least one submenu, this way we ensure
508 * that every internal cell area gets allocated the
509 * same width (and requested height for the same appropriate width).
512 sync_reserve_submenu_size (GtkTreeMenu *menu)
515 gboolean has_submenu = FALSE;
517 children = gtk_container_get_children (GTK_CONTAINER (menu));
518 for (l = children; l; l = l->next)
520 GtkMenuItem *item = l->data;
522 if (gtk_menu_item_get_submenu (item) != NULL)
529 for (l = children; l; l = l->next)
531 GtkMenuItem *item = l->data;
533 gtk_menu_item_set_reserve_indicator (item, has_submenu);
536 g_list_free (children);
540 gtk_tree_menu_get_preferred_width (GtkWidget *widget,
544 GtkTreeMenu *menu = GTK_TREE_MENU (widget);
545 GtkTreeMenuPrivate *priv = menu->priv;
547 /* We leave the requesting work up to the cellviews which operate in the same
548 * context, reserving space for the submenu indicator if any of the items have
549 * submenus ensures that every cellview will receive the same allocated width.
551 * Since GtkMenu does hieght-for-width correctly, we know that the width of
552 * every cell will be requested before the height-for-widths are requested.
554 g_signal_handler_block (priv->context, priv->size_changed_id);
556 sync_reserve_submenu_size (menu);
558 GTK_WIDGET_CLASS (gtk_tree_menu_parent_class)->get_preferred_width (widget, minimum_size, natural_size);
560 g_signal_handler_unblock (priv->context, priv->size_changed_id);
564 gtk_tree_menu_get_preferred_height (GtkWidget *widget,
568 GtkTreeMenu *menu = GTK_TREE_MENU (widget);
569 GtkTreeMenuPrivate *priv = menu->priv;
571 g_signal_handler_block (priv->context, priv->size_changed_id);
573 sync_reserve_submenu_size (menu);
575 GTK_WIDGET_CLASS (gtk_tree_menu_parent_class)->get_preferred_height (widget, minimum_size, natural_size);
577 g_signal_handler_unblock (priv->context, priv->size_changed_id);
580 /****************************************************************
581 * GtkCellLayoutIface *
582 ****************************************************************/
584 gtk_tree_menu_cell_layout_init (GtkCellLayoutIface *iface)
586 iface->get_area = gtk_tree_menu_cell_layout_get_area;
590 gtk_tree_menu_cell_layout_get_area (GtkCellLayout *layout)
592 GtkTreeMenu *menu = GTK_TREE_MENU (layout);
593 GtkTreeMenuPrivate *priv = menu->priv;
599 /****************************************************************
600 * TreeModel callbacks/populating menus *
601 ****************************************************************/
603 gtk_tree_menu_get_path_item (GtkTreeMenu *menu,
606 GtkWidget *item = NULL;
609 children = gtk_container_get_children (GTK_CONTAINER (menu));
611 for (l = children; item == NULL && l != NULL; l = l->next)
613 GtkWidget *child = l->data;
614 GtkTreePath *path = NULL;
616 if (GTK_IS_SEPARATOR_MENU_ITEM (child))
618 GtkTreeRowReference *row =
619 g_object_get_qdata (G_OBJECT (child), tree_menu_path_quark);
621 if (row && gtk_tree_row_reference_valid (row))
622 path = gtk_tree_row_reference_get_path (row);
626 GtkWidget *view = gtk_bin_get_child (GTK_BIN (child));
628 /* It's always a cellview */
629 if (GTK_IS_CELL_VIEW (view))
630 path = gtk_cell_view_get_displayed_row (GTK_CELL_VIEW (view));
635 if (gtk_tree_path_compare (search, path) == 0)
638 gtk_tree_path_free (path);
642 g_list_free (children);
648 gtk_tree_menu_path_in_menu (GtkTreeMenu *menu,
650 gboolean *header_item)
652 GtkTreeMenuPrivate *priv = menu->priv;
653 GtkTreePath *parent_path = gtk_tree_path_copy (path);
654 gboolean in_menu = FALSE;
655 gboolean is_header = FALSE;
657 /* Check if the is in root of the model */
658 if (gtk_tree_path_get_depth (parent_path) == 1)
663 /* If we are a submenu, compare the parent path */
664 else if (priv->root && gtk_tree_path_up (parent_path))
666 GtkTreePath *root_path =
667 gtk_tree_row_reference_get_path (priv->root);
669 if (gtk_tree_path_compare (root_path, parent_path) == 0)
672 if (!in_menu && priv->menu_with_header &&
673 gtk_tree_path_compare (root_path, path) == 0)
679 gtk_tree_path_free (root_path);
682 gtk_tree_path_free (parent_path);
685 *header_item = is_header;
691 row_inserted_cb (GtkTreeModel *model,
696 GtkTreeMenuPrivate *priv = menu->priv;
697 gint *indices, index, depth;
699 /* If the iter should be in this menu then go ahead and insert it */
700 if (gtk_tree_menu_path_in_menu (menu, path, NULL))
704 if (priv->wrap_width > 0)
708 /* Get the index of the path for this depth */
709 indices = gtk_tree_path_get_indices (path);
710 depth = gtk_tree_path_get_depth (path);
711 index = indices[depth -1];
713 /* Menus with a header include a menuitem for it's root node
714 * and a separator menu item */
715 if (priv->menu_with_header)
718 /* Index after the tearoff item for the root menu if
719 * there is a tearoff item
721 if (priv->root == NULL && priv->tearoff)
724 item = gtk_tree_menu_create_item (menu, iter, FALSE);
725 gtk_menu_shell_insert (GTK_MENU_SHELL (menu), item, index);
727 /* Resize everything */
728 gtk_cell_area_context_flush (menu->priv->context);
734 row_deleted_cb (GtkTreeModel *model,
738 GtkTreeMenuPrivate *priv = menu->priv;
740 gboolean header_item;
743 in_menu = gtk_tree_menu_path_in_menu (menu, path, &header_item);
745 /* If it's the header item we leave it to the parent menu
746 * to remove us from its menu
748 if (!header_item && in_menu)
750 item = gtk_tree_menu_get_path_item (menu, path);
753 if (priv->wrap_width > 0)
757 /* Get rid of the deleted item */
758 gtk_widget_destroy (item);
760 /* Resize everything */
761 gtk_cell_area_context_flush (menu->priv->context);
768 row_reordered_cb (GtkTreeModel *model,
774 GtkTreeMenuPrivate *priv = menu->priv;
775 gboolean this_menu = FALSE;
777 if (path == NULL && priv->root == NULL)
781 GtkTreePath *root_path =
782 gtk_tree_row_reference_get_path (priv->root);
784 if (gtk_tree_path_compare (root_path, path) == 0)
787 gtk_tree_path_free (root_path);
795 menu_item_position (GtkTreeMenu *menu,
801 children = gtk_container_get_children (GTK_CONTAINER (menu));
802 for (position = 0, l = children; l; position++, l = l->next)
804 GtkWidget *iitem = l->data;
810 g_list_free (children);
816 row_changed_cb (GtkTreeModel *model,
821 GtkTreeMenuPrivate *priv = menu->priv;
822 gboolean is_separator = FALSE;
823 gboolean has_header = FALSE;
826 item = gtk_tree_menu_get_path_item (menu, path);
830 GtkTreePath *root_path =
831 gtk_tree_row_reference_get_path (priv->root);
833 if (root_path && gtk_tree_path_compare (root_path, path) == 0)
835 if (priv->header_func)
837 priv->header_func (priv->model, iter, priv->header_data);
839 if (has_header && !item)
841 item = gtk_separator_menu_item_new ();
842 gtk_widget_show (item);
843 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
845 item = gtk_tree_menu_create_item (menu, iter, TRUE);
846 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
848 priv->menu_with_header = TRUE;
850 else if (!has_header && item)
852 /* Destroy the header item and then the following separator */
853 gtk_widget_destroy (item);
854 gtk_widget_destroy (GTK_MENU_SHELL (menu)->children->data);
856 priv->menu_with_header = FALSE;
859 gtk_tree_path_free (root_path);
865 if (priv->wrap_width > 0)
866 /* Ugly, we need to rebuild the menu here if
867 * the row-span/row-column values change
872 if (priv->row_separator_func)
874 priv->row_separator_func (model, iter,
875 priv->row_separator_data);
878 if (is_separator != GTK_IS_SEPARATOR_MENU_ITEM (item))
880 gint position = menu_item_position (menu, item);
882 gtk_widget_destroy (item);
883 item = gtk_tree_menu_create_item (menu, iter, FALSE);
884 gtk_menu_shell_insert (GTK_MENU_SHELL (menu), item, position);
891 context_size_changed_cb (GtkCellAreaContext *context,
895 if (!strcmp (pspec->name, "minimum-width") ||
896 !strcmp (pspec->name, "natural-width") ||
897 !strcmp (pspec->name, "minimum-height") ||
898 !strcmp (pspec->name, "natural-height"))
899 gtk_widget_queue_resize (menu);
903 area_is_sensitive (GtkCellArea *area)
906 gboolean sensitive = FALSE;
908 cells = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (area));
910 for (list = cells; list; list = list->next)
912 g_object_get (list->data, "sensitive", &sensitive, NULL);
923 area_apply_attributes_cb (GtkCellArea *area,
924 GtkTreeModel *tree_model,
926 gboolean is_expander,
927 gboolean is_expanded,
930 /* If the menu for this iter has a submenu */
931 GtkTreeMenuPrivate *priv = menu->priv;
937 path = gtk_tree_model_get_path (tree_model, iter);
939 if (gtk_tree_menu_path_in_menu (menu, path, &is_header))
941 item = gtk_tree_menu_get_path_item (menu, path);
943 /* If there is no submenu, go ahead and update item sensitivity,
944 * items with submenus are always sensitive */
945 if (item && !gtk_menu_item_get_submenu (GTK_MENU_ITEM (item)))
947 sensitive = area_is_sensitive (priv->area);
949 gtk_widget_set_sensitive (item, sensitive);
953 /* For header items we need to set the sensitivity
954 * of the following separator item
956 if (GTK_MENU_SHELL (menu)->children &&
957 GTK_MENU_SHELL (menu)->children->next)
959 GtkWidget *separator =
960 GTK_MENU_SHELL (menu)->children->next->data;
962 gtk_widget_set_sensitive (separator, sensitive);
968 gtk_tree_path_free (path);
972 gtk_tree_menu_set_area (GtkTreeMenu *menu,
975 GtkTreeMenuPrivate *priv = menu->priv;
979 g_signal_handler_disconnect (priv->area,
980 priv->apply_attributes_id);
981 priv->apply_attributes_id = 0;
983 g_object_unref (priv->area);
990 g_object_ref_sink (priv->area);
992 priv->apply_attributes_id =
993 g_signal_connect (priv->area, "apply-attributes",
994 G_CALLBACK (area_apply_attributes_cb), menu);
999 menu_occupied (GtkTreeMenu *menu,
1003 guint bottom_attach)
1007 for (i = GTK_MENU_SHELL (menu)->children; i; i = i->next)
1011 gtk_container_child_get (GTK_CONTAINER (menu),
1015 "bottom-attach", &b,
1019 /* look if this item intersects with the given coordinates */
1020 if (right_attach > l && left_attach < r && bottom_attach > t && top_attach < b)
1028 relayout_item (GtkTreeMenu *menu,
1033 GtkTreeMenuPrivate *priv = menu->priv;
1034 gint current_col = 0, current_row = 0;
1035 gint rows = 1, cols = 1;
1037 if (priv->col_span_col == -1 &&
1038 priv->row_span_col == -1 &&
1041 gtk_container_child_get (GTK_CONTAINER (menu), prev,
1042 "right-attach", ¤t_col,
1043 "top-attach", ¤t_row,
1045 if (current_col + cols > priv->wrap_width)
1053 if (priv->col_span_col != -1)
1054 gtk_tree_model_get (priv->model, iter,
1055 priv->col_span_col, &cols,
1057 if (priv->row_span_col != -1)
1058 gtk_tree_model_get (priv->model, iter,
1059 priv->row_span_col, &rows,
1064 if (current_col + cols > priv->wrap_width)
1070 if (!menu_occupied (menu,
1071 current_col, current_col + cols,
1072 current_row, current_row + rows))
1079 /* set attach props */
1080 gtk_menu_attach (GTK_MENU (menu), item,
1081 current_col, current_col + cols,
1082 current_row, current_row + rows);
1086 gtk_tree_menu_create_item (GtkTreeMenu *menu,
1088 gboolean header_item)
1090 GtkTreeMenuPrivate *priv = menu->priv;
1091 GtkWidget *item, *view;
1093 gboolean is_separator = FALSE;
1095 path = gtk_tree_model_get_path (priv->model, iter);
1097 if (priv->row_separator_func)
1099 priv->row_separator_func (priv->model, iter,
1100 priv->row_separator_data);
1104 item = gtk_separator_menu_item_new ();
1105 gtk_widget_show (item);
1107 g_object_set_qdata_full (G_OBJECT (item),
1108 tree_menu_path_quark,
1109 gtk_tree_row_reference_new (priv->model, path),
1110 (GDestroyNotify)gtk_tree_row_reference_free);
1114 view = gtk_cell_view_new_with_context (priv->area, priv->context);
1115 item = gtk_menu_item_new ();
1116 gtk_widget_show (view);
1117 gtk_widget_show (item);
1119 gtk_cell_view_set_model (GTK_CELL_VIEW (view), priv->model);
1120 gtk_cell_view_set_displayed_row (GTK_CELL_VIEW (view), path);
1122 gtk_widget_show (view);
1123 gtk_container_add (GTK_CONTAINER (item), view);
1125 g_signal_connect (item, "activate", G_CALLBACK (item_activated_cb), menu);
1127 /* Add a GtkTreeMenu submenu to render the children of this row */
1128 if (header_item == FALSE &&
1129 gtk_tree_model_iter_has_child (priv->model, iter))
1133 submenu = gtk_tree_menu_new_with_area (priv->area);
1135 gtk_tree_menu_set_row_separator_func (GTK_TREE_MENU (submenu),
1136 priv->row_separator_func,
1137 priv->row_separator_data,
1138 priv->row_separator_destroy);
1139 gtk_tree_menu_set_header_func (GTK_TREE_MENU (submenu),
1142 priv->header_destroy);
1144 gtk_tree_menu_set_model_internal (GTK_TREE_MENU (submenu), priv->model);
1145 gtk_tree_menu_set_root (GTK_TREE_MENU (submenu), path);
1146 gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), submenu);
1148 g_signal_connect (submenu, "menu-activate",
1149 G_CALLBACK (submenu_activated_cb), menu);
1153 gtk_tree_path_free (path);
1159 rebuild_menu (GtkTreeMenu *menu)
1161 GtkTreeMenuPrivate *priv = menu->priv;
1163 /* Destroy all the menu items */
1164 gtk_container_foreach (GTK_CONTAINER (menu),
1165 (GtkCallback) gtk_widget_destroy, NULL);
1169 gtk_tree_menu_populate (menu);
1174 gtk_tree_menu_populate (GtkTreeMenu *menu)
1176 GtkTreeMenuPrivate *priv = menu->priv;
1177 GtkTreePath *path = NULL;
1180 gboolean valid = FALSE;
1181 GtkWidget *menu_item, *prev = NULL;
1187 path = gtk_tree_row_reference_get_path (priv->root);
1191 if (gtk_tree_model_get_iter (priv->model, &parent, path))
1193 valid = gtk_tree_model_iter_children (priv->model, &iter, &parent);
1195 if (priv->header_func &&
1196 priv->header_func (priv->model, &parent, priv->header_data))
1198 /* Add a submenu header for rows which desire one, used for
1199 * combo boxes to allow all rows to be activatable/selectable
1201 menu_item = gtk_tree_menu_create_item (menu, &parent, TRUE);
1202 gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);
1204 menu_item = gtk_separator_menu_item_new ();
1205 gtk_widget_show (menu_item);
1206 gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);
1209 priv->menu_with_header = TRUE;
1212 gtk_tree_path_free (path);
1216 /* Tearoff menu items only go in the root menu */
1219 menu_item = gtk_tearoff_menu_item_new ();
1220 gtk_widget_show (menu_item);
1222 if (priv->wrap_width > 0)
1223 gtk_menu_attach (GTK_MENU (menu), menu_item, 0, priv->wrap_width, 0, 1);
1225 gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);
1230 valid = gtk_tree_model_iter_children (priv->model, &iter, NULL);
1233 /* Create a menu item for every row at the current depth, add a GtkTreeMenu
1234 * submenu for iters/items that have children */
1237 menu_item = gtk_tree_menu_create_item (menu, &iter, FALSE);
1239 gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);
1241 if (priv->wrap_width > 0)
1242 relayout_item (menu, menu_item, &iter, prev);
1245 valid = gtk_tree_model_iter_next (priv->model, &iter);
1250 item_activated_cb (GtkMenuItem *item,
1257 /* Only activate leafs, not parents */
1258 if (!gtk_menu_item_get_submenu (item))
1260 view = GTK_CELL_VIEW (gtk_bin_get_child (GTK_BIN (item)));
1261 path = gtk_cell_view_get_displayed_row (view);
1262 path_str = gtk_tree_path_to_string (path);
1264 g_signal_emit (menu, tree_menu_signals[SIGNAL_MENU_ACTIVATE], 0, path_str);
1267 gtk_tree_path_free (path);
1272 submenu_activated_cb (GtkTreeMenu *submenu,
1276 g_signal_emit (menu, tree_menu_signals[SIGNAL_MENU_ACTIVATE], 0, path);
1279 /* Sets the model without rebuilding the menu, prevents
1280 * infinite recursion while building submenus (we wait
1281 * until the root is set and then build the menu) */
1283 gtk_tree_menu_set_model_internal (GtkTreeMenu *menu,
1284 GtkTreeModel *model)
1286 GtkTreeMenuPrivate *priv;
1290 if (priv->model != model)
1294 /* Disconnect signals */
1295 g_signal_handler_disconnect (priv->model,
1296 priv->row_inserted_id);
1297 g_signal_handler_disconnect (priv->model,
1298 priv->row_deleted_id);
1299 g_signal_handler_disconnect (priv->model,
1300 priv->row_reordered_id);
1301 g_signal_handler_disconnect (priv->model,
1302 priv->row_changed_id);
1303 priv->row_inserted_id = 0;
1304 priv->row_deleted_id = 0;
1305 priv->row_reordered_id = 0;
1306 priv->row_changed_id = 0;
1308 g_object_unref (priv->model);
1311 priv->model = model;
1315 g_object_ref (priv->model);
1317 /* Connect signals */
1318 priv->row_inserted_id = g_signal_connect (priv->model, "row-inserted",
1319 G_CALLBACK (row_inserted_cb), menu);
1320 priv->row_deleted_id = g_signal_connect (priv->model, "row-deleted",
1321 G_CALLBACK (row_deleted_cb), menu);
1322 priv->row_reordered_id = g_signal_connect (priv->model, "rows-reordered",
1323 G_CALLBACK (row_reordered_cb), menu);
1324 priv->row_changed_id = g_signal_connect (priv->model, "row-changed",
1325 G_CALLBACK (row_changed_cb), menu);
1330 /****************************************************************
1332 ****************************************************************/
1334 gtk_tree_menu_new (void)
1336 return (GtkWidget *)g_object_new (GTK_TYPE_TREE_MENU, NULL);
1340 gtk_tree_menu_new_with_area (GtkCellArea *area)
1342 return (GtkWidget *)g_object_new (GTK_TYPE_TREE_MENU,
1348 gtk_tree_menu_new_full (GtkCellArea *area,
1349 GtkTreeModel *model,
1352 return (GtkWidget *)g_object_new (GTK_TYPE_TREE_MENU,
1360 gtk_tree_menu_set_model (GtkTreeMenu *menu,
1361 GtkTreeModel *model)
1363 g_return_if_fail (GTK_IS_TREE_MENU (menu));
1364 g_return_if_fail (model == NULL || GTK_IS_TREE_MODEL (model));
1366 gtk_tree_menu_set_model_internal (menu, model);
1368 rebuild_menu (menu);
1372 gtk_tree_menu_get_model (GtkTreeMenu *menu)
1374 GtkTreeMenuPrivate *priv;
1376 g_return_val_if_fail (GTK_IS_TREE_MENU (menu), NULL);
1384 gtk_tree_menu_set_root (GtkTreeMenu *menu,
1387 GtkTreeMenuPrivate *priv;
1389 g_return_if_fail (GTK_IS_TREE_MENU (menu));
1390 g_return_if_fail (menu->priv->model != NULL || path == NULL);
1395 gtk_tree_row_reference_free (priv->root);
1398 priv->root = gtk_tree_row_reference_new (priv->model, path);
1402 rebuild_menu (menu);
1406 gtk_tree_menu_get_root (GtkTreeMenu *menu)
1408 GtkTreeMenuPrivate *priv;
1410 g_return_val_if_fail (GTK_IS_TREE_MENU (menu), NULL);
1415 return gtk_tree_row_reference_get_path (priv->root);
1421 gtk_tree_menu_get_tearoff (GtkTreeMenu *menu)
1423 GtkTreeMenuPrivate *priv;
1425 g_return_val_if_fail (GTK_IS_TREE_MENU (menu), FALSE);
1429 return priv->tearoff;
1433 gtk_tree_menu_set_tearoff (GtkTreeMenu *menu,
1436 GtkTreeMenuPrivate *priv;
1438 g_return_if_fail (GTK_IS_TREE_MENU (menu));
1442 if (priv->tearoff != tearoff)
1444 priv->tearoff = tearoff;
1446 rebuild_menu (menu);
1448 g_object_notify (G_OBJECT (menu), "tearoff");
1453 gtk_tree_menu_get_wrap_width (GtkTreeMenu *menu)
1455 GtkTreeMenuPrivate *priv;
1457 g_return_val_if_fail (GTK_IS_TREE_MENU (menu), FALSE);
1461 return priv->wrap_width;
1465 gtk_tree_menu_set_wrap_width (GtkTreeMenu *menu,
1468 GtkTreeMenuPrivate *priv;
1470 g_return_if_fail (GTK_IS_TREE_MENU (menu));
1471 g_return_if_fail (width >= 0);
1475 if (priv->wrap_width != width)
1477 priv->wrap_width = width;
1479 rebuild_menu (menu);
1481 g_object_notify (G_OBJECT (menu), "wrap-width");
1486 gtk_tree_menu_get_row_span_column (GtkTreeMenu *menu)
1488 GtkTreeMenuPrivate *priv;
1490 g_return_val_if_fail (GTK_IS_TREE_MENU (menu), FALSE);
1494 return priv->row_span_col;
1498 gtk_tree_menu_set_row_span_column (GtkTreeMenu *menu,
1501 GtkTreeMenuPrivate *priv;
1503 g_return_if_fail (GTK_IS_TREE_MENU (menu));
1507 if (priv->row_span_col != row_span)
1509 priv->row_span_col = row_span;
1511 if (priv->wrap_width > 0)
1512 rebuild_menu (menu);
1514 g_object_notify (G_OBJECT (menu), "row-span-column");
1519 gtk_tree_menu_get_column_span_column (GtkTreeMenu *menu)
1521 GtkTreeMenuPrivate *priv;
1523 g_return_val_if_fail (GTK_IS_TREE_MENU (menu), FALSE);
1527 return priv->col_span_col;
1531 gtk_tree_menu_set_column_span_column (GtkTreeMenu *menu,
1534 GtkTreeMenuPrivate *priv;
1536 g_return_if_fail (GTK_IS_TREE_MENU (menu));
1540 if (priv->col_span_col != column_span)
1542 priv->col_span_col = column_span;
1544 if (priv->wrap_width > 0)
1545 rebuild_menu (menu);
1547 g_object_notify (G_OBJECT (menu), "column-span-column");
1551 GtkTreeViewRowSeparatorFunc
1552 gtk_tree_menu_get_row_separator_func (GtkTreeMenu *menu)
1554 GtkTreeMenuPrivate *priv;
1556 g_return_val_if_fail (GTK_IS_TREE_MENU (menu), NULL);
1560 return priv->row_separator_func;
1564 gtk_tree_menu_set_row_separator_func (GtkTreeMenu *menu,
1565 GtkTreeViewRowSeparatorFunc func,
1567 GDestroyNotify destroy)
1569 GtkTreeMenuPrivate *priv;
1571 g_return_if_fail (GTK_IS_TREE_MENU (menu));
1575 if (priv->row_separator_destroy)
1576 priv->row_separator_destroy (priv->row_separator_data);
1578 priv->row_separator_func = func;
1579 priv->row_separator_data = data;
1580 priv->row_separator_destroy = destroy;
1582 rebuild_menu (menu);
1585 GtkTreeMenuHeaderFunc
1586 gtk_tree_menu_get_header_func (GtkTreeMenu *menu)
1588 GtkTreeMenuPrivate *priv;
1590 g_return_val_if_fail (GTK_IS_TREE_MENU (menu), NULL);
1594 return priv->header_func;
1598 gtk_tree_menu_set_header_func (GtkTreeMenu *menu,
1599 GtkTreeMenuHeaderFunc func,
1601 GDestroyNotify destroy)
1603 GtkTreeMenuPrivate *priv;
1605 g_return_if_fail (GTK_IS_TREE_MENU (menu));
1609 if (priv->header_destroy)
1610 priv->header_destroy (priv->header_data);
1612 priv->header_func = func;
1613 priv->header_data = data;
1614 priv->header_destroy = destroy;
1616 rebuild_menu (menu);