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