]> Pileus Git - ~andy/gtk/blob - gtk/gtktreemenu.c
Support grid mode in GtkTreeMenu
[~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  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Library General Public
10  * License as published by the Free Software Foundation; either
11  * version 2 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Library General Public License for more details.
17  *
18  * You should have received a copy of the GNU Library General Public
19  * License along with this library; if not, write to the
20  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
21  * Boston, MA 02111-1307, USA.
22  */
23
24 #include "config.h"
25 #include "gtkintl.h"
26 #include "gtktreemenu.h"
27 #include "gtkmarshalers.h"
28 #include "gtkmenuitem.h"
29 #include "gtktearoffmenuitem.h"
30 #include "gtkseparatormenuitem.h"
31 #include "gtkcellareabox.h"
32 #include "gtkcellareacontext.h"
33 #include "gtkcelllayout.h"
34 #include "gtkcellview.h"
35 #include "gtkprivate.h"
36
37
38 /* GObjectClass */
39 static GObject  *gtk_tree_menu_constructor                    (GType                  type,
40                                                                guint                  n_construct_properties,
41                                                                GObjectConstructParam *construct_properties);
42 static void      gtk_tree_menu_dispose                        (GObject            *object);
43 static void      gtk_tree_menu_finalize                       (GObject            *object);
44 static void      gtk_tree_menu_set_property                   (GObject            *object,
45                                                                guint               prop_id,
46                                                                const GValue       *value,
47                                                                GParamSpec         *pspec);
48 static void      gtk_tree_menu_get_property                   (GObject            *object,
49                                                                guint               prop_id,
50                                                                GValue             *value,
51                                                                GParamSpec         *pspec);
52
53 /* GtkWidgetClass */
54 static void      gtk_tree_menu_get_preferred_width            (GtkWidget           *widget,
55                                                                gint                *minimum_size,
56                                                                gint                *natural_size);
57 static void      gtk_tree_menu_get_preferred_height           (GtkWidget           *widget,
58                                                                gint                *minimum_size,
59                                                                gint                *natural_size);
60
61 /* GtkCellLayoutIface */
62 static void      gtk_tree_menu_cell_layout_init               (GtkCellLayoutIface  *iface);
63 static GtkCellArea *gtk_tree_menu_cell_layout_get_area        (GtkCellLayout        *layout);
64
65
66 /* TreeModel/DrawingArea callbacks and building menus/submenus */
67 static inline void rebuild_menu                               (GtkTreeMenu          *menu);
68 static gboolean   menu_occupied                               (GtkTreeMenu          *menu,
69                                                                guint                 left_attach,
70                                                                guint                 right_attach,
71                                                                guint                 top_attach,
72                                                                guint                 bottom_attach);
73 static void       relayout_item                               (GtkTreeMenu          *menu,
74                                                                GtkWidget            *item,
75                                                                GtkTreeIter          *iter,
76                                                                GtkWidget            *prev);
77 static void       gtk_tree_menu_populate                      (GtkTreeMenu          *menu);
78 static GtkWidget *gtk_tree_menu_create_item                   (GtkTreeMenu          *menu,
79                                                                GtkTreeIter          *iter,
80                                                                gboolean              header_item);
81 static void       gtk_tree_menu_set_area                      (GtkTreeMenu          *menu,
82                                                                GtkCellArea          *area);
83 static GtkWidget *gtk_tree_menu_get_path_item                 (GtkTreeMenu          *menu,
84                                                                GtkTreePath          *path);
85 static void       row_inserted_cb                             (GtkTreeModel         *model,
86                                                                GtkTreePath          *path,
87                                                                GtkTreeIter          *iter,
88                                                                GtkTreeMenu          *menu);
89 static void       row_deleted_cb                              (GtkTreeModel         *model,
90                                                                GtkTreePath          *path,
91                                                                GtkTreeMenu          *menu);
92 static void       row_reordered_cb                            (GtkTreeModel         *model,
93                                                                GtkTreePath          *path,
94                                                                GtkTreeIter          *iter,
95                                                                gint                 *new_order,
96                                                                GtkTreeMenu          *menu);
97 static void       row_changed_cb                              (GtkTreeModel         *model,
98                                                                GtkTreePath          *path,
99                                                                GtkTreeIter          *iter,
100                                                                GtkTreeMenu          *menu);
101
102 static void       context_size_changed_cb                     (GtkCellAreaContext   *context,
103                                                                GParamSpec           *pspec,
104                                                                GtkWidget            *menu);
105 static void       item_activated_cb                           (GtkMenuItem          *item,
106                                                                GtkTreeMenu          *menu);
107 static void       submenu_activated_cb                        (GtkTreeMenu          *submenu,
108                                                                const gchar          *path,
109                                                                GtkTreeMenu          *menu);
110
111
112
113 struct _GtkTreeMenuPrivate
114 {
115   /* TreeModel and parent for this menu */
116   GtkTreeModel        *model;
117   GtkTreeRowReference *root;
118
119   /* CellArea and context for this menu */
120   GtkCellArea         *area;
121   GtkCellAreaContext  *context;
122   
123   /* Signals */
124   gulong               size_changed_id;
125   gulong               row_inserted_id;
126   gulong               row_deleted_id;
127   gulong               row_reordered_id;
128   gulong               row_changed_id;
129
130   /* Grid menu mode */
131   gint                 wrap_width;
132   gint                 row_span_col;
133   gint                 col_span_col;
134
135   /* Flags */
136   guint32              menu_with_header : 1;
137   guint32              tearoff     : 1;
138
139   /* Row separators */
140   GtkTreeViewRowSeparatorFunc row_separator_func;
141   gpointer                    row_separator_data;
142   GDestroyNotify              row_separator_destroy;
143
144   /* Submenu headers */
145   GtkTreeMenuHeaderFunc header_func;
146   gpointer              header_data;
147   GDestroyNotify        header_destroy;
148 };
149
150 enum {
151   PROP_0,
152   PROP_MODEL,
153   PROP_ROOT,
154   PROP_CELL_AREA,
155   PROP_TEAROFF,
156   PROP_WRAP_WIDTH,
157   PROP_ROW_SPAN_COL,
158   PROP_COL_SPAN_COL
159 };
160
161 enum {
162   SIGNAL_MENU_ACTIVATE,
163   N_SIGNALS
164 };
165
166 static guint   tree_menu_signals[N_SIGNALS] = { 0 };
167 static GQuark  tree_menu_path_quark = 0;
168
169 G_DEFINE_TYPE_WITH_CODE (GtkTreeMenu, gtk_tree_menu, GTK_TYPE_MENU,
170                          G_IMPLEMENT_INTERFACE (GTK_TYPE_CELL_LAYOUT,
171                                                 gtk_tree_menu_cell_layout_init));
172
173 static void
174 gtk_tree_menu_init (GtkTreeMenu *menu)
175 {
176   GtkTreeMenuPrivate *priv;
177
178   menu->priv = G_TYPE_INSTANCE_GET_PRIVATE (menu,
179                                             GTK_TYPE_TREE_MENU,
180                                             GtkTreeMenuPrivate);
181   priv = menu->priv;
182
183   priv->model     = NULL;
184   priv->root      = NULL;
185   priv->area      = NULL;
186   priv->context   = NULL;
187
188   priv->size_changed_id  = 0;
189   priv->row_inserted_id  = 0;
190   priv->row_deleted_id   = 0;
191   priv->row_reordered_id = 0;
192   priv->row_changed_id   = 0;
193
194   priv->wrap_width   = 0;
195   priv->row_span_col = -1;
196   priv->col_span_col = -1;
197
198   priv->menu_with_header = FALSE;
199   priv->tearoff          = FALSE;
200
201   priv->row_separator_func    = NULL;
202   priv->row_separator_data    = NULL;
203   priv->row_separator_destroy = NULL;
204
205   priv->header_func    = NULL;
206   priv->header_data    = NULL;
207   priv->header_destroy = NULL;
208
209   gtk_menu_set_reserve_toggle_size (GTK_MENU (menu), FALSE);
210 }
211
212 static void 
213 gtk_tree_menu_class_init (GtkTreeMenuClass *class)
214 {
215   GObjectClass   *object_class = G_OBJECT_CLASS (class);
216   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
217
218   tree_menu_path_quark = g_quark_from_static_string ("gtk-tree-menu-path");
219
220   object_class->constructor  = gtk_tree_menu_constructor;
221   object_class->dispose      = gtk_tree_menu_dispose;
222   object_class->finalize     = gtk_tree_menu_finalize;
223   object_class->set_property = gtk_tree_menu_set_property;
224   object_class->get_property = gtk_tree_menu_get_property;
225
226   widget_class->get_preferred_width            = gtk_tree_menu_get_preferred_width;
227   widget_class->get_preferred_height           = gtk_tree_menu_get_preferred_height;
228
229   tree_menu_signals[SIGNAL_MENU_ACTIVATE] =
230     g_signal_new (I_("menu-activate"),
231                   G_OBJECT_CLASS_TYPE (object_class),
232                   G_SIGNAL_RUN_FIRST,
233                   0, /* No class closure here */
234                   NULL, NULL,
235                   _gtk_marshal_VOID__STRING,
236                   G_TYPE_NONE, 1, G_TYPE_STRING);
237
238   g_object_class_install_property (object_class,
239                                    PROP_MODEL,
240                                    g_param_spec_object ("model",
241                                                         P_("TreeMenu model"),
242                                                         P_("The model for the tree menu"),
243                                                         GTK_TYPE_TREE_MODEL,
244                                                         GTK_PARAM_READWRITE));
245
246   g_object_class_install_property (object_class,
247                                    PROP_ROOT,
248                                    g_param_spec_boxed ("root",
249                                                        P_("TreeMenu root row"),
250                                                        P_("The TreeMenu will display children of the "
251                                                           "specified root"),
252                                                        GTK_TYPE_TREE_PATH,
253                                                        GTK_PARAM_READWRITE));
254
255   g_object_class_install_property (object_class,
256                                    PROP_CELL_AREA,
257                                    g_param_spec_object ("cell-area",
258                                                         P_("Cell Area"),
259                                                         P_("The GtkCellArea used to layout cells"),
260                                                         GTK_TYPE_CELL_AREA,
261                                                         GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
262
263   g_object_class_install_property (object_class,
264                                    PROP_TEAROFF,
265                                    g_param_spec_boolean ("tearoff",
266                                                          P_("Tearoff"),
267                                                          P_("Whether the menu has a tearoff item"),
268                                                          FALSE,
269                                                          GTK_PARAM_READWRITE));
270
271   /**
272    * GtkTreeMenu:wrap-width:
273    *
274    * If wrap-width is set to a positive value, the list will be
275    * displayed in multiple columns, the number of columns is
276    * determined by wrap-width.
277    *
278    * Since: 3.0
279    */
280   g_object_class_install_property (object_class,
281                                    PROP_WRAP_WIDTH,
282                                    g_param_spec_int ("wrap-width",
283                                                      P_("Wrap Width"),
284                                                      P_("Wrap width for laying out items in a grid"),
285                                                      0,
286                                                      G_MAXINT,
287                                                      0,
288                                                      GTK_PARAM_READWRITE));
289
290   /**
291    * GtkTreeMenu:row-span-column:
292    *
293    * If this is set to a non-negative value, it must be the index of a column 
294    * of type %G_TYPE_INT in the model. 
295    *
296    * The values of that column are used to determine how many rows a value in 
297    * the list will span. Therefore, the values in the model column pointed to 
298    * by this property must be greater than zero and not larger than wrap-width.
299    *
300    * Since: 3.0
301    */
302   g_object_class_install_property (object_class,
303                                    PROP_ROW_SPAN_COL,
304                                    g_param_spec_int ("row-span-column",
305                                                      P_("Row span column"),
306                                                      P_("TreeModel column containing the row span values"),
307                                                      -1,
308                                                      G_MAXINT,
309                                                      -1,
310                                                      GTK_PARAM_READWRITE));
311
312   /**
313    * GtkTreeMenu:column-span-column:
314    *
315    * If this is set to a non-negative value, it must be the index of a column 
316    * of type %G_TYPE_INT in the model. 
317    *
318    * The values of that column are used to determine how many columns a value 
319    * in the list will span. 
320    *
321    * Since: 3.0
322    */
323   g_object_class_install_property (object_class,
324                                    PROP_COL_SPAN_COL,
325                                    g_param_spec_int ("column-span-column",
326                                                      P_("Column span column"),
327                                                      P_("TreeModel column containing the column span values"),
328                                                      -1,
329                                                      G_MAXINT,
330                                                      -1,
331                                                      GTK_PARAM_READWRITE));
332
333   g_type_class_add_private (object_class, sizeof (GtkTreeMenuPrivate));
334 }
335
336 /****************************************************************
337  *                         GObjectClass                         *
338  ****************************************************************/
339 static GObject  *
340 gtk_tree_menu_constructor (GType                  type,
341                            guint                  n_construct_properties,
342                            GObjectConstructParam *construct_properties)
343 {
344   GObject            *object;
345   GtkTreeMenu        *menu;
346   GtkTreeMenuPrivate *priv;
347
348   object = G_OBJECT_CLASS (gtk_tree_menu_parent_class)->constructor
349     (type, n_construct_properties, construct_properties);
350
351   menu = GTK_TREE_MENU (object);
352   priv = menu->priv;
353
354   if (!priv->area)
355     {
356       GtkCellArea *area = gtk_cell_area_box_new ();
357
358       gtk_tree_menu_set_area (menu, area);
359     }
360
361   priv->context = gtk_cell_area_create_context (priv->area);
362   priv->size_changed_id = 
363     g_signal_connect (priv->context, "notify",
364                       G_CALLBACK (context_size_changed_cb), menu);
365
366
367   return object;
368 }
369
370 static void
371 gtk_tree_menu_dispose (GObject *object)
372 {
373   GtkTreeMenu        *menu;
374   GtkTreeMenuPrivate *priv;
375
376   menu = GTK_TREE_MENU (object);
377   priv = menu->priv;
378
379   gtk_tree_menu_set_model (menu, NULL);
380   gtk_tree_menu_set_area (menu, NULL);
381
382   if (priv->context)
383     {
384       /* Disconnect signals */
385       g_signal_handler_disconnect (priv->context, priv->size_changed_id);
386
387       g_object_unref (priv->context);
388       priv->context = NULL;
389       priv->size_changed_id = 0;
390     }
391
392   G_OBJECT_CLASS (gtk_tree_menu_parent_class)->dispose (object);
393 }
394
395 static void
396 gtk_tree_menu_finalize (GObject *object)
397 {
398   GtkTreeMenu        *menu;
399   GtkTreeMenuPrivate *priv;
400
401   menu = GTK_TREE_MENU (object);
402   priv = menu->priv;
403
404   gtk_tree_menu_set_row_separator_func (menu, NULL, NULL, NULL);
405   gtk_tree_menu_set_header_func (menu, NULL, NULL, NULL);
406
407   if (priv->root) 
408     gtk_tree_row_reference_free (priv->root);
409
410   G_OBJECT_CLASS (gtk_tree_menu_parent_class)->finalize (object);
411 }
412
413 static void
414 gtk_tree_menu_set_property (GObject            *object,
415                             guint               prop_id,
416                             const GValue       *value,
417                             GParamSpec         *pspec)
418 {
419   GtkTreeMenu *menu = GTK_TREE_MENU (object);
420
421   switch (prop_id)
422     {
423     case PROP_MODEL:
424       gtk_tree_menu_set_model (menu, g_value_get_object (value));
425       break;
426
427     case PROP_ROOT:
428       gtk_tree_menu_set_root (menu, g_value_get_boxed (value));
429       break;
430
431     case PROP_CELL_AREA:
432       /* Construct-only, can only be assigned once */
433       gtk_tree_menu_set_area (menu, (GtkCellArea *)g_value_get_object (value));
434       break;
435
436     case PROP_TEAROFF:
437       gtk_tree_menu_set_tearoff (menu, g_value_get_boolean (value));
438       break;
439
440     case PROP_WRAP_WIDTH:
441       gtk_tree_menu_set_wrap_width (menu, g_value_get_int (value));
442       break;
443
444      case PROP_ROW_SPAN_COL:
445       gtk_tree_menu_set_row_span_column (menu, g_value_get_int (value));
446       break;
447
448      case PROP_COL_SPAN_COL:
449       gtk_tree_menu_set_column_span_column (menu, g_value_get_int (value));
450       break;
451
452     default:
453       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
454       break;
455     }
456 }
457
458 static void
459 gtk_tree_menu_get_property (GObject            *object,
460                             guint               prop_id,
461                             GValue             *value,
462                             GParamSpec         *pspec)
463 {
464   GtkTreeMenu        *menu = GTK_TREE_MENU (object);
465   GtkTreeMenuPrivate *priv = menu->priv;
466
467   switch (prop_id)
468     {
469     case PROP_MODEL:
470       g_value_set_object (value, priv->model);
471       break;
472       
473     case PROP_ROOT:
474       g_value_set_boxed (value, priv->root);
475       break;
476       
477     case PROP_CELL_AREA:
478       g_value_set_object (value, priv->area);
479       break;
480
481     case PROP_TEAROFF:
482       g_value_set_boolean (value, priv->tearoff);
483       break;
484
485     default:
486       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
487       break;
488     }
489 }
490
491 /****************************************************************
492  *                         GtkWidgetClass                       *
493  ****************************************************************/
494
495 /* We tell all the menu items to reserve space for the submenu
496  * indicator if there is at least one submenu, this way we ensure
497  * that every internal cell area gets allocated the
498  * same width (and requested height for the same appropriate width).
499  */
500 static void
501 sync_reserve_submenu_size (GtkTreeMenu *menu)
502 {
503   GList              *children, *l;
504   gboolean            has_submenu = FALSE;
505
506   children = gtk_container_get_children (GTK_CONTAINER (menu));
507   for (l = children; l; l = l->next)
508     {
509       GtkMenuItem *item = l->data;
510
511       if (gtk_menu_item_get_submenu (item) != NULL)
512         {
513           has_submenu = TRUE;
514           break;
515         }
516     }
517
518   for (l = children; l; l = l->next)
519     {
520       GtkMenuItem *item = l->data;
521
522       gtk_menu_item_set_reserve_indicator (item, has_submenu);
523     }
524
525   g_list_free (children);
526 }
527
528 static void
529 gtk_tree_menu_get_preferred_width (GtkWidget           *widget,
530                                    gint                *minimum_size,
531                                    gint                *natural_size)
532 {
533   GtkTreeMenu        *menu = GTK_TREE_MENU (widget);
534   GtkTreeMenuPrivate *priv = menu->priv;
535
536   /* We leave the requesting work up to the cellviews which operate in the same
537    * context, reserving space for the submenu indicator if any of the items have
538    * submenus ensures that every cellview will receive the same allocated width.
539    *
540    * Since GtkMenu does hieght-for-width correctly, we know that the width of
541    * every cell will be requested before the height-for-widths are requested.
542    */
543   g_signal_handler_block (priv->context, priv->size_changed_id);
544
545   sync_reserve_submenu_size (menu);
546
547   GTK_WIDGET_CLASS (gtk_tree_menu_parent_class)->get_preferred_width (widget, minimum_size, natural_size);
548
549   g_signal_handler_unblock (priv->context, priv->size_changed_id);
550 }
551
552 static void
553 gtk_tree_menu_get_preferred_height (GtkWidget           *widget,
554                                     gint                *minimum_size,
555                                     gint                *natural_size)
556 {
557   GtkTreeMenu        *menu = GTK_TREE_MENU (widget);
558   GtkTreeMenuPrivate *priv = menu->priv;
559
560   g_signal_handler_block (priv->context, priv->size_changed_id);
561
562   sync_reserve_submenu_size (menu);
563
564   GTK_WIDGET_CLASS (gtk_tree_menu_parent_class)->get_preferred_height (widget, minimum_size, natural_size);
565
566   g_signal_handler_unblock (priv->context, priv->size_changed_id);
567 }
568
569 /****************************************************************
570  *                      GtkCellLayoutIface                      *
571  ****************************************************************/
572 static void
573 gtk_tree_menu_cell_layout_init (GtkCellLayoutIface  *iface)
574 {
575   iface->get_area = gtk_tree_menu_cell_layout_get_area;
576 }
577
578 static GtkCellArea *
579 gtk_tree_menu_cell_layout_get_area (GtkCellLayout *layout)
580 {
581   GtkTreeMenu        *menu = GTK_TREE_MENU (layout);
582   GtkTreeMenuPrivate *priv = menu->priv;
583
584   return priv->area;
585 }
586
587
588 /****************************************************************
589  *             TreeModel callbacks/populating menus             *
590  ****************************************************************/
591 static GtkWidget *
592 gtk_tree_menu_get_path_item (GtkTreeMenu          *menu,
593                              GtkTreePath          *search)
594 {
595   GtkWidget *item = NULL;
596   GList     *children, *l;
597
598   children = gtk_container_get_children (GTK_CONTAINER (menu));
599
600   for (l = children; item == NULL && l != NULL; l = l->next)
601     {
602       GtkWidget   *child = l->data;
603       GtkTreePath *path  = NULL;
604
605       if (GTK_IS_SEPARATOR_MENU_ITEM (child))
606         {
607           GtkTreeRowReference *row =
608             g_object_get_qdata (G_OBJECT (child), tree_menu_path_quark);
609
610           if (row && gtk_tree_row_reference_valid (row))
611             path = gtk_tree_row_reference_get_path (row);
612         }
613       else
614         {
615           GtkWidget *view = gtk_bin_get_child (GTK_BIN (child));
616
617           /* It's always a cellview */
618           if (GTK_IS_CELL_VIEW (view))
619             path = gtk_cell_view_get_displayed_row (GTK_CELL_VIEW (view));
620         }
621
622       if (path)
623         {
624           if (gtk_tree_path_compare (search, path) == 0)
625             item = child;
626
627           gtk_tree_path_free (path);
628         }
629     }
630
631   g_list_free (children);
632
633   return item;
634 }
635
636 static void
637 row_inserted_cb (GtkTreeModel     *model,
638                  GtkTreePath      *path,
639                  GtkTreeIter      *iter,
640                  GtkTreeMenu      *menu)
641 {
642   GtkTreeMenuPrivate *priv = menu->priv;
643   GtkTreePath        *parent_path;
644   gboolean            this_menu = FALSE;
645   gint               *indices, index, depth;
646
647   parent_path = gtk_tree_path_copy (path);
648
649   /* Check if the menu and the added iter are in root of the model */
650   if (gtk_tree_path_get_depth (parent_path) <= 1)
651     {
652       if (!priv->root)
653         this_menu = TRUE;
654     }
655   /* If we are a submenu, compare the path */
656   else if (priv->root)
657     {
658       GtkTreePath *root_path =
659         gtk_tree_row_reference_get_path (priv->root);
660
661       if (gtk_tree_path_compare (root_path, parent_path) == 0)
662         this_menu = TRUE;
663
664       gtk_tree_path_free (root_path);
665     }
666
667   gtk_tree_path_free (parent_path);
668
669   /* If the iter should be in this menu then go ahead and insert it */
670   if (this_menu)
671     {
672       GtkWidget *item;
673
674       if (priv->wrap_width > 0)
675         rebuild_menu (menu);
676       else
677         {
678           /* Get the index of the path for this depth */
679           indices = gtk_tree_path_get_indices (path);
680           depth   = gtk_tree_path_get_depth (path);
681           index   = indices[depth -1];
682           
683           /* Menus with a header include a menuitem for it's root node
684            * and a separator menu item */
685           if (priv->menu_with_header)
686             index += 2;
687           
688           /* Index after the tearoff item for the root menu if
689            * there is a tearoff item
690            */
691           if (priv->root == NULL && priv->tearoff)
692             index += 1;
693           
694           item = gtk_tree_menu_create_item (menu, iter, FALSE);
695           gtk_menu_shell_insert (GTK_MENU_SHELL (menu), item, index);
696           
697           /* Resize everything */
698           gtk_cell_area_context_flush (menu->priv->context);
699         }
700     }
701 }
702
703 static void
704 row_deleted_cb (GtkTreeModel     *model,
705                 GtkTreePath      *path,
706                 GtkTreeMenu      *menu)
707 {
708   GtkTreeMenuPrivate *priv = menu->priv;
709   GtkTreePath        *root_path;
710   GtkWidget          *item;
711
712   /* If it's the root node we leave it to the parent menu to remove us
713    * from its menu */
714   if (priv->root)
715     {
716       root_path = gtk_tree_row_reference_get_path (priv->root);
717
718       if (gtk_tree_path_compare (root_path, path) == 0)
719         {
720           gtk_tree_path_free (root_path);
721           return;
722         }
723     }
724
725   item = gtk_tree_menu_get_path_item (menu, path);
726   if (item)
727     {
728       if (priv->wrap_width > 0)
729         rebuild_menu (menu);
730       else
731         {
732           /* Get rid of the deleted item */
733           gtk_widget_destroy (item);
734       
735           /* Resize everything */
736           gtk_cell_area_context_flush (menu->priv->context);
737         }
738     }
739 }
740
741 static void
742 row_reordered_cb (GtkTreeModel    *model,
743                   GtkTreePath     *path,
744                   GtkTreeIter     *iter,
745                   gint            *new_order,
746                   GtkTreeMenu     *menu)
747 {
748   GtkTreeMenuPrivate *priv = menu->priv;
749   gboolean            this_menu = FALSE;
750
751   if (iter == NULL && priv->root == NULL)
752     this_menu = TRUE;
753   else if (priv->root)
754     {
755       GtkTreePath *root_path =
756         gtk_tree_row_reference_get_path (priv->root);
757
758       if (gtk_tree_path_compare (root_path, path) == 0)
759         this_menu = TRUE;
760
761       gtk_tree_path_free (root_path);
762     }
763
764   if (this_menu)
765     rebuild_menu (menu);
766 }
767
768 static gint 
769 menu_item_position (GtkTreeMenu *menu,
770                     GtkWidget   *item)
771 {
772   GList *children, *l;
773   gint   position;
774
775   children = gtk_container_get_children (GTK_CONTAINER (menu));
776   for (position = 0, l = children; l; position++, l = l->next)
777     {
778       GtkWidget *iitem = l->data;
779
780       if (item == iitem)
781         break;
782     }
783
784   g_list_free (children);
785
786   return position;
787 }
788
789 static void
790 row_changed_cb (GtkTreeModel         *model,
791                 GtkTreePath          *path,
792                 GtkTreeIter          *iter,
793                 GtkTreeMenu          *menu)
794 {
795   GtkTreeMenuPrivate *priv = menu->priv;
796   gboolean            is_separator = FALSE;
797   gboolean            has_header = FALSE;
798   GtkWidget          *item;
799
800   item = gtk_tree_menu_get_path_item (menu, path);
801
802   if (priv->root)
803     {
804       GtkTreePath *root_path =
805         gtk_tree_row_reference_get_path (priv->root);
806       
807       if (gtk_tree_path_compare (root_path, path) == 0)
808         {
809           if (priv->header_func)
810             has_header = 
811               priv->header_func (priv->model, iter, priv->header_data);
812           
813           if (has_header && !item)
814             {
815               item = gtk_separator_menu_item_new ();
816               gtk_widget_show (item);
817               gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
818
819               item = gtk_tree_menu_create_item (menu, iter, TRUE);
820               gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
821
822               priv->menu_with_header = TRUE;
823             }
824           else if (!has_header && item)
825             {
826               /* Destroy the header item and the following separator */
827               gtk_widget_destroy (item);
828               gtk_widget_destroy (GTK_MENU_SHELL (menu)->children->data);
829
830               priv->menu_with_header = FALSE;
831             }
832         }
833       
834       gtk_tree_path_free (root_path);
835     }
836   
837   if (item)
838     {
839       if (priv->wrap_width > 0)
840         /* Ugly, we need to rebuild the menu here if
841          * the row-span/row-column values change 
842          */
843         rebuild_menu (menu);
844       else
845         {
846           if (priv->row_separator_func)
847             is_separator = 
848               priv->row_separator_func (model, iter,
849                                         priv->row_separator_data);
850
851
852           if (is_separator != GTK_IS_SEPARATOR_MENU_ITEM (item))
853             {
854               gint position = menu_item_position (menu, item);
855
856               gtk_widget_destroy (item);
857               item = gtk_tree_menu_create_item (menu, iter, FALSE);
858               gtk_menu_shell_insert (GTK_MENU_SHELL (menu), item, position);
859             }
860         }
861     }
862 }
863
864 static void
865 context_size_changed_cb (GtkCellAreaContext  *context,
866                          GParamSpec          *pspec,
867                          GtkWidget           *menu)
868 {
869   if (!strcmp (pspec->name, "minimum-width") ||
870       !strcmp (pspec->name, "natural-width") ||
871       !strcmp (pspec->name, "minimum-height") ||
872       !strcmp (pspec->name, "natural-height"))
873     gtk_widget_queue_resize (menu);
874 }
875
876 static void
877 gtk_tree_menu_set_area (GtkTreeMenu *menu,
878                         GtkCellArea *area)
879 {
880   GtkTreeMenuPrivate *priv = menu->priv;
881
882   if (priv->area)
883     g_object_unref (priv->area);
884
885   priv->area = area;
886
887   if (priv->area)
888     g_object_ref_sink (priv->area);
889 }
890
891 static gboolean
892 menu_occupied (GtkTreeMenu *menu,
893                guint        left_attach,
894                guint        right_attach,
895                guint        top_attach,
896                guint        bottom_attach)
897 {
898   GList *i;
899
900   for (i = GTK_MENU_SHELL (menu)->children; i; i = i->next)
901     {
902       guint l, r, b, t;
903
904       gtk_container_child_get (GTK_CONTAINER (menu), 
905                                i->data,
906                                "left-attach", &l,
907                                "right-attach", &r,
908                                "bottom-attach", &b,
909                                "top-attach", &t,
910                                NULL);
911
912       /* look if this item intersects with the given coordinates */
913       if (right_attach > l && left_attach < r && bottom_attach > t && top_attach < b)
914         return TRUE;
915     }
916
917   return FALSE;
918 }
919
920 static void
921 relayout_item (GtkTreeMenu *menu,
922                GtkWidget   *item,
923                GtkTreeIter *iter,
924                GtkWidget   *prev)
925 {
926   GtkTreeMenuPrivate *priv = menu->priv;
927   gint                current_col = 0, current_row = 0;
928   gint                rows = 1, cols = 1;
929   
930   if (priv->col_span_col == -1 &&
931       priv->row_span_col == -1 &&
932       prev)
933     {
934       gtk_container_child_get (GTK_CONTAINER (menu), prev,
935                                "right-attach", &current_col,
936                                "top-attach", &current_row,
937                                NULL);
938       if (current_col + cols > priv->wrap_width)
939         {
940           current_col = 0;
941           current_row++;
942         }
943     }
944   else
945     {
946       if (priv->col_span_col != -1)
947         gtk_tree_model_get (priv->model, iter,
948                             priv->col_span_col, &cols,
949                             -1);
950       if (priv->row_span_col != -1)
951         gtk_tree_model_get (priv->model, iter,
952                             priv->row_span_col, &rows,
953                             -1);
954
955       while (1)
956         {
957           if (current_col + cols > priv->wrap_width)
958             {
959               current_col = 0;
960               current_row++;
961             }
962           
963           if (!menu_occupied (menu, 
964                               current_col, current_col + cols,
965                               current_row, current_row + rows))
966             break;
967
968           current_col++;
969         }
970     }
971
972   /* set attach props */
973   gtk_menu_attach (GTK_MENU (menu), item,
974                    current_col, current_col + cols,
975                    current_row, current_row + rows);
976 }
977
978 static GtkWidget *
979 gtk_tree_menu_create_item (GtkTreeMenu *menu,
980                            GtkTreeIter *iter,
981                            gboolean     header_item)
982 {
983   GtkTreeMenuPrivate *priv = menu->priv;
984   GtkWidget          *item, *view;
985   GtkTreePath        *path;
986   gboolean            is_separator = FALSE;
987
988   path = gtk_tree_model_get_path (priv->model, iter);
989
990   if (priv->row_separator_func)
991     is_separator = 
992       priv->row_separator_func (priv->model, iter,
993                                 priv->row_separator_data);
994
995   if (is_separator)
996     {
997       item = gtk_separator_menu_item_new ();
998
999       g_object_set_qdata_full (G_OBJECT (item),
1000                                tree_menu_path_quark,
1001                                gtk_tree_row_reference_new (priv->model, path),
1002                                (GDestroyNotify)gtk_tree_row_reference_free);
1003     }
1004   else
1005     {
1006       view = gtk_cell_view_new_with_context (priv->area, priv->context);
1007       item = gtk_menu_item_new ();
1008       gtk_widget_show (view);
1009       gtk_widget_show (item);
1010       
1011       gtk_cell_view_set_model (GTK_CELL_VIEW (view), priv->model);
1012       gtk_cell_view_set_displayed_row (GTK_CELL_VIEW (view), path);
1013       
1014       gtk_widget_show (view);
1015       gtk_container_add (GTK_CONTAINER (item), view);
1016       
1017       g_signal_connect (item, "activate", G_CALLBACK (item_activated_cb), menu);
1018
1019       /* Add a GtkTreeMenu submenu to render the children of this row */
1020       if (header_item == FALSE &&
1021           gtk_tree_model_iter_has_child (priv->model, iter))
1022         {
1023           GtkWidget           *submenu;
1024
1025           submenu = gtk_tree_menu_new_with_area (priv->area);
1026
1027           gtk_tree_menu_set_row_separator_func (GTK_TREE_MENU (submenu), 
1028                                                 priv->row_separator_func,
1029                                                 priv->row_separator_data,
1030                                                 priv->row_separator_destroy);
1031           gtk_tree_menu_set_header_func (GTK_TREE_MENU (submenu), 
1032                                          priv->header_func,
1033                                          priv->header_data,
1034                                          priv->header_destroy);
1035
1036           gtk_tree_menu_set_model (GTK_TREE_MENU (submenu), priv->model);
1037           gtk_tree_menu_set_root (GTK_TREE_MENU (submenu), path);
1038
1039           gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), submenu);
1040
1041           g_signal_connect (submenu, "menu-activate", 
1042                             G_CALLBACK (submenu_activated_cb), menu);
1043         }
1044     }
1045
1046   gtk_tree_path_free (path);
1047
1048   return item;
1049 }
1050
1051 static inline void 
1052 rebuild_menu (GtkTreeMenu *menu)
1053 {
1054   GtkTreeMenuPrivate *priv = menu->priv;
1055
1056   /* Destroy all the menu items */
1057   gtk_container_foreach (GTK_CONTAINER (menu), 
1058                          (GtkCallback) gtk_widget_destroy, NULL);
1059   
1060   /* Populate */
1061   if (priv->model)
1062     gtk_tree_menu_populate (menu);
1063 }
1064
1065
1066 static void
1067 gtk_tree_menu_populate (GtkTreeMenu *menu)
1068 {
1069   GtkTreeMenuPrivate *priv = menu->priv;
1070   GtkTreePath        *path = NULL;
1071   GtkTreeIter         parent;
1072   GtkTreeIter         iter;
1073   gboolean            valid = FALSE;
1074   GtkWidget          *menu_item, *prev = NULL;
1075
1076   if (!priv->model)
1077     return;
1078
1079   if (priv->root)
1080     path = gtk_tree_row_reference_get_path (priv->root);
1081
1082   if (path)
1083     {
1084       if (gtk_tree_model_get_iter (priv->model, &parent, path))
1085         {
1086           valid = gtk_tree_model_iter_children (priv->model, &iter, &parent);
1087
1088           if (priv->header_func && 
1089               priv->header_func (priv->model, &parent, priv->header_data))
1090             {
1091               /* Add a submenu header for rows which desire one, used for
1092                * combo boxes to allow all rows to be activatable/selectable 
1093                */
1094               menu_item = gtk_tree_menu_create_item (menu, &parent, TRUE);
1095               gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);
1096               
1097               menu_item = gtk_separator_menu_item_new ();
1098               gtk_widget_show (menu_item);
1099               gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);
1100
1101               prev = menu_item;
1102               priv->menu_with_header = TRUE;
1103             }
1104         }
1105       gtk_tree_path_free (path);
1106     }
1107   else
1108     {
1109       /* Tearoff menu items only go in the root menu */
1110       if (priv->tearoff)
1111         {
1112           menu_item = gtk_tearoff_menu_item_new ();
1113           gtk_widget_show (menu_item);
1114
1115           if (priv->wrap_width > 0)
1116             gtk_menu_attach (GTK_MENU (menu), menu_item, 0, priv->wrap_width, 0, 1);
1117           else
1118             gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);
1119
1120           prev = menu_item;
1121         }
1122
1123       valid = gtk_tree_model_iter_children (priv->model, &iter, NULL);
1124     }
1125
1126   /* Create a menu item for every row at the current depth, add a GtkTreeMenu
1127    * submenu for iters/items that have children */
1128   while (valid)
1129     {
1130       menu_item = gtk_tree_menu_create_item (menu, &iter, FALSE);
1131
1132       gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);
1133
1134       if (priv->wrap_width > 0)
1135         relayout_item (menu, menu_item, &iter, prev);
1136
1137       prev  = menu_item;
1138       valid = gtk_tree_model_iter_next (priv->model, &iter);
1139     }
1140 }
1141
1142 static void
1143 item_activated_cb (GtkMenuItem          *item,
1144                    GtkTreeMenu          *menu)
1145 {
1146   GtkCellView *view;
1147   GtkTreePath *path;
1148   gchar       *path_str;
1149
1150   /* Only activate leafs, not parents */
1151   if (!gtk_menu_item_get_submenu (item))
1152     {
1153       view     = GTK_CELL_VIEW (gtk_bin_get_child (GTK_BIN (item)));
1154       path     = gtk_cell_view_get_displayed_row (view);
1155       path_str = gtk_tree_path_to_string (path);
1156       
1157       g_signal_emit (menu, tree_menu_signals[SIGNAL_MENU_ACTIVATE], 0, path_str);
1158       
1159       g_free (path_str);
1160       gtk_tree_path_free (path);
1161     }
1162 }
1163
1164 static void
1165 submenu_activated_cb (GtkTreeMenu          *submenu,
1166                       const gchar          *path,
1167                       GtkTreeMenu          *menu)
1168 {
1169   g_signal_emit (menu, tree_menu_signals[SIGNAL_MENU_ACTIVATE], 0, path);
1170 }
1171
1172 /****************************************************************
1173  *                            API                               *
1174  ****************************************************************/
1175 GtkWidget *
1176 gtk_tree_menu_new (void)
1177 {
1178   return (GtkWidget *)g_object_new (GTK_TYPE_TREE_MENU, NULL);
1179 }
1180
1181 GtkWidget *
1182 gtk_tree_menu_new_with_area (GtkCellArea    *area)
1183 {
1184   return (GtkWidget *)g_object_new (GTK_TYPE_TREE_MENU, 
1185                                     "cell-area", area, 
1186                                     NULL);
1187 }
1188
1189 GtkWidget *
1190 gtk_tree_menu_new_full (GtkCellArea         *area,
1191                         GtkTreeModel        *model,
1192                         GtkTreePath         *root)
1193 {
1194   return (GtkWidget *)g_object_new (GTK_TYPE_TREE_MENU, 
1195                                     "cell-area", area, 
1196                                     "model", model,
1197                                     "root", root,
1198                                     NULL);
1199 }
1200
1201 void
1202 gtk_tree_menu_set_model (GtkTreeMenu  *menu,
1203                          GtkTreeModel *model)
1204 {
1205   GtkTreeMenuPrivate *priv;
1206
1207   g_return_if_fail (GTK_IS_TREE_MENU (menu));
1208   g_return_if_fail (model == NULL || GTK_IS_TREE_MODEL (model));
1209
1210   priv = menu->priv;
1211
1212   if (priv->model != model)
1213     {
1214       if (priv->model)
1215         {
1216           /* Disconnect signals */
1217           g_signal_handler_disconnect (priv->model,
1218                                        priv->row_inserted_id);
1219           g_signal_handler_disconnect (priv->model,
1220                                        priv->row_deleted_id);
1221           g_signal_handler_disconnect (priv->model,
1222                                        priv->row_reordered_id);
1223           g_signal_handler_disconnect (priv->model,
1224                                        priv->row_changed_id);
1225           priv->row_inserted_id  = 0;
1226           priv->row_deleted_id   = 0;
1227           priv->row_reordered_id = 0;
1228           priv->row_changed_id = 0;
1229
1230           g_object_unref (priv->model);
1231         }
1232
1233       priv->model = model;
1234
1235       if (priv->model)
1236         {
1237           g_object_ref (priv->model);
1238
1239           /* Connect signals */
1240           priv->row_inserted_id  = g_signal_connect (priv->model, "row-inserted",
1241                                                      G_CALLBACK (row_inserted_cb), menu);
1242           priv->row_deleted_id   = g_signal_connect (priv->model, "row-deleted",
1243                                                      G_CALLBACK (row_deleted_cb), menu);
1244           priv->row_reordered_id = g_signal_connect (priv->model, "rows-reordered",
1245                                                      G_CALLBACK (row_reordered_cb), menu);
1246           priv->row_changed_id   = g_signal_connect (priv->model, "row-changed",
1247                                                      G_CALLBACK (row_changed_cb), menu);
1248         }
1249     }
1250 }
1251
1252 GtkTreeModel *
1253 gtk_tree_menu_get_model (GtkTreeMenu *menu)
1254 {
1255   GtkTreeMenuPrivate *priv;
1256
1257   g_return_val_if_fail (GTK_IS_TREE_MENU (menu), NULL);
1258
1259   priv = menu->priv;
1260
1261   return priv->model;
1262 }
1263
1264 void
1265 gtk_tree_menu_set_root (GtkTreeMenu         *menu,
1266                         GtkTreePath         *path)
1267 {
1268   GtkTreeMenuPrivate *priv;
1269
1270   g_return_if_fail (GTK_IS_TREE_MENU (menu));
1271   g_return_if_fail (menu->priv->model != NULL || path == NULL);
1272
1273   priv = menu->priv;
1274
1275   if (priv->root) 
1276     gtk_tree_row_reference_free (priv->root);
1277
1278   if (path)
1279     priv->root = gtk_tree_row_reference_new (priv->model, path);
1280   else
1281     priv->root = NULL;
1282
1283   rebuild_menu (menu);
1284 }
1285
1286 GtkTreePath *
1287 gtk_tree_menu_get_root (GtkTreeMenu *menu)
1288 {
1289   GtkTreeMenuPrivate *priv;
1290
1291   g_return_val_if_fail (GTK_IS_TREE_MENU (menu), NULL);
1292
1293   priv = menu->priv;
1294
1295   if (priv->root)
1296     return gtk_tree_row_reference_get_path (priv->root);
1297
1298   return NULL;
1299 }
1300
1301 gboolean
1302 gtk_tree_menu_get_tearoff (GtkTreeMenu *menu)
1303 {
1304   GtkTreeMenuPrivate *priv;
1305
1306   g_return_val_if_fail (GTK_IS_TREE_MENU (menu), FALSE);
1307
1308   priv = menu->priv;
1309
1310   return priv->tearoff;
1311 }
1312
1313 void
1314 gtk_tree_menu_set_tearoff (GtkTreeMenu *menu,
1315                            gboolean     tearoff)
1316 {
1317   GtkTreeMenuPrivate *priv;
1318
1319   g_return_if_fail (GTK_IS_TREE_MENU (menu));
1320
1321   priv = menu->priv;
1322
1323   if (priv->tearoff != tearoff)
1324     {
1325       priv->tearoff = tearoff;
1326
1327       rebuild_menu (menu);
1328
1329       g_object_notify (G_OBJECT (menu), "tearoff");
1330     }
1331 }
1332
1333 gint
1334 gtk_tree_menu_get_wrap_width (GtkTreeMenu *menu)
1335 {
1336   GtkTreeMenuPrivate *priv;
1337
1338   g_return_val_if_fail (GTK_IS_TREE_MENU (menu), FALSE);
1339
1340   priv = menu->priv;
1341
1342   return priv->wrap_width;
1343 }
1344
1345 void
1346 gtk_tree_menu_set_wrap_width (GtkTreeMenu *menu,
1347                               gint         width)
1348 {
1349   GtkTreeMenuPrivate *priv;
1350
1351   g_return_if_fail (GTK_IS_TREE_MENU (menu));
1352   g_return_if_fail (width >= 0);
1353
1354   priv = menu->priv;
1355
1356   if (priv->wrap_width != width)
1357     {
1358       priv->wrap_width = width;
1359
1360       rebuild_menu (menu);
1361
1362       g_object_notify (G_OBJECT (menu), "wrap-width");
1363     }
1364 }
1365
1366 gint
1367 gtk_tree_menu_get_row_span_column (GtkTreeMenu *menu)
1368 {
1369   GtkTreeMenuPrivate *priv;
1370
1371   g_return_val_if_fail (GTK_IS_TREE_MENU (menu), FALSE);
1372
1373   priv = menu->priv;
1374
1375   return priv->row_span_col;
1376 }
1377
1378 void
1379 gtk_tree_menu_set_row_span_column (GtkTreeMenu *menu,
1380                                    gint         row_span)
1381 {
1382   GtkTreeMenuPrivate *priv;
1383
1384   g_return_if_fail (GTK_IS_TREE_MENU (menu));
1385
1386   priv = menu->priv;
1387
1388   if (priv->row_span_col != row_span)
1389     {
1390       priv->row_span_col = row_span;
1391
1392       if (priv->wrap_width > 0)
1393         rebuild_menu (menu);
1394
1395       g_object_notify (G_OBJECT (menu), "row-span-column");
1396     }
1397 }
1398
1399 gint
1400 gtk_tree_menu_get_column_span_column (GtkTreeMenu *menu)
1401 {
1402   GtkTreeMenuPrivate *priv;
1403
1404   g_return_val_if_fail (GTK_IS_TREE_MENU (menu), FALSE);
1405
1406   priv = menu->priv;
1407
1408   return priv->col_span_col;
1409 }
1410
1411 void
1412 gtk_tree_menu_set_column_span_column (GtkTreeMenu *menu,
1413                                       gint         column_span)
1414 {
1415   GtkTreeMenuPrivate *priv;
1416
1417   g_return_if_fail (GTK_IS_TREE_MENU (menu));
1418
1419   priv = menu->priv;
1420
1421   if (priv->col_span_col != column_span)
1422     {
1423       priv->col_span_col = column_span;
1424
1425       if (priv->wrap_width > 0)
1426         rebuild_menu (menu);
1427
1428       g_object_notify (G_OBJECT (menu), "column-span-column");
1429     }
1430 }
1431
1432 GtkTreeViewRowSeparatorFunc
1433 gtk_tree_menu_get_row_separator_func (GtkTreeMenu *menu)
1434 {
1435   GtkTreeMenuPrivate *priv;
1436
1437   g_return_val_if_fail (GTK_IS_TREE_MENU (menu), NULL);
1438
1439   priv = menu->priv;
1440
1441   return priv->row_separator_func;
1442 }
1443
1444 void
1445 gtk_tree_menu_set_row_separator_func (GtkTreeMenu          *menu,
1446                                       GtkTreeViewRowSeparatorFunc func,
1447                                       gpointer              data,
1448                                       GDestroyNotify        destroy)
1449 {
1450   GtkTreeMenuPrivate *priv;
1451
1452   g_return_if_fail (GTK_IS_TREE_MENU (menu));
1453
1454   priv = menu->priv;
1455
1456   if (priv->row_separator_destroy)
1457     priv->row_separator_destroy (priv->row_separator_data);
1458
1459   priv->row_separator_func    = func;
1460   priv->row_separator_data    = data;
1461   priv->row_separator_destroy = destroy;
1462
1463   rebuild_menu (menu);
1464 }
1465
1466 GtkTreeMenuHeaderFunc
1467 gtk_tree_menu_get_header_func (GtkTreeMenu *menu)
1468 {
1469   GtkTreeMenuPrivate *priv;
1470
1471   g_return_val_if_fail (GTK_IS_TREE_MENU (menu), NULL);
1472
1473   priv = menu->priv;
1474
1475   return priv->header_func;
1476 }
1477
1478 void
1479 gtk_tree_menu_set_header_func (GtkTreeMenu          *menu,
1480                                GtkTreeMenuHeaderFunc func,
1481                                gpointer              data,
1482                                GDestroyNotify        destroy)
1483 {
1484   GtkTreeMenuPrivate *priv;
1485
1486   g_return_if_fail (GTK_IS_TREE_MENU (menu));
1487
1488   priv = menu->priv;
1489
1490   if (priv->header_destroy)
1491     priv->header_destroy (priv->header_data);
1492
1493   priv->header_func    = func;
1494   priv->header_data    = data;
1495   priv->header_destroy = destroy;
1496
1497   rebuild_menu (menu);
1498 }