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