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