]> Pileus Git - ~andy/gtk/blob - gtk/gtktreemenu.c
stylecontext: Do invalidation on first resize container
[~andy/gtk] / gtk / gtktreemenu.c
1 /* gtktreemenu.c
2  *
3  * Copyright (C) 2010 Openismus GmbH
4  *
5  * Authors:
6  *      Tristan Van Berkom <tristanvb@openismus.com>
7  *
8  * Based on some GtkComboBox menu code by Kristian Rietveld <kris@gtk.org>
9  *
10  * This library is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU Library General Public
12  * License as published by the Free Software Foundation; either
13  * version 2 of the License, or (at your option) any later version.
14  *
15  * This library is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18  * Library General Public License for more details.
19  *
20  * You should have received a copy of the GNU Library General Public
21  * License along with this library. If not, see <http://www.gnu.org/licenses/>.
22  */
23
24 /*
25  * SECTION:gtktreemenu
26  * @Short_Description: A GtkMenu automatically created from a #GtkTreeModel
27  * @Title: GtkTreeMenu
28  *
29  * The #GtkTreeMenu is used to display a drop-down menu allowing selection
30  * of every row in the model and is used by the #GtkComboBox for its drop-down
31  * menu.
32  */
33
34 #include "config.h"
35 #include "gtkintl.h"
36 #include "gtktreemenu.h"
37 #include "gtkmarshalers.h"
38 #include "gtkmenuitem.h"
39 #include "gtkseparatormenuitem.h"
40 #include "gtkcellareabox.h"
41 #include "gtkcellareacontext.h"
42 #include "gtkcelllayout.h"
43 #include "gtkcellview.h"
44 #include "gtkmenushellprivate.h"
45 #include "gtkprivate.h"
46
47 #undef GDK_DEPRECATED
48 #undef GDK_DEPRECATED_FOR
49 #define GDK_DEPRECATED
50 #define GDK_DEPRECATED_FOR(f)
51
52 #include "deprecated/gtktearoffmenuitem.h"
53
54 /* GObjectClass */
55 static GObject  *gtk_tree_menu_constructor                    (GType                  type,
56                                                                guint                  n_construct_properties,
57                                                                GObjectConstructParam *construct_properties);
58 static void      gtk_tree_menu_dispose                        (GObject            *object);
59 static void      gtk_tree_menu_finalize                       (GObject            *object);
60 static void      gtk_tree_menu_set_property                   (GObject            *object,
61                                                                guint               prop_id,
62                                                                const GValue       *value,
63                                                                GParamSpec         *pspec);
64 static void      gtk_tree_menu_get_property                   (GObject            *object,
65                                                                guint               prop_id,
66                                                                GValue             *value,
67                                                                GParamSpec         *pspec);
68
69 /* GtkWidgetClass */
70 static void      gtk_tree_menu_get_preferred_width            (GtkWidget           *widget,
71                                                                gint                *minimum_size,
72                                                                gint                *natural_size);
73 static void      gtk_tree_menu_get_preferred_height           (GtkWidget           *widget,
74                                                                gint                *minimum_size,
75                                                                gint                *natural_size);
76
77 /* GtkCellLayoutIface */
78 static void      gtk_tree_menu_cell_layout_init               (GtkCellLayoutIface  *iface);
79 static GtkCellArea *gtk_tree_menu_cell_layout_get_area        (GtkCellLayout        *layout);
80
81
82 /* TreeModel/DrawingArea callbacks and building menus/submenus */
83 static inline void rebuild_menu                               (GtkTreeMenu          *menu);
84 static gboolean   menu_occupied                               (GtkTreeMenu          *menu,
85                                                                guint                 left_attach,
86                                                                guint                 right_attach,
87                                                                guint                 top_attach,
88                                                                guint                 bottom_attach);
89 static void       relayout_item                               (GtkTreeMenu          *menu,
90                                                                GtkWidget            *item,
91                                                                GtkTreeIter          *iter,
92                                                                GtkWidget            *prev);
93 static void       gtk_tree_menu_populate                      (GtkTreeMenu          *menu);
94 static GtkWidget *gtk_tree_menu_create_item                   (GtkTreeMenu          *menu,
95                                                                GtkTreeIter          *iter,
96                                                                gboolean              header_item);
97 static void       gtk_tree_menu_create_submenu                (GtkTreeMenu          *menu,
98                                                                GtkWidget            *item,
99                                                                GtkTreePath          *path);
100 static void       gtk_tree_menu_set_area                      (GtkTreeMenu          *menu,
101                                                                GtkCellArea          *area);
102 static GtkWidget *gtk_tree_menu_get_path_item                 (GtkTreeMenu          *menu,
103                                                                GtkTreePath          *path);
104 static gboolean   gtk_tree_menu_path_in_menu                  (GtkTreeMenu          *menu,
105                                                                GtkTreePath          *path,
106                                                                gboolean             *header_item);
107 static void       row_inserted_cb                             (GtkTreeModel         *model,
108                                                                GtkTreePath          *path,
109                                                                GtkTreeIter          *iter,
110                                                                GtkTreeMenu          *menu);
111 static void       row_deleted_cb                              (GtkTreeModel         *model,
112                                                                GtkTreePath          *path,
113                                                                GtkTreeMenu          *menu);
114 static void       row_reordered_cb                            (GtkTreeModel         *model,
115                                                                GtkTreePath          *path,
116                                                                GtkTreeIter          *iter,
117                                                                gint                 *new_order,
118                                                                GtkTreeMenu          *menu);
119 static void       row_changed_cb                              (GtkTreeModel         *model,
120                                                                GtkTreePath          *path,
121                                                                GtkTreeIter          *iter,
122                                                                GtkTreeMenu          *menu);
123 static void       context_size_changed_cb                     (GtkCellAreaContext   *context,
124                                                                GParamSpec           *pspec,
125                                                                GtkWidget            *menu);
126 static void       area_apply_attributes_cb                    (GtkCellArea          *area,
127                                                                GtkTreeModel         *tree_model,
128                                                                GtkTreeIter          *iter,
129                                                                gboolean              is_expander,
130                                                                gboolean              is_expanded,
131                                                                GtkTreeMenu          *menu);
132 static void       item_activated_cb                           (GtkMenuItem          *item,
133                                                                GtkTreeMenu          *menu);
134 static void       submenu_activated_cb                        (GtkTreeMenu          *submenu,
135                                                                const gchar          *path,
136                                                                GtkTreeMenu          *menu);
137 static void       gtk_tree_menu_set_model_internal            (GtkTreeMenu          *menu,
138                                                                GtkTreeModel         *model);
139
140
141
142 struct _GtkTreeMenuPrivate
143 {
144   /* TreeModel and parent for this menu */
145   GtkTreeModel        *model;
146   GtkTreeRowReference *root;
147
148   /* CellArea and context for this menu */
149   GtkCellArea         *area;
150   GtkCellAreaContext  *context;
151
152   /* Signals */
153   gulong               size_changed_id;
154   gulong               apply_attributes_id;
155   gulong               row_inserted_id;
156   gulong               row_deleted_id;
157   gulong               row_reordered_id;
158   gulong               row_changed_id;
159
160   /* Grid menu mode */
161   gint                 wrap_width;
162   gint                 row_span_col;
163   gint                 col_span_col;
164
165   /* Flags */
166   guint32              menu_with_header : 1;
167   guint32              tearoff     : 1;
168
169   /* Row separators */
170   GtkTreeViewRowSeparatorFunc row_separator_func;
171   gpointer                    row_separator_data;
172   GDestroyNotify              row_separator_destroy;
173
174   /* Submenu headers */
175   GtkTreeMenuHeaderFunc header_func;
176   gpointer              header_data;
177   GDestroyNotify        header_destroy;
178 };
179
180 enum {
181   PROP_0,
182   PROP_MODEL,
183   PROP_ROOT,
184   PROP_CELL_AREA,
185   PROP_TEAROFF,
186   PROP_WRAP_WIDTH,
187   PROP_ROW_SPAN_COL,
188   PROP_COL_SPAN_COL
189 };
190
191 enum {
192   SIGNAL_MENU_ACTIVATE,
193   N_SIGNALS
194 };
195
196 static guint   tree_menu_signals[N_SIGNALS] = { 0 };
197 static GQuark  tree_menu_path_quark = 0;
198
199 G_DEFINE_TYPE_WITH_CODE (GtkTreeMenu, _gtk_tree_menu, GTK_TYPE_MENU,
200                          G_IMPLEMENT_INTERFACE (GTK_TYPE_CELL_LAYOUT,
201                                                 gtk_tree_menu_cell_layout_init));
202
203 static void
204 _gtk_tree_menu_init (GtkTreeMenu *menu)
205 {
206   GtkTreeMenuPrivate *priv;
207
208   menu->priv = G_TYPE_INSTANCE_GET_PRIVATE (menu,
209                                             GTK_TYPE_TREE_MENU,
210                                             GtkTreeMenuPrivate);
211   priv = menu->priv;
212
213   priv->model     = NULL;
214   priv->root      = NULL;
215   priv->area      = NULL;
216   priv->context   = NULL;
217
218   priv->size_changed_id  = 0;
219   priv->row_inserted_id  = 0;
220   priv->row_deleted_id   = 0;
221   priv->row_reordered_id = 0;
222   priv->row_changed_id   = 0;
223
224   priv->wrap_width   = 0;
225   priv->row_span_col = -1;
226   priv->col_span_col = -1;
227
228   priv->menu_with_header = FALSE;
229   priv->tearoff          = FALSE;
230
231   priv->row_separator_func    = NULL;
232   priv->row_separator_data    = NULL;
233   priv->row_separator_destroy = NULL;
234
235   priv->header_func    = NULL;
236   priv->header_data    = NULL;
237   priv->header_destroy = NULL;
238
239   gtk_menu_set_reserve_toggle_size (GTK_MENU (menu), FALSE);
240 }
241
242 static void
243 _gtk_tree_menu_class_init (GtkTreeMenuClass *class)
244 {
245   GObjectClass   *object_class = G_OBJECT_CLASS (class);
246   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
247
248   tree_menu_path_quark = g_quark_from_static_string ("gtk-tree-menu-path");
249
250   object_class->constructor  = gtk_tree_menu_constructor;
251   object_class->dispose      = gtk_tree_menu_dispose;
252   object_class->finalize     = gtk_tree_menu_finalize;
253   object_class->set_property = gtk_tree_menu_set_property;
254   object_class->get_property = gtk_tree_menu_get_property;
255
256   widget_class->get_preferred_width  = gtk_tree_menu_get_preferred_width;
257   widget_class->get_preferred_height = gtk_tree_menu_get_preferred_height;
258
259   /*
260    * GtkTreeMenu::menu-activate:
261    * @menu: a #GtkTreeMenu
262    * @path: the #GtkTreePath string for the item which was activated
263    * @user_data: the user data
264    *
265    * This signal is emitted to notify that a menu item in the #GtkTreeMenu
266    * was activated and provides the path string from the #GtkTreeModel
267    * to specify which row was selected.
268    *
269    * Since: 3.0
270    */
271   tree_menu_signals[SIGNAL_MENU_ACTIVATE] =
272     g_signal_new (I_("menu-activate"),
273                   G_OBJECT_CLASS_TYPE (object_class),
274                   G_SIGNAL_RUN_FIRST,
275                   0, /* No class closure here */
276                   NULL, NULL,
277                   _gtk_marshal_VOID__STRING,
278                   G_TYPE_NONE, 1, G_TYPE_STRING);
279
280   /*
281    * GtkTreeMenu:model:
282    *
283    * The #GtkTreeModel from which the menu is constructed.
284    *
285    * Since: 3.0
286    */
287   g_object_class_install_property (object_class,
288                                    PROP_MODEL,
289                                    g_param_spec_object ("model",
290                                                         P_("TreeMenu model"),
291                                                         P_("The model for the tree menu"),
292                                                         GTK_TYPE_TREE_MODEL,
293                                                         GTK_PARAM_READWRITE));
294
295   /*
296    * GtkTreeMenu:root:
297    *
298    * The #GtkTreePath that is the root for this menu, or %NULL.
299    *
300    * The #GtkTreeMenu recursively creates submenus for #GtkTreeModel
301    * rows that have children and the "root" for each menu is provided
302    * by the parent menu.
303    *
304    * If you dont provide a root for the #GtkTreeMenu then the whole
305    * model will be added to the menu. Specifying a root allows you
306    * to build a menu for a given #GtkTreePath and its children.
307    * 
308    * Since: 3.0
309    */
310   g_object_class_install_property (object_class,
311                                    PROP_ROOT,
312                                    g_param_spec_boxed ("root",
313                                                        P_("TreeMenu root row"),
314                                                        P_("The TreeMenu will display children of the "
315                                                           "specified root"),
316                                                        GTK_TYPE_TREE_PATH,
317                                                        GTK_PARAM_READWRITE));
318
319   /*
320    * GtkTreeMenu:cell-area:
321    *
322    * The #GtkCellArea used to render cells in the menu items.
323    *
324    * You can provide a different cell area at object construction
325    * time, otherwise the #GtkTreeMenu will use a #GtkCellAreaBox.
326    *
327    * Since: 3.0
328    */
329   g_object_class_install_property (object_class,
330                                    PROP_CELL_AREA,
331                                    g_param_spec_object ("cell-area",
332                                                         P_("Cell Area"),
333                                                         P_("The GtkCellArea used to layout cells"),
334                                                         GTK_TYPE_CELL_AREA,
335                                                         GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
336
337   /*
338    * GtkTreeMenu:tearoff:
339    *
340    * Specifies whether this menu comes with a leading tearoff menu item
341    *
342    * Since: 3.0
343    */
344   g_object_class_install_property (object_class,
345                                    PROP_TEAROFF,
346                                    g_param_spec_boolean ("tearoff",
347                                                          P_("Tearoff"),
348                                                          P_("Whether the menu has a tearoff item"),
349                                                          FALSE,
350                                                          GTK_PARAM_READWRITE));
351
352   /*
353    * GtkTreeMenu:wrap-width:
354    *
355    * If wrap-width is set to a positive value, the list will be
356    * displayed in multiple columns, the number of columns is
357    * determined by wrap-width.
358    *
359    * Since: 3.0
360    */
361   g_object_class_install_property (object_class,
362                                    PROP_WRAP_WIDTH,
363                                    g_param_spec_int ("wrap-width",
364                                                      P_("Wrap Width"),
365                                                      P_("Wrap width for laying out items in a grid"),
366                                                      0,
367                                                      G_MAXINT,
368                                                      0,
369                                                      GTK_PARAM_READWRITE));
370
371   /*
372    * GtkTreeMenu:row-span-column:
373    *
374    * If this is set to a non-negative value, it must be the index of a column
375    * of type %G_TYPE_INT in the model.
376    *
377    * The values of that column are used to determine how many rows a value in
378    * the list will span. Therefore, the values in the model column pointed to
379    * by this property must be greater than zero and not larger than wrap-width.
380    *
381    * Since: 3.0
382    */
383   g_object_class_install_property (object_class,
384                                    PROP_ROW_SPAN_COL,
385                                    g_param_spec_int ("row-span-column",
386                                                      P_("Row span column"),
387                                                      P_("TreeModel column containing the row span values"),
388                                                      -1,
389                                                      G_MAXINT,
390                                                      -1,
391                                                      GTK_PARAM_READWRITE));
392
393   /*
394    * GtkTreeMenu:column-span-column:
395    *
396    * If this is set to a non-negative value, it must be the index of a column
397    * of type %G_TYPE_INT in the model.
398    *
399    * The values of that column are used to determine how many columns a value
400    * in the list will span.
401    *
402    * Since: 3.0
403    */
404   g_object_class_install_property (object_class,
405                                    PROP_COL_SPAN_COL,
406                                    g_param_spec_int ("column-span-column",
407                                                      P_("Column span column"),
408                                                      P_("TreeModel column containing the column span values"),
409                                                      -1,
410                                                      G_MAXINT,
411                                                      -1,
412                                                      GTK_PARAM_READWRITE));
413
414   g_type_class_add_private (object_class, sizeof (GtkTreeMenuPrivate));
415 }
416
417 /****************************************************************
418  *                         GObjectClass                         *
419  ****************************************************************/
420 static GObject  *
421 gtk_tree_menu_constructor (GType                  type,
422                            guint                  n_construct_properties,
423                            GObjectConstructParam *construct_properties)
424 {
425   GObject            *object;
426   GtkTreeMenu        *menu;
427   GtkTreeMenuPrivate *priv;
428
429   object = G_OBJECT_CLASS (_gtk_tree_menu_parent_class)->constructor
430     (type, n_construct_properties, construct_properties);
431
432   menu = GTK_TREE_MENU (object);
433   priv = menu->priv;
434
435   if (!priv->area)
436     {
437       GtkCellArea *area = gtk_cell_area_box_new ();
438
439       gtk_tree_menu_set_area (menu, area);
440     }
441
442   priv->context = gtk_cell_area_create_context (priv->area);
443
444   priv->size_changed_id =
445     g_signal_connect (priv->context, "notify",
446                       G_CALLBACK (context_size_changed_cb), menu);
447
448   return object;
449 }
450
451 static void
452 gtk_tree_menu_dispose (GObject *object)
453 {
454   GtkTreeMenu        *menu;
455   GtkTreeMenuPrivate *priv;
456
457   menu = GTK_TREE_MENU (object);
458   priv = menu->priv;
459
460   _gtk_tree_menu_set_model (menu, NULL);
461   gtk_tree_menu_set_area (menu, NULL);
462
463   if (priv->context)
464     {
465       /* Disconnect signals */
466       g_signal_handler_disconnect (priv->context, priv->size_changed_id);
467
468       g_object_unref (priv->context);
469       priv->context = NULL;
470       priv->size_changed_id = 0;
471     }
472
473   G_OBJECT_CLASS (_gtk_tree_menu_parent_class)->dispose (object);
474 }
475
476 static void
477 gtk_tree_menu_finalize (GObject *object)
478 {
479   GtkTreeMenu        *menu;
480   GtkTreeMenuPrivate *priv;
481
482   menu = GTK_TREE_MENU (object);
483   priv = menu->priv;
484
485   _gtk_tree_menu_set_row_separator_func (menu, NULL, NULL, NULL);
486   _gtk_tree_menu_set_header_func (menu, NULL, NULL, NULL);
487
488   if (priv->root)
489     gtk_tree_row_reference_free (priv->root);
490
491   G_OBJECT_CLASS (_gtk_tree_menu_parent_class)->finalize (object);
492 }
493
494 static void
495 gtk_tree_menu_set_property (GObject            *object,
496                             guint               prop_id,
497                             const GValue       *value,
498                             GParamSpec         *pspec)
499 {
500   GtkTreeMenu *menu = GTK_TREE_MENU (object);
501
502   switch (prop_id)
503     {
504     case PROP_MODEL:
505       _gtk_tree_menu_set_model (menu, g_value_get_object (value));
506       break;
507
508     case PROP_ROOT:
509       _gtk_tree_menu_set_root (menu, g_value_get_boxed (value));
510       break;
511
512     case PROP_CELL_AREA:
513       /* Construct-only, can only be assigned once */
514       gtk_tree_menu_set_area (menu, (GtkCellArea *)g_value_get_object (value));
515       break;
516
517     case PROP_TEAROFF:
518       _gtk_tree_menu_set_tearoff (menu, g_value_get_boolean (value));
519       break;
520
521     case PROP_WRAP_WIDTH:
522       _gtk_tree_menu_set_wrap_width (menu, g_value_get_int (value));
523       break;
524
525      case PROP_ROW_SPAN_COL:
526       _gtk_tree_menu_set_row_span_column (menu, g_value_get_int (value));
527       break;
528
529      case PROP_COL_SPAN_COL:
530       _gtk_tree_menu_set_column_span_column (menu, g_value_get_int (value));
531       break;
532
533     default:
534       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
535       break;
536     }
537 }
538
539 static void
540 gtk_tree_menu_get_property (GObject            *object,
541                             guint               prop_id,
542                             GValue             *value,
543                             GParamSpec         *pspec)
544 {
545   GtkTreeMenu        *menu = GTK_TREE_MENU (object);
546   GtkTreeMenuPrivate *priv = menu->priv;
547
548   switch (prop_id)
549     {
550     case PROP_MODEL:
551       g_value_set_object (value, priv->model);
552       break;
553
554     case PROP_ROOT:
555       g_value_set_boxed (value, priv->root);
556       break;
557
558     case PROP_CELL_AREA:
559       g_value_set_object (value, priv->area);
560       break;
561
562     case PROP_TEAROFF:
563       g_value_set_boolean (value, priv->tearoff);
564       break;
565
566     default:
567       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
568       break;
569     }
570 }
571
572 /****************************************************************
573  *                         GtkWidgetClass                       *
574  ****************************************************************/
575
576 /* We tell all the menu items to reserve space for the submenu
577  * indicator if there is at least one submenu, this way we ensure
578  * that every internal cell area gets allocated the
579  * same width (and requested height for the same appropriate width).
580  */
581 static void
582 sync_reserve_submenu_size (GtkTreeMenu *menu)
583 {
584   GList              *children, *l;
585   gboolean            has_submenu = FALSE;
586
587   children = gtk_container_get_children (GTK_CONTAINER (menu));
588   for (l = children; l; l = l->next)
589     {
590       GtkMenuItem *item = l->data;
591
592       if (gtk_menu_item_get_submenu (item) != NULL)
593         {
594           has_submenu = TRUE;
595           break;
596         }
597     }
598
599   for (l = children; l; l = l->next)
600     {
601       GtkMenuItem *item = l->data;
602
603       gtk_menu_item_set_reserve_indicator (item, has_submenu);
604     }
605
606   g_list_free (children);
607 }
608
609 static void
610 gtk_tree_menu_get_preferred_width (GtkWidget           *widget,
611                                    gint                *minimum_size,
612                                    gint                *natural_size)
613 {
614   GtkTreeMenu        *menu = GTK_TREE_MENU (widget);
615   GtkTreeMenuPrivate *priv = menu->priv;
616
617   /* We leave the requesting work up to the cellviews which operate in the same
618    * context, reserving space for the submenu indicator if any of the items have
619    * submenus ensures that every cellview will receive the same allocated width.
620    *
621    * Since GtkMenu does hieght-for-width correctly, we know that the width of
622    * every cell will be requested before the height-for-widths are requested.
623    */
624   g_signal_handler_block (priv->context, priv->size_changed_id);
625
626   sync_reserve_submenu_size (menu);
627
628   GTK_WIDGET_CLASS (_gtk_tree_menu_parent_class)->get_preferred_width (widget, minimum_size, natural_size);
629
630   g_signal_handler_unblock (priv->context, priv->size_changed_id);
631 }
632
633 static void
634 gtk_tree_menu_get_preferred_height (GtkWidget           *widget,
635                                     gint                *minimum_size,
636                                     gint                *natural_size)
637 {
638   GtkTreeMenu        *menu = GTK_TREE_MENU (widget);
639   GtkTreeMenuPrivate *priv = menu->priv;
640
641   g_signal_handler_block (priv->context, priv->size_changed_id);
642
643   sync_reserve_submenu_size (menu);
644
645   GTK_WIDGET_CLASS (_gtk_tree_menu_parent_class)->get_preferred_height (widget, minimum_size, natural_size);
646
647   g_signal_handler_unblock (priv->context, priv->size_changed_id);
648 }
649
650 /****************************************************************
651  *                      GtkCellLayoutIface                      *
652  ****************************************************************/
653 static void
654 gtk_tree_menu_cell_layout_init (GtkCellLayoutIface  *iface)
655 {
656   iface->get_area = gtk_tree_menu_cell_layout_get_area;
657 }
658
659 static GtkCellArea *
660 gtk_tree_menu_cell_layout_get_area (GtkCellLayout *layout)
661 {
662   GtkTreeMenu        *menu = GTK_TREE_MENU (layout);
663   GtkTreeMenuPrivate *priv = menu->priv;
664
665   return priv->area;
666 }
667
668
669 /****************************************************************
670  *             TreeModel callbacks/populating menus             *
671  ****************************************************************/
672 static GtkWidget *
673 gtk_tree_menu_get_path_item (GtkTreeMenu          *menu,
674                              GtkTreePath          *search)
675 {
676   GtkWidget *item = NULL;
677   GList     *children, *l;
678
679   children = gtk_container_get_children (GTK_CONTAINER (menu));
680
681   for (l = children; item == NULL && l != NULL; l = l->next)
682     {
683       GtkWidget   *child = l->data;
684       GtkTreePath *path  = NULL;
685
686       if (GTK_IS_SEPARATOR_MENU_ITEM (child))
687         {
688           GtkTreeRowReference *row =
689             g_object_get_qdata (G_OBJECT (child), tree_menu_path_quark);
690
691           if (row)
692             {
693               path = gtk_tree_row_reference_get_path (row);
694
695               if (!path)
696                 /* Return any first child where its row-reference became invalid,
697                  * this is because row-references get null paths before we recieve
698                  * the "row-deleted" signal.
699                  */
700                 item = child;
701             }
702         }
703       else if (!GTK_IS_TEAROFF_MENU_ITEM (child))
704         {
705           GtkWidget *view = gtk_bin_get_child (GTK_BIN (child));
706
707           /* It's always a cellview */
708           if (GTK_IS_CELL_VIEW (view))
709             path = gtk_cell_view_get_displayed_row (GTK_CELL_VIEW (view));
710
711           if (!path)
712             /* Return any first child where its row-reference became invalid,
713              * this is because row-references get null paths before we recieve
714              * the "row-deleted" signal.
715              */
716             item = child;
717         }
718
719       if (path)
720         {
721           if (gtk_tree_path_compare (search, path) == 0)
722             item = child;
723
724           gtk_tree_path_free (path);
725         }
726     }
727
728   g_list_free (children);
729
730   return item;
731 }
732
733 static gboolean
734 gtk_tree_menu_path_in_menu (GtkTreeMenu  *menu,
735                             GtkTreePath  *path,
736                             gboolean     *header_item)
737 {
738   GtkTreeMenuPrivate *priv = menu->priv;
739   gboolean            in_menu = FALSE;
740   gboolean            is_header = FALSE;
741
742   /* Check if the is in root of the model */
743   if (gtk_tree_path_get_depth (path) == 1 && !priv->root)
744     in_menu = TRUE;
745   /* If we are a submenu, compare the parent path */
746   else if (priv->root)
747     {
748       GtkTreePath *root_path   = gtk_tree_row_reference_get_path (priv->root);
749       GtkTreePath *search_path = gtk_tree_path_copy (path);
750
751       if (root_path)
752         {
753           if (priv->menu_with_header &&
754               gtk_tree_path_compare (root_path, search_path) == 0)
755             {
756               in_menu   = TRUE;
757               is_header = TRUE;
758             }
759           else if (gtk_tree_path_get_depth (search_path) > 1)
760             {
761               gtk_tree_path_up (search_path);
762
763               if (gtk_tree_path_compare (root_path, search_path) == 0)
764                 in_menu = TRUE;
765             }
766         }
767       gtk_tree_path_free (root_path);
768       gtk_tree_path_free (search_path);
769     }
770
771   if (header_item)
772     *header_item = is_header;
773
774   return in_menu;
775 }
776
777 static GtkWidget *
778 gtk_tree_menu_path_needs_submenu (GtkTreeMenu *menu,
779                                   GtkTreePath *search)
780 {
781   GtkWidget   *item = NULL;
782   GList       *children, *l;
783   GtkTreePath *parent_path;
784
785   if (gtk_tree_path_get_depth (search) <= 1)
786     return NULL;
787
788   parent_path = gtk_tree_path_copy (search);
789   gtk_tree_path_up (parent_path);
790
791   children    = gtk_container_get_children (GTK_CONTAINER (menu));
792
793   for (l = children; item == NULL && l != NULL; l = l->next)
794     {
795       GtkWidget   *child = l->data;
796       GtkTreePath *path  = NULL;
797
798       /* Separators dont get submenus, if it already has a submenu then let
799        * the submenu handle inserted rows */
800       if (!GTK_IS_SEPARATOR_MENU_ITEM (child) &&
801           !gtk_menu_item_get_submenu (GTK_MENU_ITEM (child)))
802         {
803           GtkWidget *view = gtk_bin_get_child (GTK_BIN (child));
804
805           /* It's always a cellview */
806           if (GTK_IS_CELL_VIEW (view))
807             path = gtk_cell_view_get_displayed_row (GTK_CELL_VIEW (view));
808         }
809
810       if (path)
811         {
812           if (gtk_tree_path_compare (parent_path, path) == 0)
813             item = child;
814
815           gtk_tree_path_free (path);
816         }
817     }
818
819   g_list_free (children);
820   gtk_tree_path_free (parent_path);
821
822   return item;
823 }
824
825 static GtkWidget *
826 find_empty_submenu (GtkTreeMenu  *menu)
827 {
828   GtkTreeMenuPrivate *priv = menu->priv;
829   GList              *children, *l;
830   GtkWidget          *submenu = NULL;
831
832   children = gtk_container_get_children (GTK_CONTAINER (menu));
833
834   for (l = children; submenu == NULL && l != NULL; l = l->next)
835     {
836       GtkWidget   *child = l->data;
837       GtkTreePath *path  = NULL;
838       GtkTreeIter  iter;
839
840       /* Separators dont get submenus, if it already has a submenu then let
841        * the submenu handle inserted rows */
842       if (!GTK_IS_SEPARATOR_MENU_ITEM (child) && !GTK_IS_TEAROFF_MENU_ITEM (child))
843         {
844           GtkWidget *view = gtk_bin_get_child (GTK_BIN (child));
845
846           /* It's always a cellview */
847           if (GTK_IS_CELL_VIEW (view))
848             path = gtk_cell_view_get_displayed_row (GTK_CELL_VIEW (view));
849         }
850
851       if (path)
852         {
853           if (gtk_tree_model_get_iter (priv->model, &iter, path) &&
854               !gtk_tree_model_iter_has_child (priv->model, &iter))
855             submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (child));
856
857           gtk_tree_path_free (path);
858         }
859     }
860
861   g_list_free (children);
862
863   return submenu;
864 }
865
866 static void
867 row_inserted_cb (GtkTreeModel     *model,
868                  GtkTreePath      *path,
869                  GtkTreeIter      *iter,
870                  GtkTreeMenu      *menu)
871 {
872   GtkTreeMenuPrivate *priv = menu->priv;
873   gint               *indices, index, depth;
874   GtkWidget          *item;
875
876   /* If the iter should be in this menu then go ahead and insert it */
877   if (gtk_tree_menu_path_in_menu (menu, path, NULL))
878     {
879       if (priv->wrap_width > 0)
880         rebuild_menu (menu);
881       else
882         {
883           /* Get the index of the path for this depth */
884           indices = gtk_tree_path_get_indices (path);
885           depth   = gtk_tree_path_get_depth (path);
886           index   = indices[depth -1];
887
888           /* Menus with a header include a menuitem for its root node
889            * and a separator menu item */
890           if (priv->menu_with_header)
891             index += 2;
892
893           /* Index after the tearoff item for the root menu if
894            * there is a tearoff item
895            */
896           if (priv->root == NULL && priv->tearoff)
897             index += 1;
898
899           item = gtk_tree_menu_create_item (menu, iter, FALSE);
900           gtk_menu_shell_insert (GTK_MENU_SHELL (menu), item, index);
901
902           /* Resize everything */
903           gtk_cell_area_context_reset (menu->priv->context);
904         }
905     }
906   else
907     {
908       /* Create submenus for iters if we need to */
909       item = gtk_tree_menu_path_needs_submenu (menu, path);
910       if (item)
911         {
912           GtkTreePath *item_path = gtk_tree_path_copy (path);
913
914           gtk_tree_path_up (item_path);
915           gtk_tree_menu_create_submenu (menu, item, item_path);
916           gtk_tree_path_free (item_path);
917         }
918     }
919 }
920
921 static void
922 row_deleted_cb (GtkTreeModel     *model,
923                 GtkTreePath      *path,
924                 GtkTreeMenu      *menu)
925 {
926   GtkTreeMenuPrivate *priv = menu->priv;
927   GtkWidget          *item;
928
929   /* If it's the header item we leave it to the parent menu
930    * to remove us from its menu
931    */
932   item = gtk_tree_menu_get_path_item (menu, path);
933
934   if (item)
935     {
936       if (priv->wrap_width > 0)
937         rebuild_menu (menu);
938       else
939         {
940           /* Get rid of the deleted item */
941           gtk_widget_destroy (item);
942
943           /* Resize everything */
944           gtk_cell_area_context_reset (menu->priv->context);
945         }
946     }
947   else
948     {
949       /* It's up to the parent menu to destroy a child menu that becomes empty
950        * since the topmost menu belongs to the user and is allowed to have no contents */
951       GtkWidget *submenu = find_empty_submenu (menu);
952       if (submenu)
953         gtk_widget_destroy (submenu);
954     }
955 }
956
957 static void
958 row_reordered_cb (GtkTreeModel    *model,
959                   GtkTreePath     *path,
960                   GtkTreeIter     *iter,
961                   gint            *new_order,
962                   GtkTreeMenu     *menu)
963 {
964   GtkTreeMenuPrivate *priv = menu->priv;
965   gboolean            this_menu = FALSE;
966
967   if (gtk_tree_path_get_depth (path) == 0 && !priv->root)
968     this_menu = TRUE;
969   else if (priv->root)
970     {
971       GtkTreePath *root_path =
972         gtk_tree_row_reference_get_path (priv->root);
973
974       if (gtk_tree_path_compare (root_path, path) == 0)
975         this_menu = TRUE;
976
977       gtk_tree_path_free (root_path);
978     }
979
980   if (this_menu)
981     rebuild_menu (menu);
982 }
983
984 static gint
985 menu_item_position (GtkTreeMenu *menu,
986                     GtkWidget   *item)
987 {
988   GList *children, *l;
989   gint   position;
990
991   children = gtk_container_get_children (GTK_CONTAINER (menu));
992   for (position = 0, l = children; l; position++, l = l->next)
993     {
994       GtkWidget *iitem = l->data;
995
996       if (item == iitem)
997         break;
998     }
999
1000   g_list_free (children);
1001
1002   return position;
1003 }
1004
1005 static void
1006 row_changed_cb (GtkTreeModel         *model,
1007                 GtkTreePath          *path,
1008                 GtkTreeIter          *iter,
1009                 GtkTreeMenu          *menu)
1010 {
1011   GtkTreeMenuPrivate *priv = menu->priv;
1012   gboolean            is_separator = FALSE;
1013   gboolean            has_header = FALSE;
1014   GtkWidget          *item;
1015
1016   item = gtk_tree_menu_get_path_item (menu, path);
1017
1018   if (priv->root)
1019     {
1020       GtkTreePath *root_path =
1021         gtk_tree_row_reference_get_path (priv->root);
1022
1023       if (root_path && gtk_tree_path_compare (root_path, path) == 0)
1024         {
1025           if (priv->header_func)
1026             has_header =
1027               priv->header_func (priv->model, iter, priv->header_data);
1028
1029           if (has_header && !item)
1030             {
1031               item = gtk_separator_menu_item_new ();
1032               gtk_widget_show (item);
1033               gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
1034
1035               item = gtk_tree_menu_create_item (menu, iter, TRUE);
1036               gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
1037
1038               priv->menu_with_header = TRUE;
1039             }
1040           else if (!has_header && item)
1041             {
1042               /* Destroy the header item and then the following separator */
1043               gtk_widget_destroy (item);
1044               gtk_widget_destroy (GTK_MENU_SHELL (menu)->priv->children->data);
1045
1046               priv->menu_with_header = FALSE;
1047             }
1048
1049           gtk_tree_path_free (root_path);
1050         }
1051     }
1052
1053   if (item)
1054     {
1055       if (priv->wrap_width > 0)
1056         /* Ugly, we need to rebuild the menu here if
1057          * the row-span/row-column values change
1058          */
1059         rebuild_menu (menu);
1060       else
1061         {
1062           if (priv->row_separator_func)
1063             is_separator =
1064               priv->row_separator_func (model, iter,
1065                                         priv->row_separator_data);
1066
1067
1068           if (is_separator != GTK_IS_SEPARATOR_MENU_ITEM (item))
1069             {
1070               gint position = menu_item_position (menu, item);
1071
1072               gtk_widget_destroy (item);
1073               item = gtk_tree_menu_create_item (menu, iter, FALSE);
1074               gtk_menu_shell_insert (GTK_MENU_SHELL (menu), item, position);
1075             }
1076         }
1077     }
1078 }
1079
1080 static void
1081 context_size_changed_cb (GtkCellAreaContext  *context,
1082                          GParamSpec          *pspec,
1083                          GtkWidget           *menu)
1084 {
1085   if (!strcmp (pspec->name, "minimum-width") ||
1086       !strcmp (pspec->name, "natural-width") ||
1087       !strcmp (pspec->name, "minimum-height") ||
1088       !strcmp (pspec->name, "natural-height"))
1089     gtk_widget_queue_resize (menu);
1090 }
1091
1092 static gboolean
1093 area_is_sensitive (GtkCellArea *area)
1094 {
1095   GList    *cells, *list;
1096   gboolean  sensitive = FALSE;
1097
1098   cells = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (area));
1099
1100   for (list = cells; list; list = list->next)
1101     {
1102       g_object_get (list->data, "sensitive", &sensitive, NULL);
1103
1104       if (sensitive)
1105         break;
1106     }
1107   g_list_free (cells);
1108
1109   return sensitive;
1110 }
1111
1112 static void
1113 area_apply_attributes_cb (GtkCellArea          *area,
1114                           GtkTreeModel         *tree_model,
1115                           GtkTreeIter          *iter,
1116                           gboolean              is_expander,
1117                           gboolean              is_expanded,
1118                           GtkTreeMenu          *menu)
1119 {
1120   /* If the menu for this iter has a submenu */
1121   GtkTreeMenuPrivate *priv = menu->priv;
1122   GtkTreePath        *path;
1123   GtkWidget          *item;
1124   gboolean            is_header;
1125   gboolean            sensitive;
1126
1127   path = gtk_tree_model_get_path (tree_model, iter);
1128
1129   if (gtk_tree_menu_path_in_menu (menu, path, &is_header))
1130     {
1131       item = gtk_tree_menu_get_path_item (menu, path);
1132
1133       /* If there is no submenu, go ahead and update item sensitivity,
1134        * items with submenus are always sensitive */
1135       if (item && !gtk_menu_item_get_submenu (GTK_MENU_ITEM (item)))
1136         {
1137           sensitive = area_is_sensitive (priv->area);
1138
1139           gtk_widget_set_sensitive (item, sensitive);
1140
1141           if (is_header)
1142             {
1143               /* For header items we need to set the sensitivity
1144                * of the following separator item
1145                */
1146               if (GTK_MENU_SHELL (menu)->priv->children &&
1147                   GTK_MENU_SHELL (menu)->priv->children->next)
1148                 {
1149                   GtkWidget *separator =
1150                     GTK_MENU_SHELL (menu)->priv->children->next->data;
1151
1152                   gtk_widget_set_sensitive (separator, sensitive);
1153                 }
1154             }
1155         }
1156     }
1157
1158   gtk_tree_path_free (path);
1159 }
1160
1161 static void
1162 gtk_tree_menu_set_area (GtkTreeMenu *menu,
1163                         GtkCellArea *area)
1164 {
1165   GtkTreeMenuPrivate *priv = menu->priv;
1166
1167   if (priv->area)
1168     {
1169       g_signal_handler_disconnect (priv->area,
1170                                    priv->apply_attributes_id);
1171       priv->apply_attributes_id = 0;
1172
1173       g_object_unref (priv->area);
1174     }
1175
1176   priv->area = area;
1177
1178   if (priv->area)
1179     {
1180       g_object_ref_sink (priv->area);
1181
1182       priv->apply_attributes_id =
1183         g_signal_connect (priv->area, "apply-attributes",
1184                           G_CALLBACK (area_apply_attributes_cb), menu);
1185     }
1186 }
1187
1188 static gboolean
1189 menu_occupied (GtkTreeMenu *menu,
1190                guint        left_attach,
1191                guint        right_attach,
1192                guint        top_attach,
1193                guint        bottom_attach)
1194 {
1195   GList *i;
1196
1197   for (i = GTK_MENU_SHELL (menu)->priv->children; i; i = i->next)
1198     {
1199       guint l, r, b, t;
1200
1201       gtk_container_child_get (GTK_CONTAINER (menu),
1202                                i->data,
1203                                "left-attach", &l,
1204                                "right-attach", &r,
1205                                "bottom-attach", &b,
1206                                "top-attach", &t,
1207                                NULL);
1208
1209       /* look if this item intersects with the given coordinates */
1210       if (right_attach > l && left_attach < r && bottom_attach > t && top_attach < b)
1211         return TRUE;
1212     }
1213
1214   return FALSE;
1215 }
1216
1217 static void
1218 relayout_item (GtkTreeMenu *menu,
1219                GtkWidget   *item,
1220                GtkTreeIter *iter,
1221                GtkWidget   *prev)
1222 {
1223   GtkTreeMenuPrivate *priv = menu->priv;
1224   gint                current_col = 0, current_row = 0;
1225   gint                rows = 1, cols = 1;
1226
1227   if (priv->col_span_col == -1 &&
1228       priv->row_span_col == -1 &&
1229       prev)
1230     {
1231       gtk_container_child_get (GTK_CONTAINER (menu), prev,
1232                                "right-attach", &current_col,
1233                                "top-attach", &current_row,
1234                                NULL);
1235       if (current_col + cols > priv->wrap_width)
1236         {
1237           current_col = 0;
1238           current_row++;
1239         }
1240     }
1241   else
1242     {
1243       if (priv->col_span_col != -1)
1244         gtk_tree_model_get (priv->model, iter,
1245                             priv->col_span_col, &cols,
1246                             -1);
1247       if (priv->row_span_col != -1)
1248         gtk_tree_model_get (priv->model, iter,
1249                             priv->row_span_col, &rows,
1250                             -1);
1251
1252       while (1)
1253         {
1254           if (current_col + cols > priv->wrap_width)
1255             {
1256               current_col = 0;
1257               current_row++;
1258             }
1259
1260           if (!menu_occupied (menu,
1261                               current_col, current_col + cols,
1262                               current_row, current_row + rows))
1263             break;
1264
1265           current_col++;
1266         }
1267     }
1268
1269   /* set attach props */
1270   gtk_menu_attach (GTK_MENU (menu), item,
1271                    current_col, current_col + cols,
1272                    current_row, current_row + rows);
1273 }
1274
1275 static void
1276 gtk_tree_menu_create_submenu (GtkTreeMenu *menu,
1277                               GtkWidget   *item,
1278                               GtkTreePath *path)
1279 {
1280   GtkTreeMenuPrivate *priv = menu->priv;
1281   GtkWidget          *view;
1282   GtkWidget          *submenu;
1283
1284   view = gtk_bin_get_child (GTK_BIN (item));
1285   gtk_cell_view_set_draw_sensitive (GTK_CELL_VIEW (view), TRUE);
1286
1287   submenu = _gtk_tree_menu_new_with_area (priv->area);
1288
1289   _gtk_tree_menu_set_row_separator_func (GTK_TREE_MENU (submenu),
1290                                          priv->row_separator_func,
1291                                          priv->row_separator_data,
1292                                          priv->row_separator_destroy);
1293   _gtk_tree_menu_set_header_func (GTK_TREE_MENU (submenu),
1294                                   priv->header_func,
1295                                   priv->header_data,
1296                                   priv->header_destroy);
1297
1298   _gtk_tree_menu_set_wrap_width (GTK_TREE_MENU (submenu), priv->wrap_width);
1299   _gtk_tree_menu_set_row_span_column (GTK_TREE_MENU (submenu), priv->row_span_col);
1300   _gtk_tree_menu_set_column_span_column (GTK_TREE_MENU (submenu), priv->col_span_col);
1301
1302   gtk_tree_menu_set_model_internal (GTK_TREE_MENU (submenu), priv->model);
1303   _gtk_tree_menu_set_root (GTK_TREE_MENU (submenu), path);
1304   gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), submenu);
1305
1306   g_signal_connect (submenu, "menu-activate",
1307                     G_CALLBACK (submenu_activated_cb), menu);
1308 }
1309
1310 static GtkWidget *
1311 gtk_tree_menu_create_item (GtkTreeMenu *menu,
1312                            GtkTreeIter *iter,
1313                            gboolean     header_item)
1314 {
1315   GtkTreeMenuPrivate *priv = menu->priv;
1316   GtkWidget          *item, *view;
1317   GtkTreePath        *path;
1318   gboolean            is_separator = FALSE;
1319
1320   path = gtk_tree_model_get_path (priv->model, iter);
1321
1322   if (priv->row_separator_func)
1323     is_separator =
1324       priv->row_separator_func (priv->model, iter,
1325                                 priv->row_separator_data);
1326
1327   if (is_separator)
1328     {
1329       item = gtk_separator_menu_item_new ();
1330       gtk_widget_show (item);
1331
1332       g_object_set_qdata_full (G_OBJECT (item),
1333                                tree_menu_path_quark,
1334                                gtk_tree_row_reference_new (priv->model, path),
1335                                (GDestroyNotify)gtk_tree_row_reference_free);
1336     }
1337   else
1338     {
1339       view = gtk_cell_view_new_with_context (priv->area, priv->context);
1340       item = gtk_menu_item_new ();
1341       gtk_widget_show (view);
1342       gtk_widget_show (item);
1343
1344       gtk_cell_view_set_model (GTK_CELL_VIEW (view), priv->model);
1345       gtk_cell_view_set_displayed_row (GTK_CELL_VIEW (view), path);
1346
1347       gtk_widget_show (view);
1348       gtk_container_add (GTK_CONTAINER (item), view);
1349
1350       g_signal_connect (item, "activate", G_CALLBACK (item_activated_cb), menu);
1351
1352       /* Add a GtkTreeMenu submenu to render the children of this row */
1353       if (header_item == FALSE &&
1354           gtk_tree_model_iter_has_child (priv->model, iter))
1355         gtk_tree_menu_create_submenu (menu, item, path);
1356     }
1357
1358   gtk_tree_path_free (path);
1359
1360   return item;
1361 }
1362
1363 static inline void
1364 rebuild_menu (GtkTreeMenu *menu)
1365 {
1366   GtkTreeMenuPrivate *priv = menu->priv;
1367
1368   /* Destroy all the menu items */
1369   gtk_container_foreach (GTK_CONTAINER (menu),
1370                          (GtkCallback) gtk_widget_destroy, NULL);
1371
1372   /* Populate */
1373   if (priv->model)
1374     gtk_tree_menu_populate (menu);
1375 }
1376
1377
1378 static void
1379 gtk_tree_menu_populate (GtkTreeMenu *menu)
1380 {
1381   GtkTreeMenuPrivate *priv = menu->priv;
1382   GtkTreePath        *path = NULL;
1383   GtkTreeIter         parent;
1384   GtkTreeIter         iter;
1385   gboolean            valid = FALSE;
1386   GtkWidget          *menu_item, *prev = NULL;
1387
1388   if (!priv->model)
1389     return;
1390
1391   if (priv->root)
1392     path = gtk_tree_row_reference_get_path (priv->root);
1393
1394   if (path)
1395     {
1396       if (gtk_tree_model_get_iter (priv->model, &parent, path))
1397         {
1398           valid = gtk_tree_model_iter_children (priv->model, &iter, &parent);
1399
1400           if (priv->header_func &&
1401               priv->header_func (priv->model, &parent, priv->header_data))
1402             {
1403               /* Add a submenu header for rows which desire one, used for
1404                * combo boxes to allow all rows to be activatable/selectable
1405                */
1406               menu_item = gtk_tree_menu_create_item (menu, &parent, TRUE);
1407               gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);
1408
1409               menu_item = gtk_separator_menu_item_new ();
1410               gtk_widget_show (menu_item);
1411               gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);
1412
1413               prev = menu_item;
1414               priv->menu_with_header = TRUE;
1415             }
1416         }
1417       gtk_tree_path_free (path);
1418     }
1419   else
1420     {
1421       /* Tearoff menu items only go in the root menu */
1422       if (priv->tearoff)
1423         {
1424           menu_item = gtk_tearoff_menu_item_new ();
1425           gtk_widget_show (menu_item);
1426
1427           if (priv->wrap_width > 0)
1428             gtk_menu_attach (GTK_MENU (menu), menu_item, 0, priv->wrap_width, 0, 1);
1429           else
1430             gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);
1431
1432           prev = menu_item;
1433         }
1434
1435       valid = gtk_tree_model_iter_children (priv->model, &iter, NULL);
1436     }
1437
1438   /* Create a menu item for every row at the current depth, add a GtkTreeMenu
1439    * submenu for iters/items that have children */
1440   while (valid)
1441     {
1442       menu_item = gtk_tree_menu_create_item (menu, &iter, FALSE);
1443
1444       gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);
1445
1446       if (priv->wrap_width > 0)
1447         relayout_item (menu, menu_item, &iter, prev);
1448
1449       prev  = menu_item;
1450       valid = gtk_tree_model_iter_next (priv->model, &iter);
1451     }
1452 }
1453
1454 static void
1455 item_activated_cb (GtkMenuItem          *item,
1456                    GtkTreeMenu          *menu)
1457 {
1458   GtkCellView *view;
1459   GtkTreePath *path;
1460   gchar       *path_str;
1461
1462   /* Only activate leafs, not parents */
1463   if (!gtk_menu_item_get_submenu (item))
1464     {
1465       view     = GTK_CELL_VIEW (gtk_bin_get_child (GTK_BIN (item)));
1466       path     = gtk_cell_view_get_displayed_row (view);
1467       path_str = gtk_tree_path_to_string (path);
1468
1469       g_signal_emit (menu, tree_menu_signals[SIGNAL_MENU_ACTIVATE], 0, path_str);
1470
1471       g_free (path_str);
1472       gtk_tree_path_free (path);
1473     }
1474 }
1475
1476 static void
1477 submenu_activated_cb (GtkTreeMenu          *submenu,
1478                       const gchar          *path,
1479                       GtkTreeMenu          *menu)
1480 {
1481   g_signal_emit (menu, tree_menu_signals[SIGNAL_MENU_ACTIVATE], 0, path);
1482 }
1483
1484 /* Sets the model without rebuilding the menu, prevents
1485  * infinite recursion while building submenus (we wait
1486  * until the root is set and then build the menu) */
1487 static void
1488 gtk_tree_menu_set_model_internal (GtkTreeMenu  *menu,
1489                                   GtkTreeModel *model)
1490 {
1491   GtkTreeMenuPrivate *priv;
1492
1493   priv = menu->priv;
1494
1495   if (priv->model != model)
1496     {
1497       if (priv->model)
1498         {
1499           /* Disconnect signals */
1500           g_signal_handler_disconnect (priv->model,
1501                                        priv->row_inserted_id);
1502           g_signal_handler_disconnect (priv->model,
1503                                        priv->row_deleted_id);
1504           g_signal_handler_disconnect (priv->model,
1505                                        priv->row_reordered_id);
1506           g_signal_handler_disconnect (priv->model,
1507                                        priv->row_changed_id);
1508           priv->row_inserted_id  = 0;
1509           priv->row_deleted_id   = 0;
1510           priv->row_reordered_id = 0;
1511           priv->row_changed_id = 0;
1512
1513           g_object_unref (priv->model);
1514         }
1515
1516       priv->model = model;
1517
1518       if (priv->model)
1519         {
1520           g_object_ref (priv->model);
1521
1522           /* Connect signals */
1523           priv->row_inserted_id  = g_signal_connect (priv->model, "row-inserted",
1524                                                      G_CALLBACK (row_inserted_cb), menu);
1525           priv->row_deleted_id   = g_signal_connect (priv->model, "row-deleted",
1526                                                      G_CALLBACK (row_deleted_cb), menu);
1527           priv->row_reordered_id = g_signal_connect (priv->model, "rows-reordered",
1528                                                      G_CALLBACK (row_reordered_cb), menu);
1529           priv->row_changed_id   = g_signal_connect (priv->model, "row-changed",
1530                                                      G_CALLBACK (row_changed_cb), menu);
1531         }
1532     }
1533 }
1534
1535 /****************************************************************
1536  *                            API                               *
1537  ****************************************************************/
1538
1539 /**
1540  * _gtk_tree_menu_new:
1541  *
1542  * Creates a new #GtkTreeMenu.
1543  *
1544  * Return value: A newly created #GtkTreeMenu with no model or root.
1545  *
1546  * Since: 3.0
1547  */
1548 GtkWidget *
1549 _gtk_tree_menu_new (void)
1550 {
1551   return (GtkWidget *)g_object_new (GTK_TYPE_TREE_MENU, NULL);
1552 }
1553
1554 /*
1555  * _gtk_tree_menu_new_with_area:
1556  * @area: the #GtkCellArea to use to render cells in the menu
1557  *
1558  * Creates a new #GtkTreeMenu using @area to render its cells.
1559  *
1560  * Return value: A newly created #GtkTreeMenu with no model or root.
1561  *
1562  * Since: 3.0
1563  */
1564 GtkWidget *
1565 _gtk_tree_menu_new_with_area (GtkCellArea    *area)
1566 {
1567   return (GtkWidget *)g_object_new (GTK_TYPE_TREE_MENU,
1568                                     "cell-area", area,
1569                                     NULL);
1570 }
1571
1572 /*
1573  * _gtk_tree_menu_new_full:
1574  * @area: (allow-none): the #GtkCellArea to use to render cells in the menu, or %NULL.
1575  * @model: (allow-none): the #GtkTreeModel to build the menu heirarchy from, or %NULL.
1576  * @root: (allow-none): the #GtkTreePath indicating the root row for this menu, or %NULL.
1577  *
1578  * Creates a new #GtkTreeMenu hierarchy from the provided @model and @root using @area to render its cells.
1579  *
1580  * Return value: A newly created #GtkTreeMenu.
1581  *
1582  * Since: 3.0
1583  */
1584 GtkWidget *
1585 _gtk_tree_menu_new_full (GtkCellArea         *area,
1586                          GtkTreeModel        *model,
1587                          GtkTreePath         *root)
1588 {
1589   return (GtkWidget *)g_object_new (GTK_TYPE_TREE_MENU,
1590                                     "cell-area", area,
1591                                     "model", model,
1592                                     "root", root,
1593                                     NULL);
1594 }
1595
1596 /*
1597  * _gtk_tree_menu_set_model:
1598  * @menu: a #GtkTreeMenu
1599  * @model: (allow-none): the #GtkTreeModel to build the menu hierarchy from, or %NULL.
1600  *
1601  * Sets @model to be used to build the menu heirarhcy.
1602  *
1603  * Since: 3.0
1604  */
1605 void
1606 _gtk_tree_menu_set_model (GtkTreeMenu  *menu,
1607                           GtkTreeModel *model)
1608 {
1609   g_return_if_fail (GTK_IS_TREE_MENU (menu));
1610   g_return_if_fail (model == NULL || GTK_IS_TREE_MODEL (model));
1611
1612   gtk_tree_menu_set_model_internal (menu, model);
1613
1614   rebuild_menu (menu);
1615 }
1616
1617 /*
1618  * _gtk_tree_menu_get_model:
1619  * @menu: a #GtkTreeMenu
1620  *
1621  * Gets the @model currently used for the menu heirarhcy.
1622  *
1623  * Return value: (transfer none): the #GtkTreeModel which is used
1624  * for @menu's hierarchy.
1625  *
1626  * Since: 3.0
1627  */
1628 GtkTreeModel *
1629 _gtk_tree_menu_get_model (GtkTreeMenu *menu)
1630 {
1631   GtkTreeMenuPrivate *priv;
1632
1633   g_return_val_if_fail (GTK_IS_TREE_MENU (menu), NULL);
1634
1635   priv = menu->priv;
1636
1637   return priv->model;
1638 }
1639
1640 /*
1641  * _gtk_tree_menu_set_root:
1642  * @menu: a #GtkTreeMenu
1643  * @path: (allow-none): the #GtkTreePath which is the root of @menu, or %NULL.
1644  *
1645  * Sets the root of a @menu's hierarchy to be @path. @menu must already
1646  * have a model set and @path must point to a valid path inside the model.
1647  *
1648  * Since: 3.0
1649  */
1650 void
1651 _gtk_tree_menu_set_root (GtkTreeMenu *menu,
1652                          GtkTreePath *path)
1653 {
1654   GtkTreeMenuPrivate *priv;
1655
1656   g_return_if_fail (GTK_IS_TREE_MENU (menu));
1657   g_return_if_fail (menu->priv->model != NULL || path == NULL);
1658
1659   priv = menu->priv;
1660
1661   if (priv->root)
1662     gtk_tree_row_reference_free (priv->root);
1663
1664   if (path)
1665     priv->root = gtk_tree_row_reference_new (priv->model, path);
1666   else
1667     priv->root = NULL;
1668
1669   rebuild_menu (menu);
1670 }
1671
1672 /*
1673  * _gtk_tree_menu_get_root:
1674  * @menu: a #GtkTreeMenu
1675  *
1676  * Gets the @root path for @menu's hierarchy, or returns %NULL if @menu
1677  * has no model or is building a heirarchy for the entire model. *
1678  *
1679  * Return value: (transfer full) (allow-none): A newly created #GtkTreePath
1680  * pointing to the root of @menu which must be freed with gtk_tree_path_free().
1681  *
1682  * Since: 3.0
1683  */
1684 GtkTreePath *
1685 _gtk_tree_menu_get_root (GtkTreeMenu *menu)
1686 {
1687   GtkTreeMenuPrivate *priv;
1688
1689   g_return_val_if_fail (GTK_IS_TREE_MENU (menu), NULL);
1690
1691   priv = menu->priv;
1692
1693   if (priv->root)
1694     return gtk_tree_row_reference_get_path (priv->root);
1695
1696   return NULL;
1697 }
1698
1699 /*
1700  * _gtk_tree_menu_get_tearoff:
1701  * @menu: a #GtkTreeMenu
1702  *
1703  * Gets whether this menu is build with a leading tearoff menu item.
1704  *
1705  * Return value: %TRUE if the menu has a tearoff item.
1706  *
1707  * Since: 3.0
1708  */
1709 gboolean
1710 _gtk_tree_menu_get_tearoff (GtkTreeMenu *menu)
1711 {
1712   GtkTreeMenuPrivate *priv;
1713
1714   g_return_val_if_fail (GTK_IS_TREE_MENU (menu), FALSE);
1715
1716   priv = menu->priv;
1717
1718   return priv->tearoff;
1719 }
1720
1721 /*
1722  * _gtk_tree_menu_set_tearoff:
1723  * @menu: a #GtkTreeMenu
1724  * @tearoff: whether the menu should have a leading tearoff menu item.
1725  *
1726  * Sets whether this menu has a leading tearoff menu item.
1727  *
1728  * Since: 3.0
1729  */
1730 void
1731 _gtk_tree_menu_set_tearoff (GtkTreeMenu *menu,
1732                             gboolean     tearoff)
1733 {
1734   GtkTreeMenuPrivate *priv;
1735
1736   g_return_if_fail (GTK_IS_TREE_MENU (menu));
1737
1738   priv = menu->priv;
1739
1740   if (priv->tearoff != tearoff)
1741     {
1742       priv->tearoff = tearoff;
1743
1744       rebuild_menu (menu);
1745
1746       g_object_notify (G_OBJECT (menu), "tearoff");
1747     }
1748 }
1749
1750 /*
1751  * _gtk_tree_menu_get_wrap_width:
1752  * @menu: a #GtkTreeMenu
1753  *
1754  * Gets the wrap width which is used to determine the number of columns
1755  * for @menu. If the wrap width is larger than 1, @menu is in table mode.
1756  *
1757  * Return value: the wrap width.
1758  *
1759  * Since: 3.0
1760  */
1761 gint
1762 _gtk_tree_menu_get_wrap_width (GtkTreeMenu *menu)
1763 {
1764   GtkTreeMenuPrivate *priv;
1765
1766   g_return_val_if_fail (GTK_IS_TREE_MENU (menu), FALSE);
1767
1768   priv = menu->priv;
1769
1770   return priv->wrap_width;
1771 }
1772
1773 /*
1774  * _gtk_tree_menu_set_wrap_width:
1775  * @menu: a #GtkTreeMenu
1776  * @width: the wrap width
1777  *
1778  * Sets the wrap width which is used to determine the number of columns
1779  * for @menu. If the wrap width is larger than 1, @menu is in table mode.
1780  *
1781  * Since: 3.0
1782  */
1783 void
1784 _gtk_tree_menu_set_wrap_width (GtkTreeMenu *menu,
1785                                gint         width)
1786 {
1787   GtkTreeMenuPrivate *priv;
1788
1789   g_return_if_fail (GTK_IS_TREE_MENU (menu));
1790   g_return_if_fail (width >= 0);
1791
1792   priv = menu->priv;
1793
1794   if (priv->wrap_width != width)
1795     {
1796       priv->wrap_width = width;
1797
1798       rebuild_menu (menu);
1799
1800       g_object_notify (G_OBJECT (menu), "wrap-width");
1801     }
1802 }
1803
1804 /*
1805  * _gtk_tree_menu_get_row_span_column:
1806  * @menu: a #GtkTreeMenu
1807  *
1808  * Gets the column with row span information for @menu.
1809  * The row span column contains integers which indicate how many rows
1810  * a menu item should span.
1811  *
1812  * Return value: the column in @menu's model containing row span information, or -1.
1813  *
1814  * Since: 3.0
1815  */
1816 gint
1817 _gtk_tree_menu_get_row_span_column (GtkTreeMenu *menu)
1818 {
1819   GtkTreeMenuPrivate *priv;
1820
1821   g_return_val_if_fail (GTK_IS_TREE_MENU (menu), FALSE);
1822
1823   priv = menu->priv;
1824
1825   return priv->row_span_col;
1826 }
1827
1828 /*
1829  * _gtk_tree_menu_set_row_span_column:
1830  * @menu: a #GtkTreeMenu
1831  * @row_span: the column in the model to fetch the row span for a given menu item.
1832  *
1833  * Sets the column with row span information for @menu to be @row_span.
1834  * The row span column contains integers which indicate how many rows
1835  * a menu item should span.
1836  *
1837  * Since: 3.0
1838  */
1839 void
1840 _gtk_tree_menu_set_row_span_column (GtkTreeMenu *menu,
1841                                     gint         row_span)
1842 {
1843   GtkTreeMenuPrivate *priv;
1844
1845   g_return_if_fail (GTK_IS_TREE_MENU (menu));
1846
1847   priv = menu->priv;
1848
1849   if (priv->row_span_col != row_span)
1850     {
1851       priv->row_span_col = row_span;
1852
1853       if (priv->wrap_width > 0)
1854         rebuild_menu (menu);
1855
1856       g_object_notify (G_OBJECT (menu), "row-span-column");
1857     }
1858 }
1859
1860 /*
1861  * _gtk_tree_menu_get_column_span_column:
1862  * @menu: a #GtkTreeMenu
1863  *
1864  * Gets the column with column span information for @menu.
1865  * The column span column contains integers which indicate how many columns
1866  * a menu item should span.
1867  *
1868  * Return value: the column in @menu's model containing column span information, or -1.
1869  *
1870  * Since: 3.0
1871  */
1872 gint
1873 _gtk_tree_menu_get_column_span_column (GtkTreeMenu *menu)
1874 {
1875   GtkTreeMenuPrivate *priv;
1876
1877   g_return_val_if_fail (GTK_IS_TREE_MENU (menu), FALSE);
1878
1879   priv = menu->priv;
1880
1881   return priv->col_span_col;
1882 }
1883
1884 /*
1885  * _gtk_tree_menu_set_column_span_column:
1886  * @menu: a #GtkTreeMenu
1887  * @column_span: the column in the model to fetch the column span for a given menu item.
1888  *
1889  * Sets the column with column span information for @menu to be @column_span.
1890  * The column span column contains integers which indicate how many columns
1891  * a menu item should span.
1892  *
1893  * Since: 3.0
1894  */
1895 void
1896 _gtk_tree_menu_set_column_span_column (GtkTreeMenu *menu,
1897                                        gint         column_span)
1898 {
1899   GtkTreeMenuPrivate *priv;
1900
1901   g_return_if_fail (GTK_IS_TREE_MENU (menu));
1902
1903   priv = menu->priv;
1904
1905   if (priv->col_span_col != column_span)
1906     {
1907       priv->col_span_col = column_span;
1908
1909       if (priv->wrap_width > 0)
1910         rebuild_menu (menu);
1911
1912       g_object_notify (G_OBJECT (menu), "column-span-column");
1913     }
1914 }
1915
1916 /*
1917  * _gtk_tree_menu_get_row_separator_func:
1918  * @menu: a #GtkTreeMenu
1919  *
1920  * Gets the current #GtkTreeViewRowSeparatorFunc separator function.
1921  *
1922  * Return value: the current row separator function.
1923  *
1924  * Since: 3.0
1925  */
1926 GtkTreeViewRowSeparatorFunc
1927 _gtk_tree_menu_get_row_separator_func (GtkTreeMenu *menu)
1928 {
1929   GtkTreeMenuPrivate *priv;
1930
1931   g_return_val_if_fail (GTK_IS_TREE_MENU (menu), NULL);
1932
1933   priv = menu->priv;
1934
1935   return priv->row_separator_func;
1936 }
1937
1938 /*
1939  * _gtk_tree_menu_set_row_separator_func:
1940  * @menu: a #GtkTreeMenu
1941  * @func: (allow-none): a #GtkTreeViewRowSeparatorFunc, or %NULL to unset the separator function.
1942  * @data: (allow-none): user data to pass to @func, or %NULL
1943  * @destroy: (allow-none): destroy notifier for @data, or %NULL
1944  *
1945  * Sets the row separator function, which is used to determine
1946  * whether a row should be drawn as a separator. If the row separator
1947  * function is %NULL, no separators are drawn. This is the default value.
1948  *
1949  * Since: 3.0
1950  */
1951 void
1952 _gtk_tree_menu_set_row_separator_func (GtkTreeMenu          *menu,
1953                                        GtkTreeViewRowSeparatorFunc func,
1954                                        gpointer              data,
1955                                        GDestroyNotify        destroy)
1956 {
1957   GtkTreeMenuPrivate *priv;
1958
1959   g_return_if_fail (GTK_IS_TREE_MENU (menu));
1960
1961   priv = menu->priv;
1962
1963   if (priv->row_separator_destroy)
1964     priv->row_separator_destroy (priv->row_separator_data);
1965
1966   priv->row_separator_func    = func;
1967   priv->row_separator_data    = data;
1968   priv->row_separator_destroy = destroy;
1969
1970   rebuild_menu (menu);
1971 }
1972
1973 /*
1974  * _gtk_tree_menu_get_header_func:
1975  * @menu: a #GtkTreeMenu
1976  *
1977  * Gets the current #GtkTreeMenuHeaderFunc header function.
1978  *
1979  * Return value: the current header function.
1980  *
1981  * Since: 3.0
1982  */
1983 GtkTreeMenuHeaderFunc
1984 _gtk_tree_menu_get_header_func (GtkTreeMenu *menu)
1985 {
1986   GtkTreeMenuPrivate *priv;
1987
1988   g_return_val_if_fail (GTK_IS_TREE_MENU (menu), NULL);
1989
1990   priv = menu->priv;
1991
1992   return priv->header_func;
1993 }
1994
1995 /*
1996  * _gtk_tree_menu_set_header_func:
1997  * @menu: a #GtkTreeMenu
1998  * @func: (allow-none): a #GtkTreeMenuHeaderFunc, or %NULL to unset the header function.
1999  * @data: (allow-none): user data to pass to @func, or %NULL
2000  * @destroy: (allow-none): destroy notifier for @data, or %NULL
2001  *
2002  * Sets the header function, which is used to determine
2003  * whether a row width children should contain a leading header
2004  * menu item to allow that row to be selectable as an independant
2005  * menu item. If the header function is %NULL, no rows with children
2006  * have menu items which can be activated as leafs.
2007  * This is the default value.
2008  *
2009  * Since: 3.0
2010  */
2011 void
2012 _gtk_tree_menu_set_header_func (GtkTreeMenu          *menu,
2013                                 GtkTreeMenuHeaderFunc func,
2014                                 gpointer              data,
2015                                 GDestroyNotify        destroy)
2016 {
2017   GtkTreeMenuPrivate *priv;
2018
2019   g_return_if_fail (GTK_IS_TREE_MENU (menu));
2020
2021   priv = menu->priv;
2022
2023   if (priv->header_destroy)
2024     priv->header_destroy (priv->header_data);
2025
2026   priv->header_func    = func;
2027   priv->header_data    = data;
2028   priv->header_destroy = destroy;
2029
2030   rebuild_menu (menu);
2031 }