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