gtktreemenu.c 60.7 KB
Newer Older
1 2 3 4 5 6 7
/* gtktreemenu.c
 *
 * Copyright (C) 2010 Openismus GmbH
 *
 * Authors:
 *      Tristan Van Berkom <tristanvb@openismus.com>
 *
8 9
 * Based on some GtkComboBox menu code by Kristian Rietveld <kris@gtk.org>
 *
10 11 12 13 14 15 16 17 18 19 20
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
Javier Jardón's avatar
Javier Jardón committed
21
 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
22 23
 */

24
/*
25 26 27 28
 * SECTION:gtktreemenu
 * @Short_Description: A GtkMenu automatically created from a #GtkTreeModel
 * @Title: GtkTreeMenu
 *
29
 * The #GtkTreeMenu is used to display a drop-down menu allowing selection
30 31 32 33
 * of every row in the model and is used by the #GtkComboBox for its drop-down
 * menu.
 */

34 35 36
#include "config.h"
#include "gtkintl.h"
#include "gtktreemenu.h"
37
#include "gtkmarshalers.h"
38 39 40 41 42 43
#include "gtkmenuitem.h"
#include "gtkseparatormenuitem.h"
#include "gtkcellareabox.h"
#include "gtkcellareacontext.h"
#include "gtkcelllayout.h"
#include "gtkcellview.h"
44
#include "gtkmenushellprivate.h"
45 46
#include "gtkprivate.h"

47 48 49 50 51
#undef GDK_DEPRECATED
#undef GDK_DEPRECATED_FOR
#define GDK_DEPRECATED
#define GDK_DEPRECATED_FOR(f)

52
#include "deprecated/gtktearoffmenuitem.h"
53 54

/* GObjectClass */
55
static void      gtk_tree_menu_constructed                    (GObject            *object);
56 57 58
static void      gtk_tree_menu_dispose                        (GObject            *object);
static void      gtk_tree_menu_finalize                       (GObject            *object);
static void      gtk_tree_menu_set_property                   (GObject            *object,
59 60 61
                                                               guint               prop_id,
                                                               const GValue       *value,
                                                               GParamSpec         *pspec);
62
static void      gtk_tree_menu_get_property                   (GObject            *object,
63 64 65
                                                               guint               prop_id,
                                                               GValue             *value,
                                                               GParamSpec         *pspec);
66 67 68

/* GtkWidgetClass */
static void      gtk_tree_menu_get_preferred_width            (GtkWidget           *widget,
69 70
                                                               gint                *minimum_size,
                                                               gint                *natural_size);
71
static void      gtk_tree_menu_get_preferred_height           (GtkWidget           *widget,
72 73
                                                               gint                *minimum_size,
                                                               gint                *natural_size);
74 75 76 77 78 79 80 81
static void      gtk_tree_menu_get_preferred_width_for_height (GtkWidget           *widget,
                                                               gint                 for_height,
                                                               gint                *minimum_size,
                                                               gint                *natural_size);
static void      gtk_tree_menu_get_preferred_height_for_width (GtkWidget           *widget,
                                                               gint                 for_width,
                                                               gint                *minimum_size,
                                                               gint                *natural_size);
82 83 84 85 86 87 88

/* GtkCellLayoutIface */
static void      gtk_tree_menu_cell_layout_init               (GtkCellLayoutIface  *iface);
static GtkCellArea *gtk_tree_menu_cell_layout_get_area        (GtkCellLayout        *layout);


/* TreeModel/DrawingArea callbacks and building menus/submenus */
89 90
static inline void rebuild_menu                               (GtkTreeMenu          *menu);
static gboolean   menu_occupied                               (GtkTreeMenu          *menu,
91 92 93 94
                                                               guint                 left_attach,
                                                               guint                 right_attach,
                                                               guint                 top_attach,
                                                               guint                 bottom_attach);
95
static void       relayout_item                               (GtkTreeMenu          *menu,
96 97 98
                                                               GtkWidget            *item,
                                                               GtkTreeIter          *iter,
                                                               GtkWidget            *prev);
99
static void       gtk_tree_menu_populate                      (GtkTreeMenu          *menu);
100
static GtkWidget *gtk_tree_menu_create_item                   (GtkTreeMenu          *menu,
101 102
                                                               GtkTreeIter          *iter,
                                                               gboolean              header_item);
103
static void       gtk_tree_menu_create_submenu                (GtkTreeMenu          *menu,
104 105
                                                               GtkWidget            *item,
                                                               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 112
                                                               GtkTreePath          *path,
                                                               gboolean             *header_item);
113
static void       row_inserted_cb                             (GtkTreeModel         *model,
114 115 116
                                                               GtkTreePath          *path,
                                                               GtkTreeIter          *iter,
                                                               GtkTreeMenu          *menu);
117
static void       row_deleted_cb                              (GtkTreeModel         *model,
118 119
                                                               GtkTreePath          *path,
                                                               GtkTreeMenu          *menu);
120
static void       row_reordered_cb                            (GtkTreeModel         *model,
121 122 123 124
                                                               GtkTreePath          *path,
                                                               GtkTreeIter          *iter,
                                                               gint                 *new_order,
                                                               GtkTreeMenu          *menu);
125
static void       row_changed_cb                              (GtkTreeModel         *model,
126 127 128
                                                               GtkTreePath          *path,
                                                               GtkTreeIter          *iter,
                                                               GtkTreeMenu          *menu);
129
static void       context_size_changed_cb                     (GtkCellAreaContext   *context,
130 131
                                                               GParamSpec           *pspec,
                                                               GtkWidget            *menu);
132
static void       area_apply_attributes_cb                    (GtkCellArea          *area,
133 134 135 136 137
                                                               GtkTreeModel         *tree_model,
                                                               GtkTreeIter          *iter,
                                                               gboolean              is_expander,
                                                               gboolean              is_expanded,
                                                               GtkTreeMenu          *menu);
138
static void       item_activated_cb                           (GtkMenuItem          *item,
139
                                                               GtkTreeMenu          *menu);
140
static void       submenu_activated_cb                        (GtkTreeMenu          *submenu,
141 142
                                                               const gchar          *path,
                                                               GtkTreeMenu          *menu);
143
static void       gtk_tree_menu_set_model_internal            (GtkTreeMenu          *menu,
144
                                                               GtkTreeModel         *model);
145

146 147


148 149 150 151 152 153 154 155 156
struct _GtkTreeMenuPrivate
{
  /* TreeModel and parent for this menu */
  GtkTreeModel        *model;
  GtkTreeRowReference *root;

  /* CellArea and context for this menu */
  GtkCellArea         *area;
  GtkCellAreaContext  *context;
157

158 159
  /* Signals */
  gulong               size_changed_id;
160
  gulong               apply_attributes_id;
161 162 163
  gulong               row_inserted_id;
  gulong               row_deleted_id;
  gulong               row_reordered_id;
164 165 166 167 168 169 170 171 172 173
  gulong               row_changed_id;

  /* Grid menu mode */
  gint                 wrap_width;
  gint                 row_span_col;
  gint                 col_span_col;

  /* Flags */
  guint32              menu_with_header : 1;
  guint32              tearoff     : 1;
174 175 176 177 178 179 180 181 182 183 184

  /* Row separators */
  GtkTreeViewRowSeparatorFunc row_separator_func;
  gpointer                    row_separator_data;
  GDestroyNotify              row_separator_destroy;
};

enum {
  PROP_0,
  PROP_MODEL,
  PROP_ROOT,
185
  PROP_CELL_AREA,
186 187 188 189
  PROP_TEAROFF,
  PROP_WRAP_WIDTH,
  PROP_ROW_SPAN_COL,
  PROP_COL_SPAN_COL
190 191
};

192 193 194 195 196
enum {
  SIGNAL_MENU_ACTIVATE,
  N_SIGNALS
};

197 198
static guint   tree_menu_signals[N_SIGNALS] = { 0 };
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 203
                         G_IMPLEMENT_INTERFACE (GTK_TYPE_CELL_LAYOUT,
                                                gtk_tree_menu_cell_layout_init));
204 205

static void
206
_gtk_tree_menu_init (GtkTreeMenu *menu)
207
{
208 209 210
  menu->priv = _gtk_tree_menu_get_instance_private (menu);
  menu->priv->row_span_col = -1;
  menu->priv->col_span_col = -1;
211

212
  gtk_menu_set_reserve_toggle_size (GTK_MENU (menu), FALSE);
213 214
}

215
static void
216
_gtk_tree_menu_class_init (GtkTreeMenuClass *class)
217 218 219 220
{
  GObjectClass   *object_class = G_OBJECT_CLASS (class);
  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);

221 222
  tree_menu_path_quark = g_quark_from_static_string ("gtk-tree-menu-path");

223
  object_class->constructed  = gtk_tree_menu_constructed;
224 225 226 227 228
  object_class->dispose      = gtk_tree_menu_dispose;
  object_class->finalize     = gtk_tree_menu_finalize;
  object_class->set_property = gtk_tree_menu_set_property;
  object_class->get_property = gtk_tree_menu_get_property;

229 230
  widget_class->get_preferred_width  = gtk_tree_menu_get_preferred_width;
  widget_class->get_preferred_height = gtk_tree_menu_get_preferred_height;
231 232
  widget_class->get_preferred_width_for_height  = gtk_tree_menu_get_preferred_width_for_height;
  widget_class->get_preferred_height_for_width = gtk_tree_menu_get_preferred_height_for_width;
233

234
  /*
235 236 237 238 239 240 241 242 243 244 245
   * GtkTreeMenu::menu-activate:
   * @menu: a #GtkTreeMenu
   * @path: the #GtkTreePath string for the item which was activated
   * @user_data: the user data
   *
   * This signal is emitted to notify that a menu item in the #GtkTreeMenu
   * was activated and provides the path string from the #GtkTreeModel
   * to specify which row was selected.
   *
   * Since: 3.0
   */
246 247
  tree_menu_signals[SIGNAL_MENU_ACTIVATE] =
    g_signal_new (I_("menu-activate"),
248 249 250 251 252 253
                  G_OBJECT_CLASS_TYPE (object_class),
                  G_SIGNAL_RUN_FIRST,
                  0, /* No class closure here */
                  NULL, NULL,
                  _gtk_marshal_VOID__STRING,
                  G_TYPE_NONE, 1, G_TYPE_STRING);
254

255
  /*
256 257 258
   * GtkTreeMenu:model:
   *
   * The #GtkTreeModel from which the menu is constructed.
259
   *
260 261
   * Since: 3.0
   */
262 263 264 265 266 267 268 269
  g_object_class_install_property (object_class,
                                   PROP_MODEL,
                                   g_param_spec_object ("model",
                                                        P_("TreeMenu model"),
                                                        P_("The model for the tree menu"),
                                                        GTK_TYPE_TREE_MODEL,
                                                        GTK_PARAM_READWRITE));

270
  /*
271 272 273 274 275 276 277 278 279 280
   * GtkTreeMenu:root:
   *
   * The #GtkTreePath that is the root for this menu, or %NULL.
   *
   * The #GtkTreeMenu recursively creates submenus for #GtkTreeModel
   * rows that have children and the "root" for each menu is provided
   * by the parent menu.
   *
   * If you dont provide a root for the #GtkTreeMenu then the whole
   * 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 284
   * 
   * Since: 3.0
   */
285 286 287
  g_object_class_install_property (object_class,
                                   PROP_ROOT,
                                   g_param_spec_boxed ("root",
288 289 290 291 292
                                                       P_("TreeMenu root row"),
                                                       P_("The TreeMenu will display children of the "
                                                          "specified root"),
                                                       GTK_TYPE_TREE_PATH,
                                                       GTK_PARAM_READWRITE));
293

294
  /*
295 296 297 298 299 300 301 302 303
   * GtkTreeMenu:cell-area:
   *
   * The #GtkCellArea used to render cells in the menu items.
   *
   * You can provide a different cell area at object construction
   * time, otherwise the #GtkTreeMenu will use a #GtkCellAreaBox.
   *
   * Since: 3.0
   */
304
  g_object_class_install_property (object_class,
305 306 307 308 309 310
                                   PROP_CELL_AREA,
                                   g_param_spec_object ("cell-area",
                                                        P_("Cell Area"),
                                                        P_("The GtkCellArea used to layout cells"),
                                                        GTK_TYPE_CELL_AREA,
                                                        GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
311

312
  /*
313 314 315 316 317 318
   * GtkTreeMenu:tearoff:
   *
   * Specifies whether this menu comes with a leading tearoff menu item
   *
   * Since: 3.0
   */
319
  g_object_class_install_property (object_class,
320 321 322 323 324 325
                                   PROP_TEAROFF,
                                   g_param_spec_boolean ("tearoff",
                                                         P_("Tearoff"),
                                                         P_("Whether the menu has a tearoff item"),
                                                         FALSE,
                                                         GTK_PARAM_READWRITE));
326

327
  /*
328 329 330 331 332 333 334 335 336
   * GtkTreeMenu:wrap-width:
   *
   * If wrap-width is set to a positive value, the list will be
   * displayed in multiple columns, the number of columns is
   * determined by wrap-width.
   *
   * Since: 3.0
   */
  g_object_class_install_property (object_class,
337 338 339 340 341 342 343 344
                                   PROP_WRAP_WIDTH,
                                   g_param_spec_int ("wrap-width",
                                                     P_("Wrap Width"),
                                                     P_("Wrap width for laying out items in a grid"),
                                                     0,
                                                     G_MAXINT,
                                                     0,
                                                     GTK_PARAM_READWRITE));
345

346
  /*
347 348
   * GtkTreeMenu:row-span-column:
   *
349 350
   * If this is set to a non-negative value, it must be the index of a column
   * of type %G_TYPE_INT in the model.
351
   *
352 353
   * The values of that column are used to determine how many rows a value in
   * the list will span. Therefore, the values in the model column pointed to
354 355 356 357 358
   * by this property must be greater than zero and not larger than wrap-width.
   *
   * Since: 3.0
   */
  g_object_class_install_property (object_class,
359 360 361 362 363 364 365 366
                                   PROP_ROW_SPAN_COL,
                                   g_param_spec_int ("row-span-column",
                                                     P_("Row span column"),
                                                     P_("TreeModel column containing the row span values"),
                                                     -1,
                                                     G_MAXINT,
                                                     -1,
                                                     GTK_PARAM_READWRITE));
367

368
  /*
369 370
   * GtkTreeMenu:column-span-column:
   *
371 372
   * If this is set to a non-negative value, it must be the index of a column
   * of type %G_TYPE_INT in the model.
373
   *
374 375
   * The values of that column are used to determine how many columns a value
   * in the list will span.
376 377 378 379
   *
   * Since: 3.0
   */
  g_object_class_install_property (object_class,
380 381 382 383 384 385 386 387
                                   PROP_COL_SPAN_COL,
                                   g_param_spec_int ("column-span-column",
                                                     P_("Column span column"),
                                                     P_("TreeModel column containing the column span values"),
                                                     -1,
                                                     G_MAXINT,
                                                     -1,
                                                     GTK_PARAM_READWRITE));
388 389 390 391 392
}

/****************************************************************
 *                         GObjectClass                         *
 ****************************************************************/
393 394
static void
gtk_tree_menu_constructed (GObject *object)
395
{
396 397
  GtkTreeMenu *menu = GTK_TREE_MENU (object);
  GtkTreeMenuPrivate *priv = menu->priv;
398

399
  G_OBJECT_CLASS (_gtk_tree_menu_parent_class)->constructed (object);
400 401 402 403 404 405 406 407 408

  if (!priv->area)
    {
      GtkCellArea *area = gtk_cell_area_box_new ();

      gtk_tree_menu_set_area (menu, area);
    }

  priv->context = gtk_cell_area_create_context (priv->area);
409

410
  priv->size_changed_id =
411
    g_signal_connect (priv->context, "notify",
412
                      G_CALLBACK (context_size_changed_cb), menu);
413 414 415 416 417 418 419 420 421 422 423
}

static void
gtk_tree_menu_dispose (GObject *object)
{
  GtkTreeMenu        *menu;
  GtkTreeMenuPrivate *priv;

  menu = GTK_TREE_MENU (object);
  priv = menu->priv;

424
  _gtk_tree_menu_set_model (menu, NULL);
425 426 427 428 429 430 431 432 433 434 435 436
  gtk_tree_menu_set_area (menu, NULL);

  if (priv->context)
    {
      /* Disconnect signals */
      g_signal_handler_disconnect (priv->context, priv->size_changed_id);

      g_object_unref (priv->context);
      priv->context = NULL;
      priv->size_changed_id = 0;
    }

437
  G_OBJECT_CLASS (_gtk_tree_menu_parent_class)->dispose (object);
438 439 440 441 442
}

static void
gtk_tree_menu_finalize (GObject *object)
{
443 444 445 446 447 448
  GtkTreeMenu        *menu;
  GtkTreeMenuPrivate *priv;

  menu = GTK_TREE_MENU (object);
  priv = menu->priv;

449
  _gtk_tree_menu_set_row_separator_func (menu, NULL, NULL, NULL);
450

451
  if (priv->root)
452
    gtk_tree_row_reference_free (priv->root);
453

454
  G_OBJECT_CLASS (_gtk_tree_menu_parent_class)->finalize (object);
455 456 457 458
}

static void
gtk_tree_menu_set_property (GObject            *object,
459 460 461
                            guint               prop_id,
                            const GValue       *value,
                            GParamSpec         *pspec)
462 463 464 465 466 467
{
  GtkTreeMenu *menu = GTK_TREE_MENU (object);

  switch (prop_id)
    {
    case PROP_MODEL:
468
      _gtk_tree_menu_set_model (menu, g_value_get_object (value));
469 470 471
      break;

    case PROP_ROOT:
472
      _gtk_tree_menu_set_root (menu, g_value_get_boxed (value));
473 474 475 476 477 478 479
      break;

    case PROP_CELL_AREA:
      /* Construct-only, can only be assigned once */
      gtk_tree_menu_set_area (menu, (GtkCellArea *)g_value_get_object (value));
      break;

480
    case PROP_TEAROFF:
481
      _gtk_tree_menu_set_tearoff (menu, g_value_get_boolean (value));
482 483
      break;

484
    case PROP_WRAP_WIDTH:
485
      _gtk_tree_menu_set_wrap_width (menu, g_value_get_int (value));
486 487 488
      break;

     case PROP_ROW_SPAN_COL:
489
      _gtk_tree_menu_set_row_span_column (menu, g_value_get_int (value));
490 491 492
      break;

     case PROP_COL_SPAN_COL:
493
      _gtk_tree_menu_set_column_span_column (menu, g_value_get_int (value));
494 495
      break;

496 497 498 499 500 501 502 503
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
    }
}

static void
gtk_tree_menu_get_property (GObject            *object,
504 505 506
                            guint               prop_id,
                            GValue             *value,
                            GParamSpec         *pspec)
507 508 509 510 511 512
{
  GtkTreeMenu        *menu = GTK_TREE_MENU (object);
  GtkTreeMenuPrivate *priv = menu->priv;

  switch (prop_id)
    {
513 514 515
    case PROP_MODEL:
      g_value_set_object (value, priv->model);
      break;
516

517 518 519
    case PROP_ROOT:
      g_value_set_boxed (value, priv->root);
      break;
520

521 522 523
    case PROP_CELL_AREA:
      g_value_set_object (value, priv->area);
      break;
524

525 526 527
    case PROP_TEAROFF:
      g_value_set_boolean (value, priv->tearoff);
      break;
528

529 530 531
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555
    }
}

/****************************************************************
 *                         GtkWidgetClass                       *
 ****************************************************************/

/* We tell all the menu items to reserve space for the submenu
 * indicator if there is at least one submenu, this way we ensure
 * that every internal cell area gets allocated the
 * same width (and requested height for the same appropriate width).
 */
static void
sync_reserve_submenu_size (GtkTreeMenu *menu)
{
  GList              *children, *l;
  gboolean            has_submenu = FALSE;

  children = gtk_container_get_children (GTK_CONTAINER (menu));
  for (l = children; l; l = l->next)
    {
      GtkMenuItem *item = l->data;

      if (gtk_menu_item_get_submenu (item) != NULL)
556 557 558 559
        {
          has_submenu = TRUE;
          break;
        }
560 561 562 563 564 565 566 567 568 569 570 571 572 573
    }

  for (l = children; l; l = l->next)
    {
      GtkMenuItem *item = l->data;

      gtk_menu_item_set_reserve_indicator (item, has_submenu);
    }

  g_list_free (children);
}

static void
gtk_tree_menu_get_preferred_width (GtkWidget           *widget,
574 575
                                   gint                *minimum_size,
                                   gint                *natural_size)
576 577 578 579
{
  GtkTreeMenu        *menu = GTK_TREE_MENU (widget);
  GtkTreeMenuPrivate *priv = menu->priv;

580 581 582 583 584 585
  /* We leave the requesting work up to the cellviews which operate in the same
   * context, reserving space for the submenu indicator if any of the items have
   * submenus ensures that every cellview will receive the same allocated width.
   *
   * Since GtkMenu does hieght-for-width correctly, we know that the width of
   * every cell will be requested before the height-for-widths are requested.
586
   */
587
  g_signal_handler_block (priv->context, priv->size_changed_id);
588 589 590

  sync_reserve_submenu_size (menu);

591
  GTK_WIDGET_CLASS (_gtk_tree_menu_parent_class)->get_preferred_width (widget, minimum_size, natural_size);
592 593 594 595 596 597

  g_signal_handler_unblock (priv->context, priv->size_changed_id);
}

static void
gtk_tree_menu_get_preferred_height (GtkWidget           *widget,
598 599
                                    gint                *minimum_size,
                                    gint                *natural_size)
600 601 602 603 604 605 606 607
{
  GtkTreeMenu        *menu = GTK_TREE_MENU (widget);
  GtkTreeMenuPrivate *priv = menu->priv;

  g_signal_handler_block (priv->context, priv->size_changed_id);

  sync_reserve_submenu_size (menu);

608
  GTK_WIDGET_CLASS (_gtk_tree_menu_parent_class)->get_preferred_height (widget, minimum_size, natural_size);
609 610 611 612

  g_signal_handler_unblock (priv->context, priv->size_changed_id);
}

613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655
static void
gtk_tree_menu_get_preferred_width_for_height (GtkWidget           *widget,
                                              gint                 for_height,
                                              gint                *minimum_size,
                                              gint                *natural_size)
{
  GtkTreeMenu        *menu = GTK_TREE_MENU (widget);
  GtkTreeMenuPrivate *priv = menu->priv;

  /* We leave the requesting work up to the cellviews which operate in the same
   * context, reserving space for the submenu indicator if any of the items have
   * submenus ensures that every cellview will receive the same allocated width.
   *
   * Since GtkMenu does hieght-for-width correctly, we know that the width of
   * every cell will be requested before the height-for-widths are requested.
   */
  g_signal_handler_block (priv->context, priv->size_changed_id);

  sync_reserve_submenu_size (menu);

  GTK_WIDGET_CLASS (_gtk_tree_menu_parent_class)->get_preferred_width_for_height (widget, for_height, minimum_size, natural_size);

  g_signal_handler_unblock (priv->context, priv->size_changed_id);
}

static void
gtk_tree_menu_get_preferred_height_for_width (GtkWidget           *widget,
                                              gint                 for_width,
                                              gint                *minimum_size,
                                              gint                *natural_size)
{
  GtkTreeMenu        *menu = GTK_TREE_MENU (widget);
  GtkTreeMenuPrivate *priv = menu->priv;

  g_signal_handler_block (priv->context, priv->size_changed_id);

  sync_reserve_submenu_size (menu);

  GTK_WIDGET_CLASS (_gtk_tree_menu_parent_class)->get_preferred_height_for_width (widget, for_width, minimum_size, natural_size);

  g_signal_handler_unblock (priv->context, priv->size_changed_id);
}

656 657 658 659 660 661
/****************************************************************
 *                      GtkCellLayoutIface                      *
 ****************************************************************/
static void
gtk_tree_menu_cell_layout_init (GtkCellLayoutIface  *iface)
{
662
  iface->get_area = gtk_tree_menu_cell_layout_get_area;
663 664 665 666 667 668 669 670 671 672 673 674 675 676 677
}

static GtkCellArea *
gtk_tree_menu_cell_layout_get_area (GtkCellLayout *layout)
{
  GtkTreeMenu        *menu = GTK_TREE_MENU (layout);
  GtkTreeMenuPrivate *priv = menu->priv;

  return priv->area;
}


/****************************************************************
 *             TreeModel callbacks/populating menus             *
 ****************************************************************/
678 679
static GtkWidget *
gtk_tree_menu_get_path_item (GtkTreeMenu          *menu,
680
                             GtkTreePath          *search)
681 682 683 684 685 686 687 688 689 690 691 692
{
  GtkWidget *item = NULL;
  GList     *children, *l;

  children = gtk_container_get_children (GTK_CONTAINER (menu));

  for (l = children; item == NULL && l != NULL; l = l->next)
    {
      GtkWidget   *child = l->data;
      GtkTreePath *path  = NULL;

      if (GTK_IS_SEPARATOR_MENU_ITEM (child))
693 694 695 696 697 698 699 700 701
        {
          GtkTreeRowReference *row =
            g_object_get_qdata (G_OBJECT (child), tree_menu_path_quark);

          if (row)
            {
              path = gtk_tree_row_reference_get_path (row);

              if (!path)
702
                /* Return any first child where its row-reference became invalid,
703 704 705 706 707 708
                 * this is because row-references get null paths before we recieve
                 * the "row-deleted" signal.
                 */
                item = child;
            }
        }
709
      else if (!GTK_IS_TEAROFF_MENU_ITEM (child))
710 711
        {
          GtkWidget *view = gtk_bin_get_child (GTK_BIN (child));
712

713 714 715
          /* It's always a cellview */
          if (GTK_IS_CELL_VIEW (view))
            path = gtk_cell_view_get_displayed_row (GTK_CELL_VIEW (view));
716

717
          if (!path)
718
            /* Return any first child where its row-reference became invalid,
719 720 721 722 723
             * this is because row-references get null paths before we recieve
             * the "row-deleted" signal.
             */
            item = child;
        }
724 725

      if (path)
726 727 728
        {
          if (gtk_tree_path_compare (search, path) == 0)
            item = child;
729

730 731
          gtk_tree_path_free (path);
        }
732 733 734 735 736 737 738
    }

  g_list_free (children);

  return item;
}

739 740
static gboolean
gtk_tree_menu_path_in_menu (GtkTreeMenu  *menu,
741 742
                            GtkTreePath  *path,
                            gboolean     *header_item)
743 744
{
  GtkTreeMenuPrivate *priv = menu->priv;
745 746
  gboolean            in_menu = FALSE;
  gboolean            is_header = FALSE;
747

748
  /* Check if the is in root of the model */
749 750
  if (gtk_tree_path_get_depth (path) == 1 && !priv->root)
    in_menu = TRUE;
751
  /* If we are a submenu, compare the parent path */
752
  else if (priv->root)
753
    {
754
      GtkTreePath *root_path   = gtk_tree_row_reference_get_path (priv->root);
755
      GtkTreePath *search_path = gtk_tree_path_copy (path);
756

757
      if (root_path)
758 759 760 761 762 763 764 765 766 767 768 769 770 771 772
        {
          if (priv->menu_with_header &&
              gtk_tree_path_compare (root_path, search_path) == 0)
            {
              in_menu   = TRUE;
              is_header = TRUE;
            }
          else if (gtk_tree_path_get_depth (search_path) > 1)
            {
              gtk_tree_path_up (search_path);

              if (gtk_tree_path_compare (root_path, search_path) == 0)
                in_menu = TRUE;
            }
        }
773
      gtk_tree_path_free (root_path);
774
      gtk_tree_path_free (search_path);
775 776
    }

777 778 779 780 781 782
  if (header_item)
    *header_item = is_header;

  return in_menu;
}

783
static GtkWidget *
784 785
gtk_tree_menu_path_needs_submenu (GtkTreeMenu *menu,
                                  GtkTreePath *search)
786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805
{
  GtkWidget   *item = NULL;
  GList       *children, *l;
  GtkTreePath *parent_path;

  if (gtk_tree_path_get_depth (search) <= 1)
    return NULL;

  parent_path = gtk_tree_path_copy (search);
  gtk_tree_path_up (parent_path);

  children    = gtk_container_get_children (GTK_CONTAINER (menu));

  for (l = children; item == NULL && l != NULL; l = l->next)
    {
      GtkWidget   *child = l->data;
      GtkTreePath *path  = NULL;

      /* Separators dont get submenus, if it already has a submenu then let
       * the submenu handle inserted rows */
806 807 808 809
      if (!GTK_IS_SEPARATOR_MENU_ITEM (child) &&
          !gtk_menu_item_get_submenu (GTK_MENU_ITEM (child)))
        {
          GtkWidget *view = gtk_bin_get_child (GTK_BIN (child));
810

811 812 813 814
          /* It's always a cellview */
          if (GTK_IS_CELL_VIEW (view))
            path = gtk_cell_view_get_displayed_row (GTK_CELL_VIEW (view));
        }
815 816

      if (path)
817 818 819
        {
          if (gtk_tree_path_compare (parent_path, path) == 0)
            item = child;
820

821 822
          gtk_tree_path_free (path);
        }
823 824 825 826 827 828 829 830
    }

  g_list_free (children);
  gtk_tree_path_free (parent_path);

  return item;
}

831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848
static GtkWidget *
find_empty_submenu (GtkTreeMenu  *menu)
{
  GtkTreeMenuPrivate *priv = menu->priv;
  GList              *children, *l;
  GtkWidget          *submenu = NULL;

  children = gtk_container_get_children (GTK_CONTAINER (menu));

  for (l = children; submenu == NULL && l != NULL; l = l->next)
    {
      GtkWidget   *child = l->data;
      GtkTreePath *path  = NULL;
      GtkTreeIter  iter;

      /* Separators dont get submenus, if it already has a submenu then let
       * the submenu handle inserted rows */
      if (!GTK_IS_SEPARATOR_MENU_ITEM (child) && !GTK_IS_TEAROFF_MENU_ITEM (child))
849 850
        {
          GtkWidget *view = gtk_bin_get_child (GTK_BIN (child));
851

852 853 854 855
          /* It's always a cellview */
          if (GTK_IS_CELL_VIEW (view))
            path = gtk_cell_view_get_displayed_row (GTK_CELL_VIEW (view));
        }
856 857

      if (path)
858 859 860 861
        {
          if (gtk_tree_model_get_iter (priv->model, &iter, path) &&
              !gtk_tree_model_iter_has_child (priv->model, &iter))
            submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (child));
862

863 864
          gtk_tree_path_free (path);
        }
865 866 867 868 869 870 871
    }

  g_list_free (children);

  return submenu;
}

872 873
static void
row_inserted_cb (GtkTreeModel     *model,
874 875 876
                 GtkTreePath      *path,
                 GtkTreeIter      *iter,
                 GtkTreeMenu      *menu)
877 878 879
{
  GtkTreeMenuPrivate *priv = menu->priv;
  gint               *indices, index, depth;
880
  GtkWidget          *item;
881

882
  /* If the iter should be in this menu then go ahead and insert it */
883
  if (gtk_tree_menu_path_in_menu (menu, path, NULL))
884
    {
885
      if (priv->wrap_width > 0)
886
        rebuild_menu (menu);
887
      else
888 889 890 891 892 893
        {
          /* Get the index of the path for this depth */
          indices = gtk_tree_path_get_indices (path);
          depth   = gtk_tree_path_get_depth (path);
          index   = indices[depth -1];

894
          /* Menus with a header include a menuitem for its root node
895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910
           * and a separator menu item */
          if (priv->menu_with_header)
            index += 2;

          /* Index after the tearoff item for the root menu if
           * there is a tearoff item
           */
          if (priv->root == NULL && priv->tearoff)
            index += 1;

          item = gtk_tree_menu_create_item (menu, iter, FALSE);
          gtk_menu_shell_insert (GTK_MENU_SHELL (menu), item, index);

          /* Resize everything */
          gtk_cell_area_context_reset (menu->priv->context);
        }
911
    }
912 913 914 915 916
  else
    {
      /* Create submenus for iters if we need to */
      item = gtk_tree_menu_path_needs_submenu (menu, path);
      if (item)
917 918
        {
          GtkTreePath *item_path = gtk_tree_path_copy (path);
919

920 921 922 923
          gtk_tree_path_up (item_path);
          gtk_tree_menu_create_submenu (menu, item, item_path);
          gtk_tree_path_free (item_path);
        }
924
    }
925 926 927 928
}

static void
row_deleted_cb (GtkTreeModel     *model,
929 930
                GtkTreePath      *path,
                GtkTreeMenu      *menu)
931 932 933 934
{
  GtkTreeMenuPrivate *priv = menu->priv;
  GtkWidget          *item;

935
  /* If it's the header item we leave it to the parent menu
936 937
   * to remove us from its menu
   */
938 939 940
  item = gtk_tree_menu_get_path_item (menu, path);

  if (item)
941
    {
942
      if (priv->wrap_width > 0)
943
        rebuild_menu (menu);
944
      else
945 946 947 948 949 950 951
        {
          /* Get rid of the deleted item */
          gtk_widget_destroy (item);

          /* Resize everything */
          gtk_cell_area_context_reset (menu->priv->context);
        }
952
    }
953
  else
954 955 956 957 958
    {
      /* It's up to the parent menu to destroy a child menu that becomes empty
       * since the topmost menu belongs to the user and is allowed to have no contents */
      GtkWidget *submenu = find_empty_submenu (menu);
      if (submenu)
959
        gtk_widget_destroy (submenu);
960
    }
961 962 963 964
}

static void
row_reordered_cb (GtkTreeModel    *model,
965 966 967 968
                  GtkTreePath     *path,
                  GtkTreeIter     *iter,
                  gint            *new_order,
                  GtkTreeMenu     *menu)
969 970 971 972
{
  GtkTreeMenuPrivate *priv = menu->priv;
  gboolean            this_menu = FALSE;

973
  if (gtk_tree_path_get_depth (path) == 0 && !priv->root)
974 975 976 977
    this_menu = TRUE;
  else if (priv->root)
    {
      GtkTreePath *root_path =
978
        gtk_tree_row_reference_get_path (priv->root);
979 980

      if (gtk_tree_path_compare (root_path, path) == 0)
981
        this_menu = TRUE;
982 983 984 985 986

      gtk_tree_path_free (root_path);
    }

  if (this_menu)
987 988 989
    rebuild_menu (menu);
}

990
static gint
991
menu_item_position (GtkTreeMenu *menu,
992
                    GtkWidget   *item)
993 994 995 996 997 998 999 1000 1001 1002
{
  GList *children, *l;
  gint   position;

  children = gtk_container_get_children (GTK_CONTAINER (menu));
  for (position = 0, l = children; l; position++, l = l->next)
    {
      GtkWidget *iitem = l->data;

      if (item == iitem)
1003
        break;
1004 1005 1006 1007 1008 1009 1010 1011 1012
    }

  g_list_free (children);

  return position;
}

static void
row_changed_cb (GtkTreeModel         *model,
1013 1014 1015
                GtkTreePath          *path,
                GtkTreeIter          *iter,
                GtkTreeMenu          *menu)
1016 1017 1018 1019 1020 1021 1022 1023
{
  GtkTreeMenuPrivate *priv = menu->priv;
  gboolean            is_separator = FALSE;
  GtkWidget          *item;

  item = gtk_tree_menu_get_path_item (menu, path);

  if (priv->root)
1024
    {
1025
      GtkTreePath *root_path =
1026 1027
        gtk_tree_row_reference_get_path (priv->root);

1028
      if (root_path && gtk_tree_path_compare (root_path, path) == 0)
1029
        {
1030
          if (item)
1031 1032 1033 1034 1035 1036 1037 1038 1039 1040
            {
              /* Destroy the header item and then the following separator */
              gtk_widget_destroy (item);
              gtk_widget_destroy (GTK_MENU_SHELL (menu)->priv->children->data);

              priv->menu_with_header = FALSE;
            }

          gtk_tree_path_free (root_path);
        }
1041
    }
1042

1043 1044 1045
  if (item)
    {
      if (priv->wrap_width > 0)
1046 1047 1048 1049
        /* Ugly, we need to rebuild the menu here if
         * the row-span/row-column values change
         */
        rebuild_menu (menu);
1050
      else
1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066
        {
          if (priv->row_separator_func)
            is_separator =
              priv->row_separator_func (model, iter,
                                        priv->row_separator_data);


          if (is_separator != GTK_IS_SEPARATOR_MENU_ITEM (item))
            {
              gint position = menu_item_position (menu, item);

              gtk_widget_destroy (item);
              item = gtk_tree_menu_create_item (menu, iter, FALSE);
              gtk_menu_shell_insert (GTK_MENU_SHELL (menu), item, position);
            }
        }
1067 1068 1069
    }
}

1070 1071
static void
context_size_changed_cb (GtkCellAreaContext  *context,
1072 1073
                         GParamSpec          *pspec,
                         GtkWidget           *menu)
1074 1075 1076 1077 1078
{
  if (!strcmp (pspec->name, "minimum-width") ||
      !strcmp (pspec->name, "natural-width") ||
      !strcmp (pspec->name, "minimum-height") ||
      !strcmp (pspec->name, "natural-height"))
1079
    gtk_widget_queue_resize (menu);
1080 1081
}

1082 1083 1084 1085 1086
static gboolean
area_is_sensitive (GtkCellArea *area)
{
  GList    *cells, *list;
  gboolean  sensitive = FALSE;
1087

1088 1089 1090 1091 1092
  cells = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (area));

  for (list = cells; list; list = list->next)
    {
      g_object_get (list->data, "sensitive", &sensitive, NULL);
1093

1094
      if (sensitive)
1095
        break;
1096 1097 1098 1099 1100 1101 1102 1103
    }
  g_list_free (cells);

  return sensitive;
}

static void
area_apply_attributes_cb (GtkCellArea          *area,
1104 1105 1106 1107 1108
                          GtkTreeModel         *tree_model,
                          GtkTreeIter          *iter,
                          gboolean              is_expander,
                          gboolean              is_expanded,
                          GtkTreeMenu          *menu)
1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124
{
  /* If the menu for this iter has a submenu */
  GtkTreeMenuPrivate *priv = menu->priv;
  GtkTreePath        *path;
  GtkWidget          *item;
  gboolean            is_header;
  gboolean            sensitive;

  path = gtk_tree_model_get_path (tree_model, iter);

  if (gtk_tree_menu_path_in_menu (menu, path, &is_header))
    {
      item = gtk_tree_menu_get_path_item (menu, path);

      /* If there is no submenu, go ahead and update item sensitivity,
       * items with submenus are always sensitive */
1125
      if (item && !gtk_menu_item_get_submenu (GTK_MENU_ITEM (item)))
1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145
        {
          sensitive = area_is_sensitive (priv->area);

          gtk_widget_set_sensitive (item, sensitive);

          if (is_header)
            {
              /* For header items we need to set the sensitivity
               * of the following separator item
               */
              if (GTK_MENU_SHELL (menu)->priv->children &&
                  GTK_MENU_SHELL (menu)->priv->children->next)
                {
                  GtkWidget *separator =
                    GTK_MENU_SHELL (menu)->priv->children->next->data;

                  gtk_widget_set_sensitive (separator, sensitive);
                }
            }
        }
1146 1147 1148 1149 1150
    }

  gtk_tree_path_free (path);
}

1151 1152
static void
gtk_tree_menu_set_area (GtkTreeMenu *menu,
1153
                        GtkCellArea *area)
1154 1155 1156 1157
{
  GtkTreeMenuPrivate *priv = menu->priv;

  if (priv->area)
1158 1159
    {
      g_signal_handler_disconnect (priv->area,
1160
                                   priv->apply_attributes_id);
1161 1162 1163 1164
      priv->apply_attributes_id = 0;

      g_object_unref (priv->area);
    }
1165 1166 1167 1168

  priv->area = area;

  if (priv->area)
1169 1170 1171 1172
    {
      g_object_ref_sink (priv->area);

      priv->apply_attributes_id =
1173 1174
        g_signal_connect (priv->area, "apply-attributes",
                          G_CALLBACK (area_apply_attributes_cb), menu);
1175
    }
1176 1177
}

1178 1179 1180 1181 1182 1183 1184 1185 1186
static gboolean
menu_occupied (GtkTreeMenu *menu,
               guint        left_attach,
               guint        right_attach,
               guint        top_attach,
               guint        bottom_attach)
{
  GList *i;

1187
  for (i = GTK_MENU_SHELL (menu)->priv->children; i; i = i->next)
1188 1189 1190
    {
      guint l, r, b, t;

1191 1192
      gtk_container_child_get (GTK_CONTAINER (menu),
                               i->data,
1193 1194 1195 1196 1197 1198 1199 1200
                               "left-attach", &l,
                               "right-attach", &r,
                               "bottom-attach", &b,
                               "top-attach", &t,
                               NULL);

      /* look if this item intersects with the given coordinates */
      if (right_attach > l && left_attach < r && bottom_attach > t && top_attach < b)
1201
        return TRUE;
1202 1203 1204 1205 1206 1207 1208
    }

  return FALSE;
}

static void
relayout_item (GtkTreeMenu *menu,
1209 1210 1211
               GtkWidget   *item,
               GtkTreeIter *iter,
               GtkWidget   *prev)
1212 1213 1214 1215
{
  GtkTreeMenuPrivate *priv = menu->priv;
  gint                current_col = 0, current_row = 0;
  gint                rows = 1, cols = 1;
1216

1217 1218 1219 1220 1221
  if (priv->col_span_col == -1 &&
      priv->row_span_col == -1 &&
      prev)
    {
      gtk_container_child_get (GTK_CONTAINER (menu), prev,
1222 1223 1224
                               "right-attach", &current_col,
                               "top-attach", &current_row,
                               NULL);
1225
      if (current_col + cols > priv->wrap_width)
1226 1227 1228 1229
        {
          current_col = 0;
          current_row++;
        }
1230 1231 1232 1233
    }
  else
    {
      if (priv->col_span_col != -1)
1234 1235 1236
        gtk_tree_model_get (priv->model, iter,
                            priv->col_span_col, &cols,
                            -1);
1237
      if (priv->row_span_col != -1)
1238 1239 1240
        gtk_tree_model_get (priv->model, iter,
                            priv->row_span_col, &rows,
                            -1);
1241 1242

      while (1)
1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256
        {
          if (current_col + cols > priv->wrap_width)
            {
              current_col = 0;
              current_row++;
            }

          if (!menu_occupied (menu,
                              current_col, current_col + cols,
                              current_row, current_row + rows))
            break;

          current_col++;
        }
1257 1258 1259 1260 1261 1262 1263 1264
    }

  /* set attach props */
  gtk_menu_attach (GTK_MENU (menu), item,
                   current_col, current_col + cols,
                   current_row, current_row + rows);
}

1265 1266
static void
gtk_tree_menu_create_submenu (GtkTreeMenu *menu,
1267 1268
                              GtkWidget   *item,
                              GtkTreePath *path)
1269 1270 1271 1272 1273 1274 1275 1276
{
  GtkTreeMenuPrivate *priv = menu->priv;
  GtkWidget          *view;
  GtkWidget          *submenu;

  view = gtk_bin_get_child (GTK_BIN (item));
  gtk_cell_view_set_draw_sensitive (GTK_CELL_VIEW (view), TRUE);

1277
  submenu = _gtk_tree_menu_new_with_area (priv->area);
1278

1279 1280 1281 1282
  _gtk_tree_menu_set_row_separator_func (GTK_TREE_MENU (submenu),
                                         priv->row_separator_func,
                                         priv->row_separator_data,
                                         priv->row_separator_destroy);
1283

1284 1285 1286
  _gtk_tree_menu_set_wrap_width (GTK_TREE_MENU (submenu), priv->wrap_width);
  _gtk_tree_menu_set_row_span_column (GTK_TREE_MENU (submenu), priv->row_span_col);
  _gtk_tree_menu_set_column_span_column (GTK_TREE_MENU (submenu), priv->col_span_col);
1287

1288
  gtk_tree_menu_set_model_internal (GTK_TREE_MENU (submenu), priv->model);
1289
  _gtk_tree_menu_set_root (GTK_TREE_MENU (submenu), path);
1290
  gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), submenu);
1291 1292 1293

  g_signal_connect (submenu, "menu-activate",
                    G_CALLBACK (submenu_activated_cb), menu);
1294 1295
}

1296 1297
static GtkWidget *
gtk_tree_menu_create_item (GtkTreeMenu *menu,
1298 1299
                           GtkTreeIter *iter,
                           gboolean     header_item)
1300 1301 1302 1303
{
  GtkTreeMenuPrivate *priv = menu->priv;
  GtkWidget          *item, *view;
  GtkTreePath        *path;
1304
  gboolean            is_separator = FALSE;
1305 1306 1307

  path = gtk_tree_model_get_path (priv->model, iter);

1308
  if (priv->row_separator_func)
1309
    is_separator =
1310
      priv->row_separator_func (priv->model, iter,
1311
                                priv->row_separator_data);
1312

1313 1314 1315
  if (is_separator)
    {
      item = gtk_separator_menu_item_new ();
1316
      gtk_widget_show (item);
1317

1318
      g_object_set_qdata_full (G_OBJECT (item),
1319 1320 1321
                               tree_menu_path_quark,
                               gtk_tree_row_reference_new (priv->model, path),
                               (GDestroyNotify)gtk_tree_row_reference_free);
1322 1323 1324 1325 1326 1327 1328
    }
  else
    {
      view = gtk_cell_view_new_with_context (priv->area, priv->context);
      item = gtk_menu_item_new ();
      gtk_widget_show (view);
      gtk_widget_show (item);
1329

1330 1331
      gtk_cell_view_set_model (GTK_CELL_VIEW (view), priv->model);
      gtk_cell_view_set_displayed_row (GTK_CELL_VIEW (view), path);
1332

1333 1334
      gtk_widget_show (view);
      gtk_container_add (GTK_CONTAINER (item), view);
1335

1336 1337 1338 1339
      g_signal_connect (item, "activate", G_CALLBACK (item_activated_cb), menu);

      /* Add a GtkTreeMenu submenu to render the children of this row */
      if (header_item == FALSE &&
1340 1341
          gtk_tree_model_iter_has_child (priv->model, iter))
        gtk_tree_menu_create_submenu (menu, item, path);
1342 1343 1344
    }

  gtk_tree_path_free (path);
1345

1346 1347 1348
  return item;
}

1349
static inline void
1350 1351 1352 1353 1354
rebuild_menu (GtkTreeMenu *menu)
{
  GtkTreeMenuPrivate *priv = menu->priv;

  /* Destroy all the menu items */
1355 1356 1357
  gtk_container_foreach (GTK_CONTAINER (menu),
                         (GtkCallback) gtk_widget_destroy, NULL);

1358 1359 1360 1361 1362 1363
  /* Populate */
  if (priv->model)
    gtk_tree_menu_populate (menu);
}


1364 1365 1366 1367 1368 1369 1370 1371
static void
gtk_tree_menu_populate (GtkTreeMenu *menu)
{
  GtkTreeMenuPrivate *priv = menu->priv;
  GtkTreePath        *path = NULL;
  GtkTreeIter         parent;
  GtkTreeIter         iter;
  gboolean            valid = FALSE;
1372
  GtkWidget          *menu_item, *prev = NULL;
1373 1374 1375 1376 1377 1378 1379 1380 1381 1382

  if (!priv->model)
    return;

  if (priv->root)
    path = gtk_tree_row_reference_get_path (priv->root);

  if (path)
    {
      if (gtk_tree_model_get_iter (priv->model, &parent, path))
1383
        valid = gtk_tree_model_iter_children (priv->model, &iter, &parent);
1384

1385 1386 1387
      gtk_tree_path_free (path);
    }
  else
1388 1389 1390
    {
      /* Tearoff menu items only go in the root menu */
      if (priv->tearoff)
1391 1392 1393
        {
          menu_item = gtk_tearoff_menu_item_new ();
          gtk_widget_show (menu_item);
1394

1395 1396 1397 1398
          if (priv->wrap_width > 0)
            gtk_menu_attach (GTK_MENU (menu), menu_item, 0, priv->wrap_width, 0, 1);
          else
            gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);
1399

1400 1401
          prev = menu_item;
        }
1402 1403 1404

      valid = gtk_tree_model_iter_children (priv->model, &iter, NULL);
    }
1405 1406 1407 1408 1409

  /* Create a menu item for every row at the current depth, add a GtkTreeMenu
   * submenu for iters/items that have children */
  while (valid)
    {
1410
      menu_item = gtk_tree_menu_create_item (menu, &iter, FALSE);
1411

1412 1413
      gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);

1414
      if (priv->wrap_width > 0)
1415
        relayout_item (menu, menu_item, &iter, prev);
1416 1417

      prev  = menu_item;
1418 1419 1420 1421
      valid = gtk_tree_model_iter_next (priv->model, &iter);
    }
}

1422 1423
static void
item_activated_cb (GtkMenuItem          *item,
1424
                   GtkTreeMenu          *menu)
1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435
{
  GtkCellView *view;
  GtkTreePath *path;
  gchar       *path_str;

  /* Only activate leafs, not parents */
  if (!gtk_menu_item_get_submenu (item))
    {
      view     = GTK_CELL_VIEW (gtk_bin_get_child (GTK_BIN (item)));
      path     = gtk_cell_view_get_displayed_row (view);
      path_str = gtk_tree_path_to_string (path);
1436

1437
      g_signal_emit (menu, tree_menu_signals[SIGNAL_MENU_ACTIVATE], 0, path_str);
1438

1439 1440 1441 1442 1443 1444 1445
      g_free (path_str);
      gtk_tree_path_free (path);
    }
}

static void
submenu_activated_cb (GtkTreeMenu          *submenu,
1446 1447
                      const gchar          *path,
                      GtkTreeMenu          *menu)
1448 1449 1450 1451
{
  g_signal_emit (menu, tree_menu_signals[SIGNAL_MENU_ACTIVATE], 0, path);
}

1452 1453 1454 1455 1456
/* Sets the model without rebuilding the menu, prevents
 * infinite recursion while building submenus (we wait
 * until the root is set and then build the menu) */
static void
gtk_tree_menu_set_model_internal (GtkTreeMenu  *menu,
1457
                                  GtkTreeModel *model)
1458 1459 1460 1461 1462 1463 1464 1465
{
  GtkTreeMenuPrivate *priv;

  priv = menu->priv;

  if (priv->model != model)
    {
      if (priv->model)
1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482
        {
          /* Disconnect signals */
          g_signal_handler_disconnect (priv->model,
                                       priv->row_inserted_id);
          g_signal_handler_disconnect (priv->model,
                                       priv->row_deleted_id);
          g_signal_handler_disconnect (priv->model,
                                       priv->row_reordered_id);
          g_signal_handler_disconnect (priv->model,
                                       priv->row_changed_id);
          priv->row_inserted_id  = 0;
          priv->row_deleted_id   = 0;
          priv->row_reordered_id = 0;
          priv->row_changed_id = 0;

          g_object_unref (priv->model);
        }
1483 1484 1485 1486

      priv->model = model;

      if (priv->model)
1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499
        {
          g_object_ref (priv->model);

          /* Connect signals */
          priv->row_inserted_id  = g_signal_connect (priv->model, "row-inserted",
                                                     G_CALLBACK (row_inserted_cb), menu);
          priv->row_deleted_id   = g_signal_connect (priv->model, "row-deleted",
                                                     G_CALLBACK (row_deleted_cb), menu);
          priv->row_reordered_id = g_signal_connect (priv->model, "rows-reordered",
                                                     G_CALLBACK (row_reordered_cb), menu);
          priv->row_changed_id   = g_signal_connect (priv->model, "row-changed",
                                                     G_CALLBACK (row_changed_cb), menu);
        }
1500 1501 1502
    }
}

1503 1504 1505