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