]> Pileus Git - ~andy/gtk/blob - gtk/gtktreemenu.c
Reduced code size in GtkTreeMenu by only implementing GtkCellLayout->get_area method...
[~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 "gtkseparatormenuitem.h"
30 #include "gtkcellareabox.h"
31 #include "gtkcellareacontext.h"
32 #include "gtkcelllayout.h"
33 #include "gtkcellview.h"
34 #include "gtkprivate.h"
35
36
37 /* GObjectClass */
38 static GObject  *gtk_tree_menu_constructor                    (GType                  type,
39                                                                guint                  n_construct_properties,
40                                                                GObjectConstructParam *construct_properties);
41 static void      gtk_tree_menu_dispose                        (GObject            *object);
42 static void      gtk_tree_menu_finalize                       (GObject            *object);
43 static void      gtk_tree_menu_set_property                   (GObject            *object,
44                                                                guint               prop_id,
45                                                                const GValue       *value,
46                                                                GParamSpec         *pspec);
47 static void      gtk_tree_menu_get_property                   (GObject            *object,
48                                                                guint               prop_id,
49                                                                GValue             *value,
50                                                                GParamSpec         *pspec);
51
52 /* GtkWidgetClass */
53 static void      gtk_tree_menu_get_preferred_width            (GtkWidget           *widget,
54                                                                gint                *minimum_size,
55                                                                gint                *natural_size);
56 static void      gtk_tree_menu_get_preferred_height           (GtkWidget           *widget,
57                                                                gint                *minimum_size,
58                                                                gint                *natural_size);
59 static void      gtk_tree_menu_size_allocate                  (GtkWidget           *widget,
60                                                                GtkAllocation       *allocation);
61
62 /* GtkCellLayoutIface */
63 static void      gtk_tree_menu_cell_layout_init               (GtkCellLayoutIface  *iface);
64 static GtkCellArea *gtk_tree_menu_cell_layout_get_area        (GtkCellLayout        *layout);
65
66
67 /* TreeModel/DrawingArea callbacks and building menus/submenus */
68 static void       gtk_tree_menu_populate                      (GtkTreeMenu          *menu);
69 static GtkWidget *gtk_tree_menu_create_item                   (GtkTreeMenu          *menu,
70                                                                GtkTreeIter          *iter,
71                                                                gboolean              header_item);
72 static void       gtk_tree_menu_set_area                      (GtkTreeMenu          *menu,
73                                                                GtkCellArea          *area);
74 static GtkWidget *gtk_tree_menu_get_path_item                 (GtkTreeMenu          *menu,
75                                                                GtkTreePath          *path);
76
77 static void       context_size_changed_cb                     (GtkCellAreaContext   *context,
78                                                                GParamSpec           *pspec,
79                                                                GtkWidget            *menu);
80 static void       item_activated_cb                           (GtkMenuItem          *item,
81                                                                GtkTreeMenu          *menu);
82 static void       submenu_activated_cb                        (GtkTreeMenu          *submenu,
83                                                                const gchar          *path,
84                                                                GtkTreeMenu          *menu);
85
86 struct _GtkTreeMenuPrivate
87 {
88   /* TreeModel and parent for this menu */
89   GtkTreeModel        *model;
90   GtkTreeRowReference *root;
91
92   /* CellArea and context for this menu */
93   GtkCellArea         *area;
94   GtkCellAreaContext  *context;
95   
96   /* Signals */
97   gulong               size_changed_id;
98   gulong               row_inserted_id;
99   gulong               row_deleted_id;
100   gulong               row_reordered_id;
101
102   /* Row separators */
103   GtkTreeViewRowSeparatorFunc row_separator_func;
104   gpointer                    row_separator_data;
105   GDestroyNotify              row_separator_destroy;
106
107   /* Submenu headers */
108   GtkTreeMenuHeaderFunc header_func;
109   gpointer              header_data;
110   GDestroyNotify        header_destroy;
111
112   guint32               menu_with_header : 1;
113 };
114
115 enum {
116   PROP_0,
117   PROP_MODEL,
118   PROP_ROOT,
119   PROP_CELL_AREA
120 };
121
122 enum {
123   SIGNAL_MENU_ACTIVATE,
124   N_SIGNALS
125 };
126
127 static guint   tree_menu_signals[N_SIGNALS] = { 0 };
128 static GQuark  tree_menu_path_quark = 0;
129
130 G_DEFINE_TYPE_WITH_CODE (GtkTreeMenu, gtk_tree_menu, GTK_TYPE_MENU,
131                          G_IMPLEMENT_INTERFACE (GTK_TYPE_CELL_LAYOUT,
132                                                 gtk_tree_menu_cell_layout_init));
133
134 static void
135 gtk_tree_menu_init (GtkTreeMenu *menu)
136 {
137   GtkTreeMenuPrivate *priv;
138
139   menu->priv = G_TYPE_INSTANCE_GET_PRIVATE (menu,
140                                             GTK_TYPE_TREE_MENU,
141                                             GtkTreeMenuPrivate);
142   priv = menu->priv;
143
144   gtk_menu_set_reserve_toggle_size (GTK_MENU (menu), FALSE);
145 }
146
147 static void 
148 gtk_tree_menu_class_init (GtkTreeMenuClass *class)
149 {
150   GObjectClass   *object_class = G_OBJECT_CLASS (class);
151   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
152
153   tree_menu_path_quark = g_quark_from_static_string ("gtk-tree-menu-path");
154
155   object_class->constructor  = gtk_tree_menu_constructor;
156   object_class->dispose      = gtk_tree_menu_dispose;
157   object_class->finalize     = gtk_tree_menu_finalize;
158   object_class->set_property = gtk_tree_menu_set_property;
159   object_class->get_property = gtk_tree_menu_get_property;
160
161   widget_class->get_preferred_width            = gtk_tree_menu_get_preferred_width;
162   widget_class->get_preferred_height           = gtk_tree_menu_get_preferred_height;
163   widget_class->size_allocate                  = gtk_tree_menu_size_allocate;
164
165   tree_menu_signals[SIGNAL_MENU_ACTIVATE] =
166     g_signal_new (I_("menu-activate"),
167                   G_OBJECT_CLASS_TYPE (object_class),
168                   G_SIGNAL_RUN_FIRST,
169                   0, /* No class closure here */
170                   NULL, NULL,
171                   _gtk_marshal_VOID__STRING,
172                   G_TYPE_NONE, 1, G_TYPE_STRING);
173
174   g_object_class_install_property (object_class,
175                                    PROP_MODEL,
176                                    g_param_spec_object ("model",
177                                                         P_("TreeMenu model"),
178                                                         P_("The model for the tree menu"),
179                                                         GTK_TYPE_TREE_MODEL,
180                                                         GTK_PARAM_READWRITE));
181
182   g_object_class_install_property (object_class,
183                                    PROP_ROOT,
184                                    g_param_spec_boxed ("root",
185                                                        P_("TreeMenu root row"),
186                                                        P_("The TreeMenu will display children of the "
187                                                           "specified root"),
188                                                        GTK_TYPE_TREE_PATH,
189                                                        GTK_PARAM_READWRITE));
190
191    g_object_class_install_property (object_class,
192                                     PROP_CELL_AREA,
193                                     g_param_spec_object ("cell-area",
194                                                          P_("Cell Area"),
195                                                          P_("The GtkCellArea used to layout cells"),
196                                                          GTK_TYPE_CELL_AREA,
197                                                          GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
198
199
200    g_type_class_add_private (object_class, sizeof (GtkTreeMenuPrivate));
201 }
202
203 /****************************************************************
204  *                         GObjectClass                         *
205  ****************************************************************/
206 static GObject  *
207 gtk_tree_menu_constructor (GType                  type,
208                            guint                  n_construct_properties,
209                            GObjectConstructParam *construct_properties)
210 {
211   GObject            *object;
212   GtkTreeMenu        *menu;
213   GtkTreeMenuPrivate *priv;
214
215   object = G_OBJECT_CLASS (gtk_tree_menu_parent_class)->constructor
216     (type, n_construct_properties, construct_properties);
217
218   menu = GTK_TREE_MENU (object);
219   priv = menu->priv;
220
221   if (!priv->area)
222     {
223       GtkCellArea *area = gtk_cell_area_box_new ();
224
225       gtk_tree_menu_set_area (menu, area);
226     }
227
228   priv->context = gtk_cell_area_create_context (priv->area);
229   priv->size_changed_id = 
230     g_signal_connect (priv->context, "notify",
231                       G_CALLBACK (context_size_changed_cb), menu);
232
233
234   return object;
235 }
236
237 static void
238 gtk_tree_menu_dispose (GObject *object)
239 {
240   GtkTreeMenu        *menu;
241   GtkTreeMenuPrivate *priv;
242
243   menu = GTK_TREE_MENU (object);
244   priv = menu->priv;
245
246   gtk_tree_menu_set_model (menu, NULL);
247   gtk_tree_menu_set_area (menu, NULL);
248
249   if (priv->context)
250     {
251       /* Disconnect signals */
252       g_signal_handler_disconnect (priv->context, priv->size_changed_id);
253
254       g_object_unref (priv->context);
255       priv->context = NULL;
256       priv->size_changed_id = 0;
257     }
258
259   G_OBJECT_CLASS (gtk_tree_menu_parent_class)->dispose (object);
260 }
261
262 static void
263 gtk_tree_menu_finalize (GObject *object)
264 {
265   GtkTreeMenu        *menu;
266   GtkTreeMenuPrivate *priv;
267
268   menu = GTK_TREE_MENU (object);
269   priv = menu->priv;
270
271   gtk_tree_menu_set_row_separator_func (menu, NULL, NULL, NULL);
272   gtk_tree_menu_set_header_func (menu, NULL, NULL, NULL);
273
274   if (priv->root) 
275     gtk_tree_row_reference_free (priv->root);
276
277   G_OBJECT_CLASS (gtk_tree_menu_parent_class)->finalize (object);
278 }
279
280 static void
281 gtk_tree_menu_set_property (GObject            *object,
282                             guint               prop_id,
283                             const GValue       *value,
284                             GParamSpec         *pspec)
285 {
286   GtkTreeMenu *menu = GTK_TREE_MENU (object);
287
288   switch (prop_id)
289     {
290     case PROP_MODEL:
291       gtk_tree_menu_set_model (menu, g_value_get_object (value));
292       break;
293
294     case PROP_ROOT:
295       gtk_tree_menu_set_root (menu, g_value_get_boxed (value));
296       break;
297
298     case PROP_CELL_AREA:
299       /* Construct-only, can only be assigned once */
300       gtk_tree_menu_set_area (menu, (GtkCellArea *)g_value_get_object (value));
301       break;
302
303     default:
304       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
305       break;
306     }
307 }
308
309 static void
310 gtk_tree_menu_get_property (GObject            *object,
311                             guint               prop_id,
312                             GValue             *value,
313                             GParamSpec         *pspec)
314 {
315   GtkTreeMenu        *menu = GTK_TREE_MENU (object);
316   GtkTreeMenuPrivate *priv = menu->priv;
317
318   switch (prop_id)
319     {
320       case PROP_MODEL:
321         g_value_set_object (value, priv->model);
322         break;
323
324       case PROP_ROOT:
325         g_value_set_boxed (value, priv->root);
326         break;
327
328       case PROP_CELL_AREA:
329         g_value_set_object (value, priv->area);
330         break;
331
332       default:
333         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
334         break;
335     }
336 }
337
338 /****************************************************************
339  *                         GtkWidgetClass                       *
340  ****************************************************************/
341
342 /* We tell all the menu items to reserve space for the submenu
343  * indicator if there is at least one submenu, this way we ensure
344  * that every internal cell area gets allocated the
345  * same width (and requested height for the same appropriate width).
346  */
347 static void
348 sync_reserve_submenu_size (GtkTreeMenu *menu)
349 {
350   GList              *children, *l;
351   gboolean            has_submenu = FALSE;
352
353   children = gtk_container_get_children (GTK_CONTAINER (menu));
354   for (l = children; l; l = l->next)
355     {
356       GtkMenuItem *item = l->data;
357
358       if (gtk_menu_item_get_submenu (item) != NULL)
359         {
360           has_submenu = TRUE;
361           break;
362         }
363     }
364
365   for (l = children; l; l = l->next)
366     {
367       GtkMenuItem *item = l->data;
368
369       gtk_menu_item_set_reserve_indicator (item, has_submenu);
370     }
371
372   g_list_free (children);
373 }
374
375 static void
376 gtk_tree_menu_get_preferred_width (GtkWidget           *widget,
377                                    gint                *minimum_size,
378                                    gint                *natural_size)
379 {
380   GtkTreeMenu        *menu = GTK_TREE_MENU (widget);
381   GtkTreeMenuPrivate *priv = menu->priv;
382
383   /* We leave the requesting work up to the cellviews which operate in the same
384    * context, reserving space for the submenu indicator if any of the items have
385    * submenus ensures that every cellview will receive the same allocated width.
386    *
387    * Since GtkMenu does hieght-for-width correctly, we know that the width of
388    * every cell will be requested before the height-for-widths are requested.
389    */
390   g_signal_handler_block (priv->context, priv->size_changed_id);
391
392   sync_reserve_submenu_size (menu);
393   gtk_cell_area_context_flush_preferred_width (priv->context);
394
395   GTK_WIDGET_CLASS (gtk_tree_menu_parent_class)->get_preferred_width (widget, minimum_size, natural_size);
396
397   g_signal_handler_unblock (priv->context, priv->size_changed_id);
398 }
399
400 static void
401 gtk_tree_menu_get_preferred_height (GtkWidget           *widget,
402                                     gint                *minimum_size,
403                                     gint                *natural_size)
404 {
405   GtkTreeMenu        *menu = GTK_TREE_MENU (widget);
406   GtkTreeMenuPrivate *priv = menu->priv;
407
408   g_signal_handler_block (priv->context, priv->size_changed_id);
409
410   sync_reserve_submenu_size (menu);
411   gtk_cell_area_context_flush_preferred_height (priv->context);
412
413   GTK_WIDGET_CLASS (gtk_tree_menu_parent_class)->get_preferred_height (widget, minimum_size, natural_size);
414
415   g_signal_handler_unblock (priv->context, priv->size_changed_id);
416 }
417
418 static void
419 gtk_tree_menu_size_allocate (GtkWidget           *widget,
420                              GtkAllocation       *allocation)
421 {
422   GtkTreeMenu        *menu = GTK_TREE_MENU (widget);
423   GtkTreeMenuPrivate *priv = menu->priv;
424
425   /* flush the context allocation */
426   gtk_cell_area_context_flush_allocation (priv->context);
427
428   /* Leave it to the first cell area to allocate the size of priv->context, since
429    * we configure the menu to allocate all children the same width this works fine
430    */
431   GTK_WIDGET_CLASS (gtk_tree_menu_parent_class)->size_allocate (widget, allocation);
432 }
433
434 /****************************************************************
435  *                      GtkCellLayoutIface                      *
436  ****************************************************************/
437 static void
438 gtk_tree_menu_cell_layout_init (GtkCellLayoutIface  *iface)
439 {
440   iface->get_area = gtk_tree_menu_cell_layout_get_area;
441 }
442
443 static GtkCellArea *
444 gtk_tree_menu_cell_layout_get_area (GtkCellLayout *layout)
445 {
446   GtkTreeMenu        *menu = GTK_TREE_MENU (layout);
447   GtkTreeMenuPrivate *priv = menu->priv;
448
449   return priv->area;
450 }
451
452
453 /****************************************************************
454  *             TreeModel callbacks/populating menus             *
455  ****************************************************************/
456 static GtkWidget *
457 gtk_tree_menu_get_path_item (GtkTreeMenu          *menu,
458                              GtkTreePath          *search)
459 {
460   GtkWidget *item = NULL;
461   GList     *children, *l;
462
463   children = gtk_container_get_children (GTK_CONTAINER (menu));
464
465   for (l = children; item == NULL && l != NULL; l = l->next)
466     {
467       GtkWidget   *child = l->data;
468       GtkTreePath *path  = NULL;
469
470       if (GTK_IS_SEPARATOR_MENU_ITEM (child))
471         {
472           GtkTreeRowReference *row =
473             g_object_get_qdata (G_OBJECT (child), tree_menu_path_quark);
474
475           if (row && gtk_tree_row_reference_valid (row))
476             path = gtk_tree_row_reference_get_path (row);
477         }
478       else
479         {
480           GtkWidget *view = gtk_bin_get_child (GTK_BIN (child));
481
482           /* It's always a cellview */
483           if (GTK_IS_CELL_VIEW (view))
484             path = gtk_cell_view_get_displayed_row (GTK_CELL_VIEW (view));
485         }
486
487       if (path)
488         {
489           if (gtk_tree_path_compare (search, path) == 0)
490             item = child;
491
492           gtk_tree_path_free (path);
493         }
494     }
495
496   g_list_free (children);
497
498   return item;
499 }
500
501 static void
502 row_inserted_cb (GtkTreeModel     *model,
503                  GtkTreePath      *path,
504                  GtkTreeIter      *iter,
505                  GtkTreeMenu      *menu)
506 {
507   GtkTreeMenuPrivate *priv = menu->priv;
508   GtkTreePath        *parent_path;
509   gboolean            this_menu = FALSE;
510   gint               *indices, index, depth;
511
512   parent_path = gtk_tree_path_copy (path);
513
514   /* Check if the menu and the added iter are in root of the model */
515   if (!gtk_tree_path_up (parent_path))
516     {
517       if (!priv->root)
518         this_menu = TRUE;
519     }
520   /* If we are a submenu, compare the path */
521   else if (priv->root)
522     {
523       GtkTreePath *root_path =
524         gtk_tree_row_reference_get_path (priv->root);
525
526       if (gtk_tree_path_compare (root_path, parent_path) == 0)
527         this_menu = TRUE;
528
529       gtk_tree_path_free (root_path);
530     }
531
532   gtk_tree_path_free (parent_path);
533
534   /* If the iter should be in this menu then go ahead and insert it */
535   if (this_menu)
536     {
537       GtkWidget *item;
538
539       /* Get the index of the path for this depth */
540       indices = gtk_tree_path_get_indices (path);
541       depth   = gtk_tree_path_get_depth (path);
542       index   = indices[depth -1];
543
544       /* Menus with a header include a menuitem for it's root node
545        * and a separator menu item */
546       if (priv->menu_with_header)
547         index += 2;
548
549       item = gtk_tree_menu_create_item (menu, iter, FALSE);
550       gtk_menu_shell_insert (GTK_MENU_SHELL (menu), item, index);
551
552       /* Resize everything */
553       gtk_cell_area_context_flush (menu->priv->context);
554     }
555 }
556
557 static void
558 row_deleted_cb (GtkTreeModel     *model,
559                 GtkTreePath      *path,
560                 GtkTreeMenu      *menu)
561 {
562   GtkTreeMenuPrivate *priv = menu->priv;
563   GtkTreePath        *root_path;
564   GtkWidget          *item;
565
566   /* If it's the root node we leave it to the parent menu to remove us
567    * from its menu */
568   if (priv->root)
569     {
570       root_path = gtk_tree_row_reference_get_path (priv->root);
571
572       if (gtk_tree_path_compare (root_path, path) == 0)
573         {
574           gtk_tree_path_free (root_path);
575           return;
576         }
577     }
578
579   /* Get rid of the deleted item */
580   item = gtk_tree_menu_get_path_item (menu, path);
581   if (item)
582     {
583       gtk_widget_destroy (item);
584
585       /* Resize everything */
586       gtk_cell_area_context_flush (menu->priv->context);
587     }
588 }
589
590 static void
591 row_reordered_cb (GtkTreeModel    *model,
592                   GtkTreePath     *path,
593                   GtkTreeIter     *iter,
594                   gint            *new_order,
595                   GtkTreeMenu     *menu)
596 {
597   GtkTreeMenuPrivate *priv = menu->priv;
598   gboolean            this_menu = FALSE;
599
600   if (iter == NULL && priv->root == NULL)
601     this_menu = TRUE;
602   else if (priv->root)
603     {
604       GtkTreePath *root_path =
605         gtk_tree_row_reference_get_path (priv->root);
606
607       if (gtk_tree_path_compare (root_path, path) == 0)
608         this_menu = TRUE;
609
610       gtk_tree_path_free (root_path);
611     }
612
613   if (this_menu)
614     {
615       /* Destroy and repopulate the menu at the level where the order changed */
616       gtk_container_foreach (GTK_CONTAINER (menu), 
617                              (GtkCallback) gtk_widget_destroy, NULL);
618
619       gtk_tree_menu_populate (menu);
620
621       /* Resize everything */
622       gtk_cell_area_context_flush (menu->priv->context);
623     }
624 }
625
626 static void
627 context_size_changed_cb (GtkCellAreaContext  *context,
628                          GParamSpec          *pspec,
629                          GtkWidget           *menu)
630 {
631   if (!strcmp (pspec->name, "minimum-width") ||
632       !strcmp (pspec->name, "natural-width") ||
633       !strcmp (pspec->name, "minimum-height") ||
634       !strcmp (pspec->name, "natural-height"))
635     gtk_widget_queue_resize (menu);
636 }
637
638 static void
639 gtk_tree_menu_set_area (GtkTreeMenu *menu,
640                         GtkCellArea *area)
641 {
642   GtkTreeMenuPrivate *priv = menu->priv;
643
644   if (priv->area)
645     g_object_unref (priv->area);
646
647   priv->area = area;
648
649   if (priv->area)
650     g_object_ref_sink (priv->area);
651 }
652
653 static GtkWidget *
654 gtk_tree_menu_create_item (GtkTreeMenu *menu,
655                            GtkTreeIter *iter,
656                            gboolean     header_item)
657 {
658   GtkTreeMenuPrivate *priv = menu->priv;
659   GtkWidget          *item, *view;
660   GtkTreePath        *path;
661   gboolean            is_separator = FALSE;
662
663   path = gtk_tree_model_get_path (priv->model, iter);
664
665   if (priv->row_separator_func)
666     is_separator = 
667       priv->row_separator_func (priv->model, iter,
668                                 priv->row_separator_data);
669
670   if (is_separator)
671     {
672       item = gtk_separator_menu_item_new ();
673
674       g_object_set_qdata_full (G_OBJECT (item),
675                                tree_menu_path_quark,
676                                gtk_tree_row_reference_new (priv->model, path),
677                                (GDestroyNotify)gtk_tree_row_reference_free);
678     }
679   else
680     {
681       view = gtk_cell_view_new_with_context (priv->area, priv->context);
682       item = gtk_menu_item_new ();
683       gtk_widget_show (view);
684       gtk_widget_show (item);
685       
686       gtk_cell_view_set_model (GTK_CELL_VIEW (view), priv->model);
687       gtk_cell_view_set_displayed_row (GTK_CELL_VIEW (view), path);
688       
689       gtk_widget_show (view);
690       gtk_container_add (GTK_CONTAINER (item), view);
691       
692       g_signal_connect (item, "activate", G_CALLBACK (item_activated_cb), menu);
693
694       /* Add a GtkTreeMenu submenu to render the children of this row */
695       if (header_item == FALSE &&
696           gtk_tree_model_iter_has_child (priv->model, iter))
697         {
698           GtkWidget           *submenu;
699
700           submenu = gtk_tree_menu_new_with_area (priv->area);
701
702           gtk_tree_menu_set_row_separator_func (GTK_TREE_MENU (submenu), 
703                                                 priv->row_separator_func,
704                                                 priv->row_separator_data,
705                                                 priv->row_separator_destroy);
706           gtk_tree_menu_set_header_func (GTK_TREE_MENU (submenu), 
707                                          priv->header_func,
708                                          priv->header_data,
709                                          priv->header_destroy);
710
711           gtk_tree_menu_set_model (GTK_TREE_MENU (submenu), priv->model);
712           gtk_tree_menu_set_root (GTK_TREE_MENU (submenu), path);
713
714           gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), submenu);
715
716           g_signal_connect (submenu, "menu-activate", 
717                             G_CALLBACK (submenu_activated_cb), menu);
718         }
719     }
720
721   gtk_tree_path_free (path);
722
723   return item;
724 }
725
726 static void
727 gtk_tree_menu_populate (GtkTreeMenu *menu)
728 {
729   GtkTreeMenuPrivate *priv = menu->priv;
730   GtkTreePath        *path = NULL;
731   GtkTreeIter         parent;
732   GtkTreeIter         iter;
733   gboolean            valid = FALSE;
734   GtkWidget          *menu_item;
735
736   if (!priv->model)
737     return;
738
739   if (priv->root)
740     path = gtk_tree_row_reference_get_path (priv->root);
741
742   if (path)
743     {
744       if (gtk_tree_model_get_iter (priv->model, &parent, path))
745         {
746           valid = gtk_tree_model_iter_children (priv->model, &iter, &parent);
747
748           if (priv->header_func && 
749               priv->header_func (priv->model, &parent, priv->header_data))
750             {
751               /* Add a submenu header for rows which desire one, used for
752                * combo boxes to allow all rows to be activatable/selectable 
753                */
754               menu_item = gtk_tree_menu_create_item (menu, &parent, TRUE);
755               gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);
756               
757               menu_item = gtk_separator_menu_item_new ();
758               gtk_widget_show (menu_item);
759               gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);
760
761               priv->menu_with_header = TRUE;
762             }
763         }
764       gtk_tree_path_free (path);
765     }
766   else
767     valid = gtk_tree_model_iter_children (priv->model, &iter, NULL);
768
769   /* Create a menu item for every row at the current depth, add a GtkTreeMenu
770    * submenu for iters/items that have children */
771   while (valid)
772     {
773       menu_item = gtk_tree_menu_create_item (menu, &iter, FALSE);
774       gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);
775
776       valid = gtk_tree_model_iter_next (priv->model, &iter);
777     }
778 }
779
780 static void
781 item_activated_cb (GtkMenuItem          *item,
782                    GtkTreeMenu          *menu)
783 {
784   GtkCellView *view;
785   GtkTreePath *path;
786   gchar       *path_str;
787
788   /* Only activate leafs, not parents */
789   if (!gtk_menu_item_get_submenu (item))
790     {
791       view     = GTK_CELL_VIEW (gtk_bin_get_child (GTK_BIN (item)));
792       path     = gtk_cell_view_get_displayed_row (view);
793       path_str = gtk_tree_path_to_string (path);
794       
795       g_signal_emit (menu, tree_menu_signals[SIGNAL_MENU_ACTIVATE], 0, path_str);
796       
797       g_free (path_str);
798       gtk_tree_path_free (path);
799     }
800 }
801
802 static void
803 submenu_activated_cb (GtkTreeMenu          *submenu,
804                       const gchar          *path,
805                       GtkTreeMenu          *menu)
806 {
807   g_signal_emit (menu, tree_menu_signals[SIGNAL_MENU_ACTIVATE], 0, path);
808 }
809
810 /****************************************************************
811  *                            API                               *
812  ****************************************************************/
813 GtkWidget *
814 gtk_tree_menu_new (void)
815 {
816   return (GtkWidget *)g_object_new (GTK_TYPE_TREE_MENU, NULL);
817 }
818
819 GtkWidget *
820 gtk_tree_menu_new_with_area (GtkCellArea    *area)
821 {
822   return (GtkWidget *)g_object_new (GTK_TYPE_TREE_MENU, 
823                                     "cell-area", area, 
824                                     NULL);
825 }
826
827 GtkWidget *
828 gtk_tree_menu_new_full (GtkCellArea         *area,
829                         GtkTreeModel        *model,
830                         GtkTreePath         *root)
831 {
832   return (GtkWidget *)g_object_new (GTK_TYPE_TREE_MENU, 
833                                     "cell-area", area, 
834                                     "model", model,
835                                     "root", root,
836                                     NULL);
837 }
838
839 void
840 gtk_tree_menu_set_model (GtkTreeMenu  *menu,
841                          GtkTreeModel *model)
842 {
843   GtkTreeMenuPrivate *priv;
844
845   g_return_if_fail (GTK_IS_TREE_MENU (menu));
846   g_return_if_fail (model == NULL || GTK_IS_TREE_MODEL (model));
847
848   priv = menu->priv;
849
850   if (priv->model != model)
851     {
852       if (priv->model)
853         {
854           /* Disconnect signals */
855           g_signal_handler_disconnect (priv->model,
856                                        priv->row_inserted_id);
857           g_signal_handler_disconnect (priv->model,
858                                        priv->row_deleted_id);
859           g_signal_handler_disconnect (priv->model,
860                                        priv->row_reordered_id);
861           priv->row_inserted_id  = 0;
862           priv->row_deleted_id   = 0;
863           priv->row_reordered_id = 0;
864
865           g_object_unref (priv->model);
866         }
867
868       priv->model = model;
869
870       if (priv->model)
871         {
872           g_object_ref (priv->model);
873
874           /* Connect signals */
875           priv->row_inserted_id  = g_signal_connect (priv->model, "row-inserted",
876                                                      G_CALLBACK (row_inserted_cb), menu);
877           priv->row_deleted_id   = g_signal_connect (priv->model, "row-deleted",
878                                                      G_CALLBACK (row_deleted_cb), menu);
879           priv->row_reordered_id = g_signal_connect (priv->model, "rows-reordered",
880                                                      G_CALLBACK (row_reordered_cb), menu);
881         }
882     }
883 }
884
885 GtkTreeModel *
886 gtk_tree_menu_get_model (GtkTreeMenu *menu)
887 {
888   GtkTreeMenuPrivate *priv;
889
890   g_return_val_if_fail (GTK_IS_TREE_MENU (menu), NULL);
891
892   priv = menu->priv;
893
894   return priv->model;
895 }
896
897 void
898 gtk_tree_menu_set_root (GtkTreeMenu         *menu,
899                         GtkTreePath         *path)
900 {
901   GtkTreeMenuPrivate *priv;
902
903   g_return_if_fail (GTK_IS_TREE_MENU (menu));
904   g_return_if_fail (menu->priv->model != NULL || path == NULL);
905
906   priv = menu->priv;
907
908   if (priv->root) 
909     gtk_tree_row_reference_free (priv->root);
910
911   if (path)
912     priv->root = gtk_tree_row_reference_new (priv->model, path);
913   else
914     priv->root = NULL;
915
916   /* Destroy all the menu items for the previous root */
917   gtk_container_foreach (GTK_CONTAINER (menu), 
918                          (GtkCallback) gtk_widget_destroy, NULL);
919   
920   /* Populate for the new root */
921   if (priv->model)
922     gtk_tree_menu_populate (menu);
923 }
924
925 GtkTreePath *
926 gtk_tree_menu_get_root (GtkTreeMenu *menu)
927 {
928   GtkTreeMenuPrivate *priv;
929
930   g_return_val_if_fail (GTK_IS_TREE_MENU (menu), NULL);
931
932   priv = menu->priv;
933
934   if (priv->root)
935     return gtk_tree_row_reference_get_path (priv->root);
936
937   return NULL;
938 }
939
940 void
941 gtk_tree_menu_set_row_separator_func (GtkTreeMenu          *menu,
942                                       GtkTreeViewRowSeparatorFunc func,
943                                       gpointer              data,
944                                       GDestroyNotify        destroy)
945 {
946   GtkTreeMenuPrivate *priv;
947
948   g_return_if_fail (GTK_IS_TREE_MENU (menu));
949
950   priv = menu->priv;
951
952   if (priv->row_separator_destroy)
953     priv->row_separator_destroy (priv->row_separator_data);
954
955   priv->row_separator_func    = func;
956   priv->row_separator_data    = data;
957   priv->row_separator_destroy = destroy;
958
959   /* Destroy all the menu items */
960   gtk_container_foreach (GTK_CONTAINER (menu), 
961                          (GtkCallback) gtk_widget_destroy, NULL);
962   
963   /* Populate again */
964   if (priv->model)
965     gtk_tree_menu_populate (menu);
966 }
967
968 GtkTreeViewRowSeparatorFunc
969 gtk_tree_menu_get_row_separator_func (GtkTreeMenu *menu)
970 {
971   GtkTreeMenuPrivate *priv;
972
973   g_return_val_if_fail (GTK_IS_TREE_MENU (menu), NULL);
974
975   priv = menu->priv;
976
977   return priv->row_separator_func;
978 }
979
980 void
981 gtk_tree_menu_set_header_func (GtkTreeMenu          *menu,
982                                GtkTreeMenuHeaderFunc func,
983                                gpointer              data,
984                                GDestroyNotify        destroy)
985 {
986   GtkTreeMenuPrivate *priv;
987
988   g_return_if_fail (GTK_IS_TREE_MENU (menu));
989
990   priv = menu->priv;
991
992   if (priv->header_destroy)
993     priv->header_destroy (priv->header_data);
994
995   priv->header_func    = func;
996   priv->header_data    = data;
997   priv->header_destroy = destroy;
998
999   /* Destroy all the menu items */
1000   gtk_container_foreach (GTK_CONTAINER (menu), 
1001                          (GtkCallback) gtk_widget_destroy, NULL);
1002   
1003   /* Populate again */
1004   if (priv->model)
1005     gtk_tree_menu_populate (menu);
1006 }
1007
1008 GtkTreeMenuHeaderFunc
1009 gtk_tree_menu_get_header_func (GtkTreeMenu *menu)
1010 {
1011   GtkTreeMenuPrivate *priv;
1012
1013   g_return_val_if_fail (GTK_IS_TREE_MENU (menu), NULL);
1014
1015   priv = menu->priv;
1016
1017   return priv->header_func;
1018 }