combobox: Replace GtkTreeMenu with a popover
authorMatthias Clasen <mclasen@redhat.com>
Thu, 26 Dec 2019 05:06:48 +0000 (00:06 -0500)
committerMatthias Clasen <mclasen@redhat.com>
Sun, 29 Dec 2019 23:45:40 +0000 (18:45 -0500)
This does not currently try to reproduce the exact
placement, since GtkPopover doesn't have to have
the necessary placement hints.

gtk/gtkcombobox.c
gtk/gtktreemenu.c [deleted file]
gtk/gtktreemenu.h [deleted file]
gtk/gtktreepopover.c [new file with mode: 0644]
gtk/gtktreepopoverprivate.h [new file with mode: 0644]
gtk/meson.build
gtk/ui/gtkcombobox.ui

index c10a6835ce75e072e0e190c9f0af2cddce3aba66..caf9a04f3e8b94bbbe4ac249dcecd6d5a89ec8dd 100644 (file)
@@ -37,7 +37,7 @@
 #include "gtkmenushellprivate.h"
 #include "gtkprivate.h"
 #include "gtktogglebutton.h"
-#include "gtktreemenu.h"
+#include "gtktreepopoverprivate.h"
 #include "gtktypebuiltins.h"
 #include "gtkeventcontrollerkey.h"
 
@@ -361,6 +361,7 @@ gtk_combo_box_size_allocate (GtkWidget *widget,
 {
   GtkComboBox *combo_box = GTK_COMBO_BOX (widget);
   GtkComboBoxPrivate *priv = gtk_combo_box_get_instance_private (combo_box);
+  gint menu_width;
 
   gtk_widget_size_allocate (priv->box,
                             &(GtkAllocation) {
@@ -368,25 +369,19 @@ gtk_combo_box_size_allocate (GtkWidget *widget,
                               width, height
                             }, baseline);
 
-  if (gtk_widget_get_visible (priv->popup_widget))
-    {
-      gint menu_width;
-
-      gtk_widget_set_size_request (priv->popup_widget, -1, -1);
+  gtk_widget_set_size_request (priv->popup_widget, -1, -1);
 
-      if (priv->popup_fixed_width)
-        gtk_widget_measure (priv->popup_widget, GTK_ORIENTATION_HORIZONTAL, -1,
-                            &menu_width, NULL, NULL, NULL);
-      else
-        gtk_widget_measure (priv->popup_widget, GTK_ORIENTATION_HORIZONTAL, -1,
-                            NULL, &menu_width, NULL, NULL);
+  if (priv->popup_fixed_width)
+    gtk_widget_measure (priv->popup_widget, GTK_ORIENTATION_HORIZONTAL, -1,
+                        &menu_width, NULL, NULL, NULL);
+  else
+    gtk_widget_measure (priv->popup_widget, GTK_ORIENTATION_HORIZONTAL, -1,
+                        NULL, &menu_width, NULL, NULL);
 
-      gtk_widget_set_size_request (priv->popup_widget,
-                                   MAX (width, menu_width), -1);
+  gtk_widget_set_size_request (priv->popup_widget,
+                               MAX (width, menu_width), -1);
 
-      /* reposition the menu after giving it a new width */
-      gtk_menu_reposition (GTK_MENU (priv->popup_widget));
-    }
+  gtk_native_check_resize (GTK_NATIVE (priv->popup_widget));
 }
 
 static void
@@ -834,7 +829,6 @@ gtk_combo_box_init (GtkComboBox *combo_box)
 {
   GtkComboBoxPrivate *priv = gtk_combo_box_get_instance_private (combo_box);
   GtkStyleContext *context;
-  GtkTreeMenu *menu;
   GtkEventController *controller;
 
   priv->active = -1;
@@ -854,20 +848,16 @@ gtk_combo_box_init (GtkComboBox *combo_box)
   priv->id_column = -1;
 
   g_type_ensure (GTK_TYPE_ICON);
-  g_type_ensure (GTK_TYPE_TREE_MENU);
+  g_type_ensure (GTK_TYPE_TREE_POPOVER);
   gtk_widget_init_template (GTK_WIDGET (combo_box));
 
   context = gtk_widget_get_style_context (priv->button);
   gtk_style_context_remove_class (context, "toggle");
   gtk_style_context_add_class (context, "combo");
 
-  menu = GTK_TREE_MENU (priv->popup_widget);
-  _gtk_tree_menu_set_row_separator_func (menu,
-                                         (GtkTreeViewRowSeparatorFunc)gtk_combo_box_row_separator_func,
-                                         combo_box, NULL);
-  gtk_menu_attach_to_widget (GTK_MENU (menu),
-                             GTK_WIDGET (combo_box),
-                             NULL);
+  gtk_tree_popover_set_row_separator_func (GTK_TREE_POPOVER (priv->popup_widget),
+                                           (GtkTreeViewRowSeparatorFunc)gtk_combo_box_row_separator_func,
+                                           combo_box, NULL);
 
   controller = gtk_event_controller_scroll_new (GTK_EVENT_CONTROLLER_SCROLL_VERTICAL |
                                                 GTK_EVENT_CONTROLLER_SCROLL_DISCRETE);
@@ -1174,8 +1164,7 @@ gtk_combo_box_menu_hide (GtkWidget *menu,
 
   gtk_combo_box_child_hide (menu,user_data);
 
-  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->button),
-                                FALSE);
+  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->button), FALSE);
 }
 
 static gboolean
@@ -1217,54 +1206,19 @@ tree_column_row_is_sensitive (GtkComboBox *combo_box,
 }
 
 static void
-update_menu_sensitivity (GtkComboBox *combo_box,
-                         GtkWidget   *menu)
-{
-  GtkComboBoxPrivate *priv = gtk_combo_box_get_instance_private (combo_box);
-  GList *children, *child;
-  GtkWidget *item, *submenu;
-  GtkWidget *cell_view;
-  gboolean sensitive;
-
-  if (!priv->model)
-    return;
-
-  children = gtk_menu_shell_get_items (GTK_MENU_SHELL (menu));
-
-  for (child = children; child; child = child->next)
-    {
-      item = GTK_WIDGET (child->data);
-      cell_view = gtk_bin_get_child (GTK_BIN (item));
-
-      if (!GTK_IS_CELL_VIEW (cell_view))
-        continue;
-
-      submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (item));
-      if (submenu != NULL)
-        {
-          gtk_widget_set_sensitive (item, TRUE);
-          update_menu_sensitivity (combo_box, submenu);
-        }
-      else
-        {
-          sensitive = cell_layout_is_sensitive (GTK_CELL_LAYOUT (cell_view));
-          gtk_widget_set_sensitive (item, sensitive);
-        }
-    }
-
-  g_list_free (children);
-}
-
-static void
-gtk_combo_box_menu_popup (GtkComboBox    *combo_box)
+gtk_combo_box_menu_popup (GtkComboBox *combo_box)
 {
   GtkComboBoxPrivate *priv = gtk_combo_box_get_instance_private (combo_box);
+#if 0
   gint active_item;
   GtkWidget *active;
   int width, min_width, nat_width;
+#endif
 
-  update_menu_sensitivity (combo_box, priv->popup_widget);
+  gtk_tree_popover_open_submenu (GTK_TREE_POPOVER (priv->popup_widget), "main");
+  gtk_popover_popup (GTK_POPOVER (priv->popup_widget));
 
+#if 0
   active_item = -1;
   if (gtk_tree_row_reference_valid (priv->active_row))
     {
@@ -1276,7 +1230,7 @@ gtk_combo_box_menu_popup (GtkComboBox    *combo_box)
     }
 
   /* FIXME handle nested menus better */
-  gtk_menu_set_active (GTK_MENU (priv->popup_widget), active_item);
+  //gtk_tree_popover_set_active (GTK_TREE_POPOVER (priv->popup_widget), active_item);
 
   width = gtk_widget_get_width (GTK_WIDGET (combo_box));
   gtk_widget_set_size_request (priv->popup_widget, -1, -1);
@@ -1294,8 +1248,6 @@ gtk_combo_box_menu_popup (GtkComboBox    *combo_box)
                                         gtk_menu_update_scroll_offset,
                                         NULL);
 
-  g_object_set (priv->popup_widget, "menu-type-hint", GDK_SURFACE_TYPE_HINT_COMBO, NULL);
-
   if (priv->cell_view == NULL)
     {
       g_object_set (priv->popup_widget,
@@ -1374,14 +1326,7 @@ gtk_combo_box_menu_popup (GtkComboBox    *combo_box)
                                 GDK_GRAVITY_NORTH_WEST,
                                 NULL);
     }
-
-    /* Re-get the active item before selecting it, as a popped-up handler – like
-     * that of FileChooserButton in folder mode – can refilter the model, making
-     * the original active item pointer invalid. This seems ugly and makes some
-     * of the above code pointless in such cases, so hopefully we can FIXME. */
-    active = gtk_menu_get_active (GTK_MENU (priv->popup_widget));
-    if (active && gtk_widget_get_visible (active))
-      gtk_menu_shell_select_item (GTK_MENU_SHELL (priv->popup_widget), active);
+#endif
 }
 
 /**
@@ -1470,7 +1415,7 @@ gtk_combo_box_popdown (GtkComboBox *combo_box)
 
   g_return_if_fail (GTK_IS_COMBO_BOX (combo_box));
 
-  gtk_menu_popdown (GTK_MENU (priv->popup_widget));
+  gtk_popover_popdown (GTK_POPOVER (priv->popup_widget));
 }
 
 static void
@@ -2045,7 +1990,7 @@ gtk_combo_box_set_active_internal (GtkComboBox *combo_box,
 
   if (!path)
     {
-      gtk_menu_set_active (GTK_MENU (priv->popup_widget), -1);
+      gtk_tree_popover_set_active (GTK_TREE_POPOVER (priv->popup_widget), -1);
 
       if (priv->cell_view)
         gtk_cell_view_set_displayed_row (GTK_CELL_VIEW (priv->cell_view), NULL);
@@ -2062,13 +2007,11 @@ gtk_combo_box_set_active_internal (GtkComboBox *combo_box,
       priv->active_row =
         gtk_tree_row_reference_new (priv->model, path);
 
-      /* FIXME handle nested menus better */
-      gtk_menu_set_active (GTK_MENU (priv->popup_widget),
-                           gtk_tree_path_get_indices (path)[0]);
+      gtk_tree_popover_set_active (GTK_TREE_POPOVER (priv->popup_widget),
+                                   gtk_tree_path_get_indices (path)[0]);
 
       if (priv->cell_view)
-        gtk_cell_view_set_displayed_row (GTK_CELL_VIEW (priv->cell_view),
-                                         path);
+        gtk_cell_view_set_displayed_row (GTK_CELL_VIEW (priv->cell_view), path);
     }
 
   g_signal_emit (combo_box, combo_box_signals[CHANGED], 0);
@@ -2176,8 +2119,7 @@ gtk_combo_box_set_model (GtkComboBox  *combo_box,
                     G_CALLBACK (gtk_combo_box_model_row_changed),
                     combo_box);
 
-  _gtk_tree_menu_set_model (GTK_TREE_MENU (priv->popup_widget),
-                            priv->model);
+  gtk_tree_popover_set_model (GTK_TREE_POPOVER (priv->popup_widget), priv->model);
 
   if (priv->cell_view)
     gtk_cell_view_set_model (GTK_CELL_VIEW (priv->cell_view),
@@ -2499,8 +2441,7 @@ gtk_combo_box_dispose (GObject* object)
       g_signal_handlers_disconnect_by_func (priv->popup_widget,
                                             gtk_combo_box_menu_hide,
                                             combo_box);
-      gtk_menu_detach (GTK_MENU (priv->popup_widget));
-      priv->popup_widget = NULL;
+      g_clear_pointer (&priv->popup_widget, gtk_widget_unparent);
     }
 
   gtk_combo_box_unset_model (combo_box);
@@ -2681,9 +2622,9 @@ gtk_combo_box_set_row_separator_func (GtkComboBox                 *combo_box,
   priv->row_separator_data = data;
   priv->row_separator_destroy = destroy;
 
-  /* Make the TreeMenu rebuild itself using the new separator func */
-  _gtk_tree_menu_set_model (GTK_TREE_MENU (priv->popup_widget), NULL);
-  _gtk_tree_menu_set_model (GTK_TREE_MENU (priv->popup_widget), priv->model);
+  gtk_tree_popover_set_row_separator_func (GTK_TREE_POPOVER (priv->popup_widget),
+                                           (GtkTreeViewRowSeparatorFunc)gtk_combo_box_row_separator_func,
+                                           combo_box, NULL);
 
   gtk_widget_queue_draw (GTK_WIDGET (combo_box));
 }
diff --git a/gtk/gtktreemenu.c b/gtk/gtktreemenu.c
deleted file mode 100644 (file)
index c216ce0..0000000
+++ /dev/null
@@ -1,1367 +0,0 @@
-/* gtktreemenu.c
- *
- * Copyright (C) 2010 Openismus GmbH
- *
- * Authors:
- *      Tristan Van Berkom <tristanvb@openismus.com>
- *
- * Based on some GtkComboBox menu code by Kristian Rietveld <kris@gtk.org>
- *
- * 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
- * License along with this library. If not, see <http://www.gnu.org/licenses/>.
- */
-
-/*
- * SECTION:gtktreemenu
- * @Short_Description: A GtkMenu automatically created from a #GtkTreeModel
- * @Title: GtkTreeMenu
- *
- * The #GtkTreeMenu is used to display a drop-down menu allowing selection
- * of every row in the model and is used by the #GtkComboBox for its drop-down
- * menu.
- */
-
-#include "config.h"
-#include "gtkintl.h"
-#include "gtktreemenu.h"
-#include "gtkmarshalers.h"
-#include "gtkmenuitem.h"
-#include "gtkseparatormenuitem.h"
-#include "gtkcellareabox.h"
-#include "gtkcellareacontext.h"
-#include "gtkcelllayout.h"
-#include "gtkcellview.h"
-#include "gtkmenushellprivate.h"
-#include "gtkprivate.h"
-
-/* GObjectClass */
-static void      gtk_tree_menu_constructed                    (GObject            *object);
-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,
-                                                               guint               prop_id,
-                                                               const GValue       *value,
-                                                               GParamSpec         *pspec);
-static void      gtk_tree_menu_get_property                   (GObject            *object,
-                                                               guint               prop_id,
-                                                               GValue             *value,
-                                                               GParamSpec         *pspec);
-
-/* GtkWidgetClass */
-static void gtk_tree_menu_measure (GtkWidget      *widget,
-                                   GtkOrientation  orientation,
-                                   int             for_size,
-                                   int            *minimum,
-                                   int            *natural,
-                                   int            *minimum_baseline,
-                                   int            *natural_baseline);
-
-/* 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 */
-static inline void rebuild_menu                               (GtkTreeMenu          *menu);
-static void       gtk_tree_menu_populate                      (GtkTreeMenu          *menu);
-static GtkWidget *gtk_tree_menu_create_item                   (GtkTreeMenu          *menu,
-                                                               GtkTreeIter          *iter,
-                                                               gboolean              header_item);
-static void       gtk_tree_menu_create_submenu                (GtkTreeMenu          *menu,
-                                                               GtkWidget            *item,
-                                                               GtkTreePath          *path);
-static void       gtk_tree_menu_set_area                      (GtkTreeMenu          *menu,
-                                                               GtkCellArea          *area);
-static GtkWidget *gtk_tree_menu_get_path_item                 (GtkTreeMenu          *menu,
-                                                               GtkTreePath          *path);
-static gboolean   gtk_tree_menu_path_in_menu                  (GtkTreeMenu          *menu,
-                                                               GtkTreePath          *path,
-                                                               gboolean             *header_item);
-static void       row_inserted_cb                             (GtkTreeModel         *model,
-                                                               GtkTreePath          *path,
-                                                               GtkTreeIter          *iter,
-                                                               GtkTreeMenu          *menu);
-static void       row_deleted_cb                              (GtkTreeModel         *model,
-                                                               GtkTreePath          *path,
-                                                               GtkTreeMenu          *menu);
-static void       row_reordered_cb                            (GtkTreeModel         *model,
-                                                               GtkTreePath          *path,
-                                                               GtkTreeIter          *iter,
-                                                               gint                 *new_order,
-                                                               GtkTreeMenu          *menu);
-static void       row_changed_cb                              (GtkTreeModel         *model,
-                                                               GtkTreePath          *path,
-                                                               GtkTreeIter          *iter,
-                                                               GtkTreeMenu          *menu);
-static void       context_size_changed_cb                     (GtkCellAreaContext   *context,
-                                                               GParamSpec           *pspec,
-                                                               GtkWidget            *menu);
-static void       area_apply_attributes_cb                    (GtkCellArea          *area,
-                                                               GtkTreeModel         *tree_model,
-                                                               GtkTreeIter          *iter,
-                                                               gboolean              is_expander,
-                                                               gboolean              is_expanded,
-                                                               GtkTreeMenu          *menu);
-static void       item_activated_cb                           (GtkMenuItem          *item,
-                                                               GtkTreeMenu          *menu);
-static void       submenu_activated_cb                        (GtkTreeMenu          *submenu,
-                                                               const gchar          *path,
-                                                               GtkTreeMenu          *menu);
-static void       gtk_tree_menu_set_model_internal            (GtkTreeMenu          *menu,
-                                                               GtkTreeModel         *model);
-
-
-
-struct _GtkTreeMenuPrivate
-{
-  /* TreeModel and parent for this menu */
-  GtkTreeModel        *model;
-  GtkTreeRowReference *root;
-
-  /* CellArea and context for this menu */
-  GtkCellArea         *area;
-  GtkCellAreaContext  *context;
-
-  /* Signals */
-  gulong               size_changed_id;
-  gulong               apply_attributes_id;
-  gulong               row_inserted_id;
-  gulong               row_deleted_id;
-  gulong               row_reordered_id;
-  gulong               row_changed_id;
-
-  /* Flags */
-  guint32              menu_with_header : 1;
-
-  /* Row separators */
-  GtkTreeViewRowSeparatorFunc row_separator_func;
-  gpointer                    row_separator_data;
-  GDestroyNotify              row_separator_destroy;
-};
-
-enum {
-  PROP_0,
-  PROP_MODEL,
-  PROP_ROOT,
-  PROP_CELL_AREA
-};
-
-enum {
-  SIGNAL_MENU_ACTIVATE,
-  N_SIGNALS
-};
-
-static guint   tree_menu_signals[N_SIGNALS] = { 0 };
-static GQuark  tree_menu_path_quark = 0;
-
-G_DEFINE_TYPE_WITH_CODE (GtkTreeMenu, _gtk_tree_menu, GTK_TYPE_MENU,
-                         G_ADD_PRIVATE (GtkTreeMenu)
-                         G_IMPLEMENT_INTERFACE (GTK_TYPE_CELL_LAYOUT,
-                                                gtk_tree_menu_cell_layout_init));
-
-static void
-_gtk_tree_menu_init (GtkTreeMenu *menu)
-{
-  menu->priv = _gtk_tree_menu_get_instance_private (menu);
-
-  gtk_menu_set_reserve_toggle_size (GTK_MENU (menu), FALSE);
-}
-
-static void
-_gtk_tree_menu_class_init (GtkTreeMenuClass *class)
-{
-  GObjectClass   *object_class = G_OBJECT_CLASS (class);
-  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
-
-  tree_menu_path_quark = g_quark_from_static_string ("gtk-tree-menu-path");
-
-  object_class->constructed  = gtk_tree_menu_constructed;
-  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;
-
-  widget_class->measure = gtk_tree_menu_measure;
-
-  /*
-   * 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.
-   */
-  tree_menu_signals[SIGNAL_MENU_ACTIVATE] =
-    g_signal_new (I_("menu-activate"),
-                  G_OBJECT_CLASS_TYPE (object_class),
-                  G_SIGNAL_RUN_FIRST,
-                  0, /* No class closure here */
-                  NULL, NULL,
-                  NULL,
-                  G_TYPE_NONE, 1, G_TYPE_STRING);
-
-  /*
-   * GtkTreeMenu:model:
-   *
-   * The #GtkTreeModel from which the menu is constructed.
-   */
-  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));
-
-  /*
-   * 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
-   * to build a menu for a given #GtkTreePath and its children.
-   */
-  g_object_class_install_property (object_class,
-                                   PROP_ROOT,
-                                   g_param_spec_boxed ("root",
-                                                       P_("TreeMenu root row"),
-                                                       P_("The TreeMenu will display children of the "
-                                                          "specified root"),
-                                                       GTK_TYPE_TREE_PATH,
-                                                       GTK_PARAM_READWRITE));
-
-  /*
-   * 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.
-   */
-  g_object_class_install_property (object_class,
-                                   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));
-}
-
-/****************************************************************
- *                         GObjectClass                         *
- ****************************************************************/
-static void
-gtk_tree_menu_constructed (GObject *object)
-{
-  GtkTreeMenu *menu = GTK_TREE_MENU (object);
-  GtkTreeMenuPrivate *priv = menu->priv;
-
-  G_OBJECT_CLASS (_gtk_tree_menu_parent_class)->constructed (object);
-
-  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);
-
-  priv->size_changed_id =
-    g_signal_connect (priv->context, "notify",
-                      G_CALLBACK (context_size_changed_cb), menu);
-}
-
-static void
-gtk_tree_menu_dispose (GObject *object)
-{
-  GtkTreeMenu        *menu;
-  GtkTreeMenuPrivate *priv;
-
-  menu = GTK_TREE_MENU (object);
-  priv = menu->priv;
-
-  _gtk_tree_menu_set_model (menu, NULL);
-  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;
-    }
-
-  G_OBJECT_CLASS (_gtk_tree_menu_parent_class)->dispose (object);
-}
-
-static void
-gtk_tree_menu_finalize (GObject *object)
-{
-  GtkTreeMenu        *menu;
-  GtkTreeMenuPrivate *priv;
-
-  menu = GTK_TREE_MENU (object);
-  priv = menu->priv;
-
-  _gtk_tree_menu_set_row_separator_func (menu, NULL, NULL, NULL);
-
-  if (priv->root)
-    gtk_tree_row_reference_free (priv->root);
-
-  G_OBJECT_CLASS (_gtk_tree_menu_parent_class)->finalize (object);
-}
-
-static void
-gtk_tree_menu_set_property (GObject            *object,
-                            guint               prop_id,
-                            const GValue       *value,
-                            GParamSpec         *pspec)
-{
-  GtkTreeMenu *menu = GTK_TREE_MENU (object);
-
-  switch (prop_id)
-    {
-    case PROP_MODEL:
-      _gtk_tree_menu_set_model (menu, g_value_get_object (value));
-      break;
-
-    case PROP_ROOT:
-      _gtk_tree_menu_set_root (menu, g_value_get_boxed (value));
-      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;
-
-    default:
-      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
-      break;
-    }
-}
-
-static void
-gtk_tree_menu_get_property (GObject            *object,
-                            guint               prop_id,
-                            GValue             *value,
-                            GParamSpec         *pspec)
-{
-  GtkTreeMenu        *menu = GTK_TREE_MENU (object);
-  GtkTreeMenuPrivate *priv = menu->priv;
-
-  switch (prop_id)
-    {
-    case PROP_MODEL:
-      g_value_set_object (value, priv->model);
-      break;
-
-    case PROP_ROOT:
-      g_value_set_boxed (value, priv->root);
-      break;
-
-    case PROP_CELL_AREA:
-      g_value_set_object (value, priv->area);
-      break;
-
-    default:
-      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
-      break;
-    }
-}
-
-/****************************************************************
- *                         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)
-        {
-          has_submenu = TRUE;
-          break;
-        }
-    }
-
-  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_measure (GtkWidget      *widget,
-                       GtkOrientation  orientation,
-                       int             for_size,
-                       int            *minimum,
-                       int            *natural,
-                       int            *minimum_baseline,
-                       int            *natural_baseline)
-{
-  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)->measure (widget,
-                                                           orientation,
-                                                           for_size,
-                                                           minimum, natural,
-                                                           minimum_baseline, natural_baseline);
-
-  g_signal_handler_unblock (priv->context, priv->size_changed_id);
-}
-
-
-/****************************************************************
- *                      GtkCellLayoutIface                      *
- ****************************************************************/
-static void
-gtk_tree_menu_cell_layout_init (GtkCellLayoutIface  *iface)
-{
-  iface->get_area = gtk_tree_menu_cell_layout_get_area;
-}
-
-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             *
- ****************************************************************/
-static GtkWidget *
-gtk_tree_menu_get_path_item (GtkTreeMenu          *menu,
-                             GtkTreePath          *search)
-{
-  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))
-        {
-          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)
-                /* Return any first child where its row-reference became invalid,
-                 * this is because row-references get null paths before we recieve
-                 * the "row-deleted" signal.
-                 */
-                item = child;
-            }
-        }
-      else
-        {
-          GtkWidget *view = gtk_bin_get_child (GTK_BIN (child));
-
-          /* It's always a cellview */
-          if (GTK_IS_CELL_VIEW (view))
-            path = gtk_cell_view_get_displayed_row (GTK_CELL_VIEW (view));
-
-          if (!path)
-            /* Return any first child where its row-reference became invalid,
-             * this is because row-references get null paths before we recieve
-             * the "row-deleted" signal.
-             */
-            item = child;
-        }
-
-      if (path)
-        {
-          if (gtk_tree_path_compare (search, path) == 0)
-            item = child;
-
-          gtk_tree_path_free (path);
-        }
-    }
-
-  g_list_free (children);
-
-  return item;
-}
-
-static gboolean
-gtk_tree_menu_path_in_menu (GtkTreeMenu  *menu,
-                            GtkTreePath  *path,
-                            gboolean     *header_item)
-{
-  GtkTreeMenuPrivate *priv = menu->priv;
-  gboolean            in_menu = FALSE;
-  gboolean            is_header = FALSE;
-
-  /* Check if the is in root of the model */
-  if (gtk_tree_path_get_depth (path) == 1 && !priv->root)
-    in_menu = TRUE;
-  /* If we are a submenu, compare the parent path */
-  else if (priv->root)
-    {
-      GtkTreePath *root_path   = gtk_tree_row_reference_get_path (priv->root);
-      GtkTreePath *search_path = gtk_tree_path_copy (path);
-
-      if (root_path)
-        {
-          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;
-            }
-        }
-      gtk_tree_path_free (root_path);
-      gtk_tree_path_free (search_path);
-    }
-
-  if (header_item)
-    *header_item = is_header;
-
-  return in_menu;
-}
-
-static GtkWidget *
-gtk_tree_menu_path_needs_submenu (GtkTreeMenu *menu,
-                                  GtkTreePath *search)
-{
-  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 */
-      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));
-
-          /* It's always a cellview */
-          if (GTK_IS_CELL_VIEW (view))
-            path = gtk_cell_view_get_displayed_row (GTK_CELL_VIEW (view));
-        }
-
-      if (path)
-        {
-          if (gtk_tree_path_compare (parent_path, path) == 0)
-            item = child;
-
-          gtk_tree_path_free (path);
-        }
-    }
-
-  g_list_free (children);
-  gtk_tree_path_free (parent_path);
-
-  return item;
-}
-
-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))
-        {
-          GtkWidget *view = gtk_bin_get_child (GTK_BIN (child));
-
-          /* It's always a cellview */
-          if (GTK_IS_CELL_VIEW (view))
-            path = gtk_cell_view_get_displayed_row (GTK_CELL_VIEW (view));
-        }
-
-      if (path)
-        {
-          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));
-
-          gtk_tree_path_free (path);
-        }
-    }
-
-  g_list_free (children);
-
-  return submenu;
-}
-
-static void
-row_inserted_cb (GtkTreeModel     *model,
-                 GtkTreePath      *path,
-                 GtkTreeIter      *iter,
-                 GtkTreeMenu      *menu)
-{
-  GtkTreeMenuPrivate *priv = menu->priv;
-  gint               *indices, index, depth;
-  GtkWidget          *item;
-
-  /* If the iter should be in this menu then go ahead and insert it */
-  if (gtk_tree_menu_path_in_menu (menu, path, NULL))
-    {
-      /* 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];
-
-      /* Menus with a header include a menuitem for its root node
-       * and a separator menu item */
-      if (priv->menu_with_header)
-        index += 2;
-
-      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);
-    }
-  else
-    {
-      /* Create submenus for iters if we need to */
-      item = gtk_tree_menu_path_needs_submenu (menu, path);
-      if (item)
-        {
-          GtkTreePath *item_path = gtk_tree_path_copy (path);
-
-          gtk_tree_path_up (item_path);
-          gtk_tree_menu_create_submenu (menu, item, item_path);
-          gtk_tree_path_free (item_path);
-        }
-    }
-}
-
-static void
-row_deleted_cb (GtkTreeModel     *model,
-                GtkTreePath      *path,
-                GtkTreeMenu      *menu)
-{
-  GtkWidget          *item;
-
-  /* If it's the header item we leave it to the parent menu
-   * to remove us from its menu
-   */
-  item = gtk_tree_menu_get_path_item (menu, path);
-
-  if (item)
-    {
-      /* Get rid of the deleted item */
-      gtk_widget_destroy (item);
-
-      /* Resize everything */
-      gtk_cell_area_context_reset (menu->priv->context);
-    }
-  else
-    {
-      /* 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)
-        gtk_widget_destroy (submenu);
-    }
-}
-
-static void
-row_reordered_cb (GtkTreeModel    *model,
-                  GtkTreePath     *path,
-                  GtkTreeIter     *iter,
-                  gint            *new_order,
-                  GtkTreeMenu     *menu)
-{
-  GtkTreeMenuPrivate *priv = menu->priv;
-  gboolean            this_menu = FALSE;
-
-  if (gtk_tree_path_get_depth (path) == 0 && !priv->root)
-    this_menu = TRUE;
-  else if (priv->root)
-    {
-      GtkTreePath *root_path =
-        gtk_tree_row_reference_get_path (priv->root);
-
-      if (gtk_tree_path_compare (root_path, path) == 0)
-        this_menu = TRUE;
-
-      gtk_tree_path_free (root_path);
-    }
-
-  if (this_menu)
-    rebuild_menu (menu);
-}
-
-static gint
-menu_item_position (GtkTreeMenu *menu,
-                    GtkWidget   *item)
-{
-  GList *children;
-  gint   position;
-
-  children = gtk_container_get_children (GTK_CONTAINER (menu));
-  position = g_list_index (children, item);
-  g_list_free (children);
-
-  return position;
-}
-
-static void
-row_changed_cb (GtkTreeModel         *model,
-                GtkTreePath          *path,
-                GtkTreeIter          *iter,
-                GtkTreeMenu          *menu)
-{
-  GtkTreeMenuPrivate *priv = menu->priv;
-  gboolean            is_separator = FALSE;
-  GtkWidget          *item;
-  GList *children;
-
-  item = gtk_tree_menu_get_path_item (menu, path);
-
-  if (priv->root)
-    {
-      GtkTreePath *root_path =
-        gtk_tree_row_reference_get_path (priv->root);
-
-      if (root_path && gtk_tree_path_compare (root_path, path) == 0)
-        {
-          if (item)
-            {
-              /* Destroy the header item and then the following separator */
-              gtk_widget_destroy (item);
-              children = gtk_menu_shell_get_items (GTK_MENU_SHELL (menu));
-              gtk_widget_destroy (children->data);
-              g_list_free (children);
-
-              priv->menu_with_header = FALSE;
-            }
-
-          gtk_tree_path_free (root_path);
-        }
-    }
-
-  if (item)
-    {
-      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);
-        }
-    }
-}
-
-static void
-context_size_changed_cb (GtkCellAreaContext  *context,
-                         GParamSpec          *pspec,
-                         GtkWidget           *menu)
-{
-  if (!strcmp (pspec->name, "minimum-width") ||
-      !strcmp (pspec->name, "natural-width") ||
-      !strcmp (pspec->name, "minimum-height") ||
-      !strcmp (pspec->name, "natural-height"))
-    gtk_widget_queue_resize (menu);
-}
-
-static gboolean
-area_is_sensitive (GtkCellArea *area)
-{
-  GList    *cells, *list;
-  gboolean  sensitive = FALSE;
-
-  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);
-
-      if (sensitive)
-        break;
-    }
-  g_list_free (cells);
-
-  return sensitive;
-}
-
-static void
-area_apply_attributes_cb (GtkCellArea          *area,
-                          GtkTreeModel         *tree_model,
-                          GtkTreeIter          *iter,
-                          gboolean              is_expander,
-                          gboolean              is_expanded,
-                          GtkTreeMenu          *menu)
-{
-  /* If the menu for this iter has a submenu */
-  GtkTreeMenuPrivate *priv = menu->priv;
-  GtkTreePath        *path;
-  GtkWidget          *item;
-  gboolean            is_header;
-  gboolean            sensitive;
-  GList *children;
-
-  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 */
-      if (item && !gtk_menu_item_get_submenu (GTK_MENU_ITEM (item)))
-        {
-          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
-               */
-              children = gtk_menu_shell_get_items (GTK_MENU_SHELL (menu));
-              if (children && children->next)
-                {
-                  GtkWidget *separator = children->next->data;
-
-                  gtk_widget_set_sensitive (separator, sensitive);
-                }
-              g_list_free (children);
-            }
-        }
-    }
-
-  gtk_tree_path_free (path);
-}
-
-static void
-gtk_tree_menu_set_area (GtkTreeMenu *menu,
-                        GtkCellArea *area)
-{
-  GtkTreeMenuPrivate *priv = menu->priv;
-
-  if (priv->area)
-    {
-      g_signal_handler_disconnect (priv->area,
-                                   priv->apply_attributes_id);
-      priv->apply_attributes_id = 0;
-
-      g_object_unref (priv->area);
-    }
-
-  priv->area = area;
-
-  if (priv->area)
-    {
-      g_object_ref_sink (priv->area);
-
-      priv->apply_attributes_id =
-        g_signal_connect (priv->area, "apply-attributes",
-                          G_CALLBACK (area_apply_attributes_cb), menu);
-    }
-}
-
-static void
-gtk_tree_menu_create_submenu (GtkTreeMenu *menu,
-                              GtkWidget   *item,
-                              GtkTreePath *path)
-{
-  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);
-
-  submenu = _gtk_tree_menu_new_with_area (priv->area);
-
-  _gtk_tree_menu_set_row_separator_func (GTK_TREE_MENU (submenu),
-                                         priv->row_separator_func,
-                                         priv->row_separator_data,
-                                         priv->row_separator_destroy);
-
-  gtk_tree_menu_set_model_internal (GTK_TREE_MENU (submenu), priv->model);
-  _gtk_tree_menu_set_root (GTK_TREE_MENU (submenu), path);
-  gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), submenu);
-
-  g_signal_connect (submenu, "menu-activate",
-                    G_CALLBACK (submenu_activated_cb), menu);
-}
-
-static GtkWidget *
-gtk_tree_menu_create_item (GtkTreeMenu *menu,
-                           GtkTreeIter *iter,
-                           gboolean     header_item)
-{
-  GtkTreeMenuPrivate *priv = menu->priv;
-  GtkWidget          *item, *view;
-  GtkTreePath        *path;
-  gboolean            is_separator = FALSE;
-
-  path = gtk_tree_model_get_path (priv->model, iter);
-
-  if (priv->row_separator_func)
-    is_separator =
-      priv->row_separator_func (priv->model, iter,
-                                priv->row_separator_data);
-
-  if (is_separator)
-    {
-      item = gtk_separator_menu_item_new ();
-
-      g_object_set_qdata_full (G_OBJECT (item),
-                               tree_menu_path_quark,
-                               gtk_tree_row_reference_new (priv->model, path),
-                               (GDestroyNotify)gtk_tree_row_reference_free);
-    }
-  else
-    {
-      view = gtk_cell_view_new_with_context (priv->area, priv->context);
-      item = gtk_menu_item_new ();
-
-      gtk_cell_view_set_model (GTK_CELL_VIEW (view), priv->model);
-      gtk_cell_view_set_displayed_row (GTK_CELL_VIEW (view), path);
-
-      gtk_container_add (GTK_CONTAINER (item), view);
-
-      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 &&
-          gtk_tree_model_iter_has_child (priv->model, iter))
-        gtk_tree_menu_create_submenu (menu, item, path);
-    }
-
-  gtk_tree_path_free (path);
-
-  return item;
-}
-
-static inline void
-rebuild_menu (GtkTreeMenu *menu)
-{
-  GtkTreeMenuPrivate *priv = menu->priv;
-
-  /* Destroy all the menu items */
-  gtk_container_foreach (GTK_CONTAINER (menu),
-                         (GtkCallback) gtk_widget_destroy, NULL);
-
-  /* Populate */
-  if (priv->model)
-    gtk_tree_menu_populate (menu);
-}
-
-
-static void
-gtk_tree_menu_populate (GtkTreeMenu *menu)
-{
-  GtkTreeMenuPrivate *priv = menu->priv;
-  GtkTreePath        *path = NULL;
-  GtkTreeIter         parent;
-  GtkTreeIter         iter;
-  gboolean            valid = FALSE;
-  GtkWidget          *menu_item;
-
-  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))
-        valid = gtk_tree_model_iter_children (priv->model, &iter, &parent);
-
-      gtk_tree_path_free (path);
-    }
-  else
-    {
-      valid = gtk_tree_model_iter_children (priv->model, &iter, NULL);
-    }
-
-  /* Create a menu item for every row at the current depth, add a GtkTreeMenu
-   * submenu for iters/items that have children */
-  while (valid)
-    {
-      menu_item = gtk_tree_menu_create_item (menu, &iter, FALSE);
-
-      gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);
-
-      valid = gtk_tree_model_iter_next (priv->model, &iter);
-    }
-}
-
-static void
-item_activated_cb (GtkMenuItem          *item,
-                   GtkTreeMenu          *menu)
-{
-  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);
-
-      g_signal_emit (menu, tree_menu_signals[SIGNAL_MENU_ACTIVATE], 0, path_str);
-
-      g_free (path_str);
-      gtk_tree_path_free (path);
-    }
-}
-
-static void
-submenu_activated_cb (GtkTreeMenu          *submenu,
-                      const gchar          *path,
-                      GtkTreeMenu          *menu)
-{
-  g_signal_emit (menu, tree_menu_signals[SIGNAL_MENU_ACTIVATE], 0, path);
-}
-
-/* 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,
-                                  GtkTreeModel *model)
-{
-  GtkTreeMenuPrivate *priv;
-
-  priv = menu->priv;
-
-  if (priv->model != model)
-    {
-      if (priv->model)
-        {
-          /* 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);
-        }
-
-      priv->model = model;
-
-      if (priv->model)
-        {
-          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);
-        }
-    }
-}
-
-/****************************************************************
- *                            API                               *
- ****************************************************************/
-
-/**
- * _gtk_tree_menu_new:
- *
- * Creates a new #GtkTreeMenu.
- *
- * Returns: A newly created #GtkTreeMenu with no model or root.
- */
-GtkWidget *
-_gtk_tree_menu_new (void)
-{
-  return (GtkWidget *)g_object_new (GTK_TYPE_TREE_MENU, NULL);
-}
-
-/*
- * _gtk_tree_menu_new_with_area:
- * @area: the #GtkCellArea to use to render cells in the menu
- *
- * Creates a new #GtkTreeMenu using @area to render its cells.
- *
- * Returns: A newly created #GtkTreeMenu with no model or root.
- */
-GtkWidget *
-_gtk_tree_menu_new_with_area (GtkCellArea    *area)
-{
-  return (GtkWidget *)g_object_new (GTK_TYPE_TREE_MENU,
-                                    "cell-area", area,
-                                    NULL);
-}
-
-/*
- * _gtk_tree_menu_new_full:
- * @area: (allow-none): the #GtkCellArea to use to render cells in the menu, or %NULL.
- * @model: (allow-none): the #GtkTreeModel to build the menu hierarchy from, or %NULL.
- * @root: (allow-none): the #GtkTreePath indicating the root row for this menu, or %NULL.
- *
- * Creates a new #GtkTreeMenu hierarchy from the provided @model and @root using @area to render its cells.
- *
- * Returns: A newly created #GtkTreeMenu.
- */
-GtkWidget *
-_gtk_tree_menu_new_full (GtkCellArea         *area,
-                         GtkTreeModel        *model,
-                         GtkTreePath         *root)
-{
-  return (GtkWidget *)g_object_new (GTK_TYPE_TREE_MENU,
-                                    "cell-area", area,
-                                    "model", model,
-                                    "root", root,
-                                    NULL);
-}
-
-/*
- * _gtk_tree_menu_set_model:
- * @menu: a #GtkTreeMenu
- * @model: (allow-none): the #GtkTreeModel to build the menu hierarchy from, or %NULL.
- *
- * Sets @model to be used to build the menu hierarhcy.
- */
-void
-_gtk_tree_menu_set_model (GtkTreeMenu  *menu,
-                          GtkTreeModel *model)
-{
-  g_return_if_fail (GTK_IS_TREE_MENU (menu));
-  g_return_if_fail (model == NULL || GTK_IS_TREE_MODEL (model));
-
-  gtk_tree_menu_set_model_internal (menu, model);
-
-  rebuild_menu (menu);
-}
-
-/*
- * _gtk_tree_menu_get_model:
- * @menu: a #GtkTreeMenu
- *
- * Gets the @model currently used for the menu hierarhcy.
- *
- * Returns: (transfer none): the #GtkTreeModel which is used
- * for @menu’s hierarchy.
- */
-GtkTreeModel *
-_gtk_tree_menu_get_model (GtkTreeMenu *menu)
-{
-  GtkTreeMenuPrivate *priv;
-
-  g_return_val_if_fail (GTK_IS_TREE_MENU (menu), NULL);
-
-  priv = menu->priv;
-
-  return priv->model;
-}
-
-/*
- * _gtk_tree_menu_set_root:
- * @menu: a #GtkTreeMenu
- * @path: (allow-none): the #GtkTreePath which is the root of @menu, or %NULL.
- *
- * Sets the root of a @menu’s hierarchy to be @path. @menu must already
- * have a model set and @path must point to a valid path inside the model.
- */
-void
-_gtk_tree_menu_set_root (GtkTreeMenu *menu,
-                         GtkTreePath *path)
-{
-  GtkTreeMenuPrivate *priv;
-
-  g_return_if_fail (GTK_IS_TREE_MENU (menu));
-  g_return_if_fail (menu->priv->model != NULL || path == NULL);
-
-  priv = menu->priv;
-
-  if (priv->root)
-    gtk_tree_row_reference_free (priv->root);
-
-  if (path)
-    priv->root = gtk_tree_row_reference_new (priv->model, path);
-  else
-    priv->root = NULL;
-
-  rebuild_menu (menu);
-}
-
-/*
- * _gtk_tree_menu_get_root:
- * @menu: a #GtkTreeMenu
- *
- * Gets the @root path for @menu’s hierarchy, or returns %NULL if @menu
- * has no model or is building a hierarchy for the entire model. *
- *
- * Returns: (transfer full) (allow-none): A newly created #GtkTreePath
- * pointing to the root of @menu which must be freed with gtk_tree_path_free().
- */
-GtkTreePath *
-_gtk_tree_menu_get_root (GtkTreeMenu *menu)
-{
-  GtkTreeMenuPrivate *priv;
-
-  g_return_val_if_fail (GTK_IS_TREE_MENU (menu), NULL);
-
-  priv = menu->priv;
-
-  if (priv->root)
-    return gtk_tree_row_reference_get_path (priv->root);
-
-  return NULL;
-}
-
-/*
- * _gtk_tree_menu_get_row_separator_func:
- * @menu: a #GtkTreeMenu
- *
- * Gets the current #GtkTreeViewRowSeparatorFunc separator function.
- *
- * Returns: the current row separator function.
- */
-GtkTreeViewRowSeparatorFunc
-_gtk_tree_menu_get_row_separator_func (GtkTreeMenu *menu)
-{
-  GtkTreeMenuPrivate *priv;
-
-  g_return_val_if_fail (GTK_IS_TREE_MENU (menu), NULL);
-
-  priv = menu->priv;
-
-  return priv->row_separator_func;
-}
-
-/*
- * _gtk_tree_menu_set_row_separator_func:
- * @menu: a #GtkTreeMenu
- * @func: (allow-none): a #GtkTreeViewRowSeparatorFunc, or %NULL to unset the separator function.
- * @data: (allow-none): user data to pass to @func, or %NULL
- * @destroy: (allow-none): destroy notifier for @data, or %NULL
- *
- * Sets the row separator function, which is used to determine
- * whether a row should be drawn as a separator. If the row separator
- * function is %NULL, no separators are drawn. This is the default value.
- */
-void
-_gtk_tree_menu_set_row_separator_func (GtkTreeMenu          *menu,
-                                       GtkTreeViewRowSeparatorFunc func,
-                                       gpointer              data,
-                                       GDestroyNotify        destroy)
-{
-  GtkTreeMenuPrivate *priv;
-
-  g_return_if_fail (GTK_IS_TREE_MENU (menu));
-
-  priv = menu->priv;
-
-  if (priv->row_separator_destroy)
-    priv->row_separator_destroy (priv->row_separator_data);
-
-  priv->row_separator_func    = func;
-  priv->row_separator_data    = data;
-  priv->row_separator_destroy = destroy;
-
-  rebuild_menu (menu);
-}
diff --git a/gtk/gtktreemenu.h b/gtk/gtktreemenu.h
deleted file mode 100644 (file)
index 360a28d..0000000
+++ /dev/null
@@ -1,100 +0,0 @@
-/* gtktreemenu.h
- *
- * Copyright (C) 2010 Openismus GmbH
- *
- * Authors:
- *      Tristan Van Berkom <tristanvb@openismus.com>
- *
- * 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
- * License along with this library. If not, see <http://www.gnu.org/licenses/>.
- */
-
-#ifndef __GTK_TREE_MENU_H__
-#define __GTK_TREE_MENU_H__
-
-#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
-#error "Only <gtk/gtk.h> can be included directly."
-#endif
-
-#include <gtk/gtkmenuprivate.h>
-#include <gtk/gtktreemodel.h>
-#include <gtk/gtktreeview.h>
-#include <gtk/gtkcellarea.h>
-
-G_BEGIN_DECLS
-
-#define GTK_TYPE_TREE_MENU               (_gtk_tree_menu_get_type ())
-#define GTK_TREE_MENU(obj)               (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_TREE_MENU, GtkTreeMenu))
-#define GTK_TREE_MENU_CLASS(klass)       (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_TREE_MENU, GtkTreeMenuClass))
-#define GTK_IS_TREE_MENU(obj)    (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_TREE_MENU))
-#define GTK_IS_TREE_MENU_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_TREE_MENU))
-#define GTK_TREE_MENU_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_TREE_MENU, GtkTreeMenuClass))
-
-typedef struct _GtkTreeMenu              GtkTreeMenu;
-typedef struct _GtkTreeMenuClass         GtkTreeMenuClass;
-typedef struct _GtkTreeMenuPrivate       GtkTreeMenuPrivate;
-
-struct _GtkTreeMenu
-{
-  GtkMenu parent_instance;
-
-  /*< private >*/
-  GtkTreeMenuPrivate *priv;
-};
-
-struct _GtkTreeMenuClass
-{
-  GtkMenuClass parent_class;
-
-  /*< private >*/
-  /* Padding for future expansion */
-  void (*_gtk_reserved1) (void);
-  void (*_gtk_reserved2) (void);
-  void (*_gtk_reserved3) (void);
-  void (*_gtk_reserved4) (void);
-  void (*_gtk_reserved5) (void);
-  void (*_gtk_reserved6) (void);
-};
-
-GType                 _gtk_tree_menu_get_type                       (void) G_GNUC_CONST;
-
-GtkWidget            *_gtk_tree_menu_new                            (void);
-GtkWidget            *_gtk_tree_menu_new_with_area                  (GtkCellArea         *area);
-GtkWidget            *_gtk_tree_menu_new_full                       (GtkCellArea         *area,
-                                                                     GtkTreeModel        *model,
-                                                                     GtkTreePath         *root);
-void                  _gtk_tree_menu_set_model                      (GtkTreeMenu         *menu,
-                                                                     GtkTreeModel        *model);
-GtkTreeModel         *_gtk_tree_menu_get_model                      (GtkTreeMenu         *menu);
-void                  _gtk_tree_menu_set_root                       (GtkTreeMenu         *menu,
-                                                                     GtkTreePath         *path);
-GtkTreePath          *_gtk_tree_menu_get_root                       (GtkTreeMenu         *menu);
-gint                  _gtk_tree_menu_get_wrap_width                 (GtkTreeMenu         *menu);
-void                  _gtk_tree_menu_set_wrap_width                 (GtkTreeMenu         *menu,
-                                                                     gint                 width);
-gint                  _gtk_tree_menu_get_row_span_column            (GtkTreeMenu         *menu);
-void                  _gtk_tree_menu_set_row_span_column            (GtkTreeMenu         *menu,
-                                                                     gint                 row_span);
-gint                  _gtk_tree_menu_get_column_span_column         (GtkTreeMenu         *menu);
-void                  _gtk_tree_menu_set_column_span_column         (GtkTreeMenu         *menu,
-                                                                     gint                 column_span);
-
-GtkTreeViewRowSeparatorFunc _gtk_tree_menu_get_row_separator_func   (GtkTreeMenu          *menu);
-void                        _gtk_tree_menu_set_row_separator_func   (GtkTreeMenu          *menu,
-                                                                     GtkTreeViewRowSeparatorFunc func,
-                                                                     gpointer              data,
-                                                                     GDestroyNotify        destroy);
-
-G_END_DECLS
-
-#endif /* __GTK_TREE_MENU_H__ */
diff --git a/gtk/gtktreepopover.c b/gtk/gtktreepopover.c
new file mode 100644 (file)
index 0000000..a9803a7
--- /dev/null
@@ -0,0 +1,868 @@
+/*
+ * Copyright © 2019 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the licence, 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Matthias Clasen
+ */
+
+#include "config.h"
+
+#include "gtktreepopoverprivate.h"
+
+#include "gtktreemodel.h"
+#include "gtkcellarea.h"
+#include "gtkcelllayout.h"
+#include "gtkcellview.h"
+#include "gtkbin.h"
+#include "gtkintl.h"
+#include "gtkprivate.h"
+#include "gtkgizmoprivate.h"
+#include "gtkiconprivate.h"
+
+// TODO
+// positioning + sizing
+
+struct _GtkTreePopover
+{
+  GtkPopover parent_instance;
+
+  GtkTreeModel *model;
+
+  GtkCellArea *area;
+  GtkCellAreaContext *context;
+
+  gulong size_changed_id;
+  gulong row_inserted_id;
+  gulong row_deleted_id;
+  gulong row_changed_id;
+  gulong row_reordered_id;
+  gulong apply_attributes_id;
+
+  GtkTreeViewRowSeparatorFunc row_separator_func;
+  gpointer                    row_separator_data;
+  GDestroyNotify              row_separator_destroy;
+
+  GtkWidget *active_item;
+};
+
+enum {
+  PROP_0,
+  PROP_MODEL,
+  PROP_CELL_AREA,
+
+  NUM_PROPERTIES
+};
+
+enum {
+  MENU_ACTIVATE,
+  NUM_SIGNALS
+};
+
+static guint signals[NUM_SIGNALS];
+
+static void gtk_tree_popover_cell_layout_init (GtkCellLayoutIface  *iface);
+static void gtk_tree_popover_set_area (GtkTreePopover *popover,
+                                       GtkCellArea    *area);
+static void rebuild_menu (GtkTreePopover *popover);
+static void context_size_changed_cb (GtkCellAreaContext *context,
+                                     GParamSpec         *pspec,
+                                     GtkWidget          *popover);
+static GtkWidget * gtk_tree_popover_create_item (GtkTreePopover *popover,
+                                                 GtkTreePath    *path,
+                                                 GtkTreeIter    *iter,
+                                                 gboolean        header_item);
+static GtkWidget * gtk_tree_popover_get_path_item (GtkTreePopover *popover,
+                                                   GtkTreePath    *search);
+static void gtk_tree_popover_set_active_item (GtkTreePopover *popover,
+                                              GtkWidget      *item);
+
+G_DEFINE_TYPE_WITH_CODE (GtkTreePopover, gtk_tree_popover, GTK_TYPE_POPOVER,
+                         G_IMPLEMENT_INTERFACE (GTK_TYPE_CELL_LAYOUT,
+                                                gtk_tree_popover_cell_layout_init));
+
+static void
+gtk_tree_popover_constructed (GObject *object)
+{
+  GtkTreePopover *popover = GTK_TREE_POPOVER (object);
+
+  G_OBJECT_CLASS (gtk_tree_popover_parent_class)->constructed (object);
+
+  if (!popover->area)
+    {
+      GtkCellArea *area = gtk_cell_area_box_new ();
+      gtk_tree_popover_set_area (popover, area);
+    }
+
+  popover->context = gtk_cell_area_create_context (popover->area);
+
+  popover->size_changed_id = g_signal_connect (popover->context, "notify",
+                                               G_CALLBACK (context_size_changed_cb), popover);
+}
+
+static void
+gtk_tree_popover_dispose (GObject *object)
+{
+  GtkTreePopover *popover = GTK_TREE_POPOVER (object);
+
+  gtk_tree_popover_set_model (popover, NULL);
+  gtk_tree_popover_set_area (popover, NULL);
+
+  if (popover->context)
+    {
+      g_signal_handler_disconnect (popover->context, popover->size_changed_id);
+      popover->size_changed_id = 0;
+
+      g_clear_object (&popover->context);
+    }
+
+  G_OBJECT_CLASS (gtk_tree_popover_parent_class)->dispose (object);
+}
+
+static void
+gtk_tree_popover_finalize (GObject *object)
+{
+  GtkTreePopover *popover = GTK_TREE_POPOVER (object);
+
+  if (popover->row_separator_destroy)
+    popover->row_separator_destroy (popover->row_separator_data);
+
+  G_OBJECT_CLASS (gtk_tree_popover_parent_class)->finalize (object);
+}
+
+static void
+gtk_tree_popover_set_property (GObject      *object,
+                               guint         prop_id,
+                               const GValue *value,
+                               GParamSpec   *pspec)
+{
+  GtkTreePopover *popover = GTK_TREE_POPOVER (object);
+
+  switch (prop_id)
+    {
+    case PROP_MODEL:
+      gtk_tree_popover_set_model (popover, g_value_get_object (value));
+      break;
+
+    case PROP_CELL_AREA:
+      gtk_tree_popover_set_area (popover, g_value_get_object (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+gtk_tree_popover_get_property (GObject    *object,
+                               guint       prop_id,
+                               GValue     *value,
+                               GParamSpec *pspec)
+{
+  GtkTreePopover *popover = GTK_TREE_POPOVER (object);
+
+  switch (prop_id)
+    {
+    case PROP_MODEL:
+      g_value_set_object (value, popover->model);
+      break;
+
+    case PROP_CELL_AREA:
+      g_value_set_object (value, popover->area);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+gtk_tree_popover_class_init (GtkTreePopoverClass *class)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+  object_class->constructed  = gtk_tree_popover_constructed;
+  object_class->dispose = gtk_tree_popover_dispose;
+  object_class->finalize = gtk_tree_popover_finalize;
+  object_class->set_property = gtk_tree_popover_set_property;
+  object_class->get_property = gtk_tree_popover_get_property;
+
+  g_object_class_install_property (object_class,
+                                   PROP_MODEL,
+                                   g_param_spec_object ("model",
+                                                        P_("model"),
+                                                        P_("The model for the popover"),
+                                                        GTK_TYPE_TREE_MODEL,
+                                                        GTK_PARAM_READWRITE));
+
+  g_object_class_install_property (object_class,
+                                   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));
+
+  signals[MENU_ACTIVATE] =
+    g_signal_new (I_("menu-activate"),
+                  G_OBJECT_CLASS_TYPE (object_class),
+                  G_SIGNAL_RUN_FIRST,
+                  0,
+                  NULL, NULL,
+                  NULL,
+                  G_TYPE_NONE, 1, G_TYPE_STRING);
+}
+
+static void
+gtk_tree_popover_add_submenu (GtkTreePopover *popover,
+                              GtkWidget      *submenu,
+                              const char     *name)
+{
+  GtkWidget *stack = gtk_bin_get_child (GTK_BIN (popover));
+  gtk_stack_add_named (GTK_STACK (stack), submenu, name);
+}
+
+static GtkWidget *
+gtk_tree_popover_get_submenu (GtkTreePopover *popover,
+                              const char     *name)
+{
+  GtkWidget *stack = gtk_bin_get_child (GTK_BIN (popover));
+
+  return gtk_stack_get_child_by_name (GTK_STACK (stack), name);
+}
+
+void
+gtk_tree_popover_open_submenu (GtkTreePopover *popover,
+                               const char     *name)
+{
+  GtkWidget *stack = gtk_bin_get_child (GTK_BIN (popover));
+  gtk_stack_set_visible_child_name (GTK_STACK (stack), name);
+}
+
+static void
+gtk_tree_popover_init (GtkTreePopover *popover)
+{
+  GtkWidget *stack;
+  GtkStyleContext *style_context;
+
+  stack = gtk_stack_new ();
+  gtk_stack_set_vhomogeneous (GTK_STACK (stack), FALSE);
+  gtk_stack_set_transition_type (GTK_STACK (stack), GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT_RIGHT);
+  gtk_stack_set_interpolate_size (GTK_STACK (stack), TRUE);
+  gtk_container_add (GTK_CONTAINER (popover), stack);
+
+  style_context = gtk_widget_get_style_context (GTK_WIDGET (popover));
+  gtk_style_context_add_class (style_context, GTK_STYLE_CLASS_MENU);
+}
+
+static GtkCellArea *
+gtk_tree_popover_cell_layout_get_area (GtkCellLayout *layout)
+{
+  return GTK_TREE_POPOVER (layout)->area;
+}
+
+static void
+gtk_tree_popover_cell_layout_init (GtkCellLayoutIface  *iface)
+{
+  iface->get_area = gtk_tree_popover_cell_layout_get_area;
+}
+
+static void
+insert_at_position (GtkBox    *box,
+                    GtkWidget *child,
+                    int        position)
+{
+  GtkWidget *sibling = NULL;
+
+  if (position > 0)
+    {
+      int i;
+
+      sibling = gtk_widget_get_first_child (GTK_WIDGET (box));
+      for (i = 1; i < position; i++)
+        sibling = gtk_widget_get_next_sibling (sibling);
+    }
+
+  gtk_box_insert_child_after (box, child, sibling);
+}
+
+static GtkWidget *
+ensure_submenu (GtkTreePopover *popover,
+                GtkTreePath    *path)
+{
+  GtkWidget *box;
+  char *name;
+
+  if (path)
+    name = gtk_tree_path_to_string (path);
+  else
+    name = NULL;
+
+  box = gtk_tree_popover_get_submenu (popover, name ? name : "main");
+  if (!box)
+    {
+      box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
+      gtk_tree_popover_add_submenu (popover, box, name ? name : "main");
+      if (path)
+        {
+          GtkTreeIter iter;
+          GtkWidget *item;
+          gtk_tree_model_get_iter (popover->model, &iter, path);
+          item = gtk_tree_popover_create_item (popover, path, &iter, TRUE);
+          gtk_container_add (GTK_CONTAINER (box), item);
+          gtk_container_add (GTK_CONTAINER (box), gtk_separator_new (GTK_ORIENTATION_HORIZONTAL));
+        }
+
+    }
+
+  g_free (name);
+
+  return box;
+}
+
+static void
+row_inserted_cb (GtkTreeModel   *model,
+                 GtkTreePath    *path,
+                 GtkTreeIter    *iter,
+                 GtkTreePopover *popover)
+{
+  gint *indices, depth, index;
+  GtkWidget *item;
+  GtkWidget *box;
+
+  indices = gtk_tree_path_get_indices (path);
+  depth = gtk_tree_path_get_depth (path);
+  index = indices[depth - 1];
+
+  item = gtk_tree_popover_create_item (popover, path, iter, FALSE);
+  if (depth == 1)
+    {
+      box = ensure_submenu (popover, NULL);
+      insert_at_position (GTK_BOX (box), item, index);
+    }
+  else
+    {
+      GtkTreePath *ppath;
+
+      ppath = gtk_tree_path_copy (path);
+      gtk_tree_path_up (ppath);
+
+      box = ensure_submenu (popover, ppath);
+      insert_at_position (GTK_BOX (box), item, index + 2);
+
+      gtk_tree_path_free (ppath);
+    }
+
+  gtk_cell_area_context_reset (popover->context);
+}
+
+static void
+row_deleted_cb (GtkTreeModel   *model,
+                GtkTreePath    *path,
+                GtkTreePopover *popover)
+{
+  GtkWidget *item;
+
+  item = gtk_tree_popover_get_path_item (popover, path);
+
+  if (item)
+    {
+      gtk_widget_destroy (item);
+      gtk_cell_area_context_reset (popover->context);
+    }
+}
+
+static void
+row_changed_cb (GtkTreeModel   *model,
+                GtkTreePath    *path,
+                GtkTreeIter    *iter,
+                GtkTreePopover *popover)
+{
+  gboolean is_separator = FALSE;
+  GtkWidget *item;
+  gint *indices, depth, index;
+
+  item = gtk_tree_popover_get_path_item (popover, path);
+
+  if (!item)
+    return;
+
+  indices = gtk_tree_path_get_indices (path);
+  depth = gtk_tree_path_get_depth (path);
+  index = indices[depth - 1];
+
+  if (popover->row_separator_func)
+    is_separator = popover->row_separator_func (model, iter, popover->row_separator_data);
+
+  if (is_separator != GTK_IS_SEPARATOR (item))
+    {
+      GtkWidget *box= gtk_widget_get_parent (item);
+
+      gtk_widget_destroy (item);
+
+      item = gtk_tree_popover_create_item (popover, path, iter, FALSE);
+
+      if (depth == 1)
+        insert_at_position (GTK_BOX (box), item, index);
+      else
+        insert_at_position (GTK_BOX (box), item, index + 2);
+    }
+}
+
+static void
+row_reordered_cb (GtkTreeModel   *model,
+                  GtkTreePath    *path,
+                  GtkTreeIter    *iter,
+                  gint           *new_order,
+                  GtkTreePopover *popover)
+{
+  rebuild_menu (popover);
+}
+
+static void
+context_size_changed_cb (GtkCellAreaContext *context,
+                         GParamSpec         *pspec,
+                         GtkWidget          *popover)
+{
+  if (!strcmp (pspec->name, "minimum-width") ||
+      !strcmp (pspec->name, "natural-width") ||
+      !strcmp (pspec->name, "minimum-height") ||
+      !strcmp (pspec->name, "natural-height"))
+    gtk_widget_queue_resize (popover);
+}
+
+static gboolean
+area_is_sensitive (GtkCellArea *area)
+{
+  GList    *cells, *list;
+  gboolean  sensitive = FALSE;
+
+  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);
+
+      if (sensitive)
+        break;
+    }
+  g_list_free (cells);
+
+  return sensitive;
+}
+
+static GtkWidget *
+gtk_tree_popover_get_path_item (GtkTreePopover *popover,
+                                GtkTreePath    *search)
+{
+  GtkWidget *stack = gtk_bin_get_child (GTK_BIN (popover));
+  GtkWidget *item = NULL;
+  GList *children, *l;
+
+  children = gtk_container_get_children (GTK_CONTAINER (stack));
+
+  for (l = children; !item && l; l = l->next)
+    {
+      GtkWidget *child;
+
+      for (child = gtk_widget_get_first_child (GTK_WIDGET (l->data));
+           !item && child;
+           child = gtk_widget_get_next_sibling (child))
+        {
+          GtkTreePath *path  = NULL;
+
+          if (GTK_IS_SEPARATOR (child))
+            {
+              GtkTreeRowReference *row = g_object_get_data (G_OBJECT (child), "gtk-tree-path");
+
+              if (row)
+                {
+                  path = gtk_tree_row_reference_get_path (row);
+                  if (!path)
+                    item = child;
+                }
+            }
+          else
+            {
+              GtkWidget *view = GTK_WIDGET (g_object_get_data (G_OBJECT (child), "view"));
+
+              path = gtk_cell_view_get_displayed_row (GTK_CELL_VIEW (view));
+
+              if (!path)
+                item = child;
+             }
+
+           if (path)
+             {
+               if (gtk_tree_path_compare (search, path) == 0)
+                 item = child;
+               gtk_tree_path_free (path);
+             }
+        }
+    }
+
+  g_list_free (children);
+
+  return item;
+}
+
+static void
+area_apply_attributes_cb (GtkCellArea    *area,
+                          GtkTreeModel   *tree_model,
+                          GtkTreeIter    *iter,
+                          gboolean       is_expander,
+                          gboolean       is_expanded,
+                          GtkTreePopover *popover)
+{
+  GtkTreePath*path;
+  GtkWidget *item;
+  gboolean sensitive;
+  GtkTreeIter dummy;
+  gboolean has_submenu = FALSE;
+
+  if (gtk_tree_model_iter_children (popover->model, &dummy, iter))
+    has_submenu = TRUE;
+
+  path = gtk_tree_model_get_path (tree_model, iter);
+  item = gtk_tree_popover_get_path_item (popover, path);
+
+  if (item)
+    {
+      sensitive = area_is_sensitive (popover->area);
+      gtk_widget_set_sensitive (item, sensitive || has_submenu);
+    }
+
+  gtk_tree_path_free (path);
+}
+
+static void
+gtk_tree_popover_set_area (GtkTreePopover *popover,
+                           GtkCellArea    *area)
+{
+  if (popover->area)
+    {
+      g_signal_handler_disconnect (popover->area, popover->apply_attributes_id);
+      popover->apply_attributes_id = 0;
+      g_clear_object (&popover->area);
+    }
+
+  popover->area = area;
+
+  if (popover->area)
+    {
+      g_object_ref_sink (popover->area);
+      popover->apply_attributes_id = g_signal_connect (popover->area, "apply-attributes",
+                                                       G_CALLBACK (area_apply_attributes_cb), popover);
+    }
+}
+
+static void
+item_activated_cb (GtkGesture     *gesture,
+                   guint           n_press,
+                   double          x,
+                   double          y,
+                   GtkTreePopover *popover)
+{
+  GtkWidget *item;
+  GtkCellView *view;
+  GtkTreePath *path;
+  gchar *path_str;
+  gboolean is_header = FALSE;
+  gboolean has_submenu = FALSE;
+
+  item = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (gesture));
+  is_header = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (item), "is-header"));
+
+  view = GTK_CELL_VIEW (g_object_get_data (G_OBJECT (item), "view"));
+
+  path = gtk_cell_view_get_displayed_row (view);
+
+  if (is_header)
+    {
+      gtk_tree_path_up (path);
+    }
+  else
+    {
+      GtkTreeIter iter;
+      GtkTreeIter dummy;
+
+      gtk_tree_model_get_iter (popover->model, &iter, path);
+      if (gtk_tree_model_iter_children (popover->model, &dummy, &iter))
+        has_submenu = TRUE;
+    }
+
+  path_str = gtk_tree_path_to_string (path);
+
+  if (is_header || has_submenu)
+    {
+      gtk_tree_popover_open_submenu (popover, path_str ? path_str : "main");
+    }
+  else
+    {
+      g_signal_emit (popover, signals[MENU_ACTIVATE], 0, path_str);
+      gtk_popover_popdown (GTK_POPOVER (popover));
+    }
+
+  g_free (path_str);
+  gtk_tree_path_free (path);
+}
+
+static void
+enter_cb (GtkEventController *controller,
+          double              x,
+          double              y,
+          GdkCrossingMode     mode,
+          GdkNotifyType       type,
+          GtkTreePopover     *popover)
+{
+  GtkWidget *item;
+  item = gtk_event_controller_get_widget (controller);
+
+  if (gtk_event_controller_motion_contains_pointer (GTK_EVENT_CONTROLLER_MOTION (controller)))
+    {
+      gtk_tree_popover_set_active_item (popover, item);
+    }
+}
+
+static void
+leave_cb (GtkEventController *controller,
+          GdkCrossingMode     mode,
+          GdkNotifyType       type,
+          GtkTreePopover     *popover)
+{
+}
+
+static GtkWidget *
+gtk_tree_popover_create_item (GtkTreePopover *popover,
+                              GtkTreePath    *path,
+                              GtkTreeIter    *iter,
+                              gboolean        header_item)
+{
+  GtkWidget *item, *view;
+  gboolean is_separator = FALSE;
+
+  if (popover->row_separator_func)
+    is_separator = popover->row_separator_func (popover->model, iter, popover->row_separator_data);
+
+  if (is_separator)
+    {
+      item = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL);
+      g_object_set_data_full (G_OBJECT (item), "gtk-tree-path",
+                                               gtk_tree_row_reference_new (popover->model, path),
+                                               (GDestroyNotify)gtk_tree_row_reference_free);
+    }
+  else
+    {
+      GtkEventController *controller;
+      GtkTreeIter dummy;
+      gboolean has_submenu = FALSE;
+      GtkWidget *indicator;
+
+      if (!header_item &&
+          gtk_tree_model_iter_children (popover->model, &dummy, iter))
+        has_submenu = TRUE;
+
+      view = gtk_cell_view_new_with_context (popover->area, popover->context);
+      gtk_cell_view_set_model (GTK_CELL_VIEW (view), popover->model);
+      gtk_cell_view_set_displayed_row (GTK_CELL_VIEW (view), path);
+      gtk_widget_set_hexpand (view, TRUE);
+
+      item = gtk_gizmo_new ("modelbutton", NULL, NULL, NULL, NULL);
+      gtk_widget_set_layout_manager (item, gtk_box_layout_new (GTK_ORIENTATION_HORIZONTAL));
+      gtk_style_context_add_class (gtk_widget_get_style_context (item), "flat");
+
+      if (header_item)
+        {
+          indicator = gtk_icon_new ("arrow");
+          gtk_icon_set_image (GTK_ICON (indicator), GTK_CSS_IMAGE_BUILTIN_ARROW_LEFT);
+          gtk_style_context_add_class (gtk_widget_get_style_context (indicator), "left");
+          gtk_widget_set_parent (indicator, item);
+        }
+
+      gtk_widget_set_parent (view, item);
+
+      indicator = gtk_icon_new (has_submenu ? "arrow" : "none");
+      gtk_icon_set_image (GTK_ICON (indicator), has_submenu ? GTK_CSS_IMAGE_BUILTIN_ARROW_RIGHT
+                                                            : GTK_CSS_IMAGE_BUILTIN_NONE); 
+      gtk_style_context_add_class (gtk_widget_get_style_context (indicator), "right");
+      gtk_widget_set_parent (indicator, item);
+
+      controller = GTK_EVENT_CONTROLLER (gtk_gesture_click_new ());
+      g_signal_connect (controller, "pressed", G_CALLBACK (item_activated_cb), popover);
+      gtk_widget_add_controller (item, GTK_EVENT_CONTROLLER (controller));
+
+      controller = gtk_event_controller_motion_new ();
+      g_signal_connect (controller, "enter", G_CALLBACK (enter_cb), popover);
+      g_signal_connect (controller, "leave", G_CALLBACK (leave_cb), popover);
+      gtk_widget_add_controller (item, controller);
+
+      g_object_set_data (G_OBJECT (item), "is-header", GINT_TO_POINTER (header_item));
+      g_object_set_data (G_OBJECT (item), "view", view);
+    }
+
+  return item;
+}
+
+static void
+populate (GtkTreePopover *popover,
+          GtkTreeIter    *parent)
+{
+  GtkTreeIter iter;
+  gboolean valid = FALSE;
+
+  if (!popover->model)
+    return;
+
+  valid = gtk_tree_model_iter_children (popover->model, &iter, parent);
+
+  while (valid)
+    {
+      GtkTreePath *path;
+
+      path = gtk_tree_model_get_path (popover->model, &iter);
+      row_inserted_cb (popover->model, path, &iter, popover);
+
+      populate (popover, &iter);
+
+      valid = gtk_tree_model_iter_next (popover->model, &iter);
+      gtk_tree_path_free (path);
+    }
+}
+
+static void
+gtk_tree_popover_populate (GtkTreePopover *popover)
+{
+  populate (popover, NULL);
+}
+
+static void
+rebuild_menu (GtkTreePopover *popover)
+{
+  GtkWidget *stack = gtk_bin_get_child (GTK_BIN (popover));
+  gtk_container_foreach (GTK_CONTAINER (stack), (GtkCallback) gtk_widget_destroy, NULL);
+
+  if (popover->model)
+    gtk_tree_popover_populate (popover);
+}
+
+void
+gtk_tree_popover_set_model (GtkTreePopover *popover,
+                            GtkTreeModel   *model)
+{
+  if (popover->model == model)
+    return;
+
+  if (popover->model)
+    {
+      g_signal_handler_disconnect (popover->model, popover->row_inserted_id);
+      g_signal_handler_disconnect (popover->model, popover->row_deleted_id);
+      g_signal_handler_disconnect (popover->model, popover->row_changed_id);
+      g_signal_handler_disconnect (popover->model, popover->row_reordered_id);
+      popover->row_inserted_id  = 0;
+      popover->row_deleted_id = 0;
+      popover->row_changed_id = 0;
+      popover->row_reordered_id = 0;
+
+      g_object_unref (popover->model);
+    }
+
+  popover->model = model;
+
+  if (popover->model)
+    {
+      g_object_ref (popover->model);
+
+      popover->row_inserted_id = g_signal_connect (popover->model, "row-inserted",
+                                                   G_CALLBACK (row_inserted_cb), popover);
+      popover->row_deleted_id = g_signal_connect (popover->model, "row-deleted",
+                                                  G_CALLBACK (row_deleted_cb), popover);
+      popover->row_changed_id = g_signal_connect (popover->model, "row-changed",
+                                                  G_CALLBACK (row_changed_cb), popover);
+      popover->row_reordered_id = g_signal_connect (popover->model, "rows-reordered",
+                                                    G_CALLBACK (row_reordered_cb), popover);
+    }
+
+  rebuild_menu (popover);
+}
+
+void
+gtk_tree_popover_set_row_separator_func (GtkTreePopover              *popover,
+                                         GtkTreeViewRowSeparatorFunc  func,
+                                         gpointer                     data,
+                                         GDestroyNotify               destroy)
+{
+  if (popover->row_separator_destroy)
+    popover->row_separator_destroy (popover->row_separator_data);
+
+  popover->row_separator_func = func;
+  popover->row_separator_data = data;
+  popover->row_separator_destroy = destroy;
+
+  rebuild_menu (popover);
+}
+
+static void
+gtk_tree_popover_set_active_item (GtkTreePopover *popover,
+                                  GtkWidget      *item)
+{
+  if (popover->active_item == item)
+    return;
+
+  if (popover->active_item)
+    {
+      gtk_widget_unset_state_flags (popover->active_item, GTK_STATE_FLAG_SELECTED);
+      g_object_remove_weak_pointer (G_OBJECT (popover->active_item), (gpointer *)&popover->active_item);
+    }
+
+  popover->active_item = item;
+
+  if (popover->active_item)
+    {
+      g_object_add_weak_pointer (G_OBJECT (popover->active_item), (gpointer *)&popover->active_item);
+      gtk_widget_set_state_flags (popover->active_item, GTK_STATE_FLAG_SELECTED, FALSE);
+    }
+}
+
+void
+gtk_tree_popover_set_active (GtkTreePopover *popover,
+                             int             item)
+{
+  GtkWidget *box;
+  GtkWidget *child;
+  int pos;
+
+  if (item == -1)
+    {
+      gtk_tree_popover_set_active_item (popover, NULL);
+      return;
+    }
+
+  box = gtk_tree_popover_get_submenu (popover, "main");
+  if (!box)
+    return;
+
+  for (child = gtk_widget_get_first_child (box), pos = 0;
+       child;
+       child = gtk_widget_get_next_sibling (child), pos++)
+    {
+      if (pos == item)
+        {
+          gtk_tree_popover_set_active_item (popover, child);
+          break;
+        }
+    }
+}
+
diff --git a/gtk/gtktreepopoverprivate.h b/gtk/gtktreepopoverprivate.h
new file mode 100644 (file)
index 0000000..a43bfcb
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * Copyright © 2019 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the licence, 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Matthias Clasen
+ */
+
+#ifndef __GTK_TREE_POPOPVER_PRIVATE_H__
+#define __GTK_TREE_POPOVER_PRIVATE_H__
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GTK_TYPE_TREE_POPOVER         (gtk_tree_popover_get_type ())
+G_DECLARE_FINAL_TYPE (GtkTreePopover, gtk_tree_popover, GTK, TREE_POPOVER, GtkPopover)
+
+void gtk_tree_popover_set_model              (GtkTreePopover              *popover,
+                                              GtkTreeModel                *model);
+void gtk_tree_popover_set_row_separator_func (GtkTreePopover              *popover,
+                                              GtkTreeViewRowSeparatorFunc  func,
+                                              gpointer                     data,
+                                              GDestroyNotify               destroy);
+void gtk_tree_popover_set_active             (GtkTreePopover              *popover,
+                                              int                          item);
+void gtk_tree_popover_open_submenu           (GtkTreePopover              *popover,
+                                              const char                  *name);
+
+G_END_DECLS
+
+#endif /* __GTK_TREE_POPOVER_PRIVATE_H__ */
index 827c7b2708bc837cd821eef5c59a84f5c57ef97c..00a30688648ef8aace9f9b7a5ae1b97d4c0bda96 100644 (file)
@@ -393,10 +393,10 @@ gtk_public_sources = files([
   'gtktooltipwindow.c',
   'gtktreednd.c',
   'gtktreelistmodel.c',
-  'gtktreemenu.c',
   'gtktreemodel.c',
   'gtktreemodelfilter.c',
   'gtktreemodelsort.c',
+  'gtktreepopover.c',
   'gtktreeselection.c',
   'gtktreesortable.c',
   'gtktreestore.c',
index 41b5754b75ea9c4faedf3120d680d948003b57ab..24cafedb21fa3f4f3da5da98148fead8db03c0ce 100644 (file)
     </child>
   </template>
   <object class="GtkCellAreaBox" id="area"/>
-  <object class="GtkTreeMenu" id="popup_widget">
+  <object class="GtkTreePopover" id="popup_widget">
     <property name="cell-area">area</property>
+    <property name="relative-to">GtkComboBox</property>
+    <property name="has-arrow">0</property>
     <signal name="menu-activate" handler="gtk_combo_box_menu_activate" swapped="no"/>
     <signal name="show" handler="gtk_combo_box_menu_show" swapped="no"/>
     <signal name="hide" handler="gtk_combo_box_menu_hide" swapped="no"/>