[gnome-builder] libide-tree: port to GTK 4



commit 6a0647826b6e16cbb8dbbf432584d216bd27b1f3
Author: Christian Hergert <chergert redhat com>
Date:   Mon Jul 11 17:46:19 2022 -0700

    libide-tree: port to GTK 4
    
    The most invasive change from this port is the need for
    IdePopoverPositioner so that we can place popovers over widgets that do
    not really support it natively. GtkTreeView is the main difficulty here
    that we're working around.

 src/libide/tree/ide-cell-renderer-status.c | 196 +++----------------------
 src/libide/tree/ide-cell-renderer-status.h |   8 +-
 src/libide/tree/ide-popover-positioner.c   |  56 +++++++
 src/libide/tree/ide-popover-positioner.h   |  50 +++++++
 src/libide/tree/ide-tree-addin.c           |  26 ++--
 src/libide/tree/ide-tree-addin.h           |  40 ++---
 src/libide/tree/ide-tree-model.c           |  78 ++++------
 src/libide/tree/ide-tree-model.h           |  24 +--
 src/libide/tree/ide-tree-node.c            | 165 ++++-----------------
 src/libide/tree/ide-tree-node.h            | 112 +++++++-------
 src/libide/tree/ide-tree-private.h         |   3 +
 src/libide/tree/ide-tree.c                 | 225 ++++++++++++-----------------
 src/libide/tree/ide-tree.h                 |  51 +++----
 src/libide/tree/libide-tree.h              |  11 +-
 src/libide/tree/meson.build                |   2 +
 15 files changed, 411 insertions(+), 636 deletions(-)
---
diff --git a/src/libide/tree/ide-cell-renderer-status.c b/src/libide/tree/ide-cell-renderer-status.c
index 29bc39e45..7be50bddb 100644
--- a/src/libide/tree/ide-cell-renderer-status.c
+++ b/src/libide/tree/ide-cell-renderer-status.c
@@ -22,187 +22,37 @@
 
 #include "config.h"
 
-#include <math.h>
-
 #include "ide-cell-renderer-status.h"
 
-#define CELL_HEIGHT 16
-#define CELL_WIDTH  16
-#define RPAD        8
-#define LPAD        3
-
-struct _IdeCellRendererStatus
-{
-  GtkCellRenderer  parent_instance;
-  IdeTreeNodeFlags flags;
-};
-
-enum {
-  PROP_0,
-  PROP_FLAGS,
-  N_PROPS
-};
-
-G_DEFINE_FINAL_TYPE (IdeCellRendererStatus, ide_cell_renderer_status, GTK_TYPE_CELL_RENDERER)
-
-static GParamSpec *properties [N_PROPS];
-
-static void
-ide_cell_renderer_status_get_preferred_height (GtkCellRenderer *cell,
-                                               GtkWidget       *widget,
-                                               gint            *min_size,
-                                               gint            *nat_size)
-{
-  g_assert (IDE_IS_CELL_RENDERER_STATUS (cell));
-  g_assert (GTK_IS_WIDGET (widget));
-
-  if (min_size)
-    *min_size = CELL_HEIGHT;
-
-  if (nat_size)
-    *nat_size = CELL_HEIGHT;
-}
-
-static void
-ide_cell_renderer_status_get_preferred_width (GtkCellRenderer *cell,
-                                              GtkWidget       *widget,
-                                              gint            *min_size,
-                                              gint            *nat_size)
-{
-  g_assert (IDE_IS_CELL_RENDERER_STATUS (cell));
-  g_assert (GTK_IS_WIDGET (widget));
-
-  if (min_size)
-    *min_size = LPAD + CELL_WIDTH + RPAD;
-
-  if (nat_size)
-    *nat_size = LPAD + CELL_WIDTH + RPAD;
-}
-
-static void
-ide_cell_renderer_status_render (GtkCellRenderer      *cell,
-                                 cairo_t              *cr,
-                                 GtkWidget            *widget,
-                                 const GdkRectangle   *bg_area,
-                                 const GdkRectangle   *cell_area,
-                                 GtkCellRendererState  state)
-{
-  IdeCellRendererStatus *self = (IdeCellRendererStatus *)cell;
-  GtkStyleContext *style_context;
-  GdkRGBA color;
-
-  g_assert (IDE_IS_CELL_RENDERER_STATUS (self));
-  g_assert (cr != NULL);
-  g_assert (GTK_IS_WIDGET (widget));
-  g_assert (bg_area != NULL);
-  g_assert (cell_area != NULL);
-
-  if (self->flags == 0)
-    return;
-
-  style_context = gtk_widget_get_style_context (widget);
-  gtk_style_context_save (style_context);
-
-  if (state & GTK_CELL_RENDERER_SELECTED)
-    gtk_style_context_set_state (style_context,
-                                 gtk_style_context_get_state (style_context) & GTK_STATE_FLAG_SELECTED);
-  gtk_style_context_get_color (style_context,
-                               gtk_style_context_get_state (style_context),
-                               &color);
-  gdk_cairo_set_source_rgba (cr, &color);
-
-  cairo_arc (cr,
-             cell_area->x + cell_area->width - RPAD - (CELL_WIDTH/2),
-             cell_area->y + (cell_area->height / 2),
-             3,
-             0,
-             M_PI * 2);
-
-  if (self->flags & IDE_TREE_NODE_FLAGS_ADDED)
-    cairo_fill_preserve (cr);
+#define VALUE_INIT_STATIC_STRING(name)         \
+  {                                            \
+    .g_type = G_TYPE_STRING,                   \
+    .data = {                                  \
+      { .v_pointer = (char *)name },           \
+      { .v_uint = G_VALUE_NOCOPY_CONTENTS },   \
+    },                                         \
+  }
 
-  cairo_stroke (cr);
-
-  gtk_style_context_restore (style_context);
-}
-
-static void
-ide_cell_renderer_status_get_property (GObject    *object,
-                                       guint       prop_id,
-                                       GValue     *value,
-                                       GParamSpec *pspec)
-{
-  IdeCellRendererStatus *self = IDE_CELL_RENDERER_STATUS (object);
-
-  switch (prop_id)
-    {
-    case PROP_FLAGS:
-      g_value_set_uint (value, self->flags);
-      break;
-
-    default:
-      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
-    }
-}
-
-static void
-ide_cell_renderer_status_set_property (GObject      *object,
-                                       guint         prop_id,
-                                       const GValue *value,
-                                       GParamSpec   *pspec)
-{
-  IdeCellRendererStatus *self = IDE_CELL_RENDERER_STATUS (object);
-
-  switch (prop_id)
-    {
-    case PROP_FLAGS:
-      self->flags = g_value_get_uint (value);
-      break;
-
-    default:
-      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
-    }
-}
-
-static void
-ide_cell_renderer_status_class_init (IdeCellRendererStatusClass *klass)
-{
-  GObjectClass *object_class = G_OBJECT_CLASS (klass);
-  GtkCellRendererClass *renderer_class = GTK_CELL_RENDERER_CLASS (klass);
-
-  object_class->get_property = ide_cell_renderer_status_get_property;
-  object_class->set_property = ide_cell_renderer_status_set_property;
-
-  renderer_class->get_preferred_height = ide_cell_renderer_status_get_preferred_height;
-  renderer_class->get_preferred_width = ide_cell_renderer_status_get_preferred_width;
-  renderer_class->render = ide_cell_renderer_status_render;
-
-  properties [PROP_FLAGS] =
-    g_param_spec_uint ("flags",
-                       "Flags",
-                       "The flags for the state",
-                       0, G_MAXUINT, 0,
-                       (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
-  
-  g_object_class_install_properties (object_class, N_PROPS, properties);
-}
-
-static void
-ide_cell_renderer_status_init (IdeCellRendererStatus *self)
-{
-}
+static GValue added = VALUE_INIT_STATIC_STRING ("builder-vcs-added-symbolic");
+static GValue changed = VALUE_INIT_STATIC_STRING ("builder-vcs-changed-symbolic");
+static GValue empty = VALUE_INIT_STATIC_STRING (NULL);
 
 GtkCellRenderer *
 ide_cell_renderer_status_new (void)
 {
-  return g_object_new (IDE_TYPE_CELL_RENDERER_STATUS, NULL);
+  return g_object_new (GTK_TYPE_CELL_RENDERER_PIXBUF,
+                       "xpad", 3,
+                       NULL);
 }
 
 void
-ide_cell_renderer_status_set_flags (IdeCellRendererStatus *self,
-                                    IdeTreeNodeFlags       flags)
-{
-  g_return_if_fail (IDE_IS_CELL_RENDERER_STATUS (self));
-
-  self->flags = flags;
+ide_cell_renderer_status_set_flags (GtkCellRenderer  *renderer,
+                                    IdeTreeNodeFlags  flags)
+{
+  if (flags & IDE_TREE_NODE_FLAGS_ADDED)
+    g_object_set_property ((GObject *)renderer, "icon-name", &added);
+  else if (flags & IDE_TREE_NODE_FLAGS_CHANGED)
+    g_object_set_property ((GObject *)renderer, "icon-name", &changed);
+  else
+    g_object_set_property ((GObject *)renderer, "icon-name", &empty);
 }
diff --git a/src/libide/tree/ide-cell-renderer-status.h b/src/libide/tree/ide-cell-renderer-status.h
index 1ed489c58..6ff70ce05 100644
--- a/src/libide/tree/ide-cell-renderer-status.h
+++ b/src/libide/tree/ide-cell-renderer-status.h
@@ -26,12 +26,8 @@
 
 G_BEGIN_DECLS
 
-#define IDE_TYPE_CELL_RENDERER_STATUS (ide_cell_renderer_status_get_type())
-
-G_DECLARE_FINAL_TYPE (IdeCellRendererStatus, ide_cell_renderer_status, IDE, CELL_RENDERER_STATUS, 
GtkCellRenderer)
-
 GtkCellRenderer *ide_cell_renderer_status_new       (void);
-void             ide_cell_renderer_status_set_flags (IdeCellRendererStatus *self,
-                                                     IdeTreeNodeFlags       flags);
+void             ide_cell_renderer_status_set_flags (GtkCellRenderer  *cell,
+                                                     IdeTreeNodeFlags  flags);
 
 G_END_DECLS
diff --git a/src/libide/tree/ide-popover-positioner.c b/src/libide/tree/ide-popover-positioner.c
new file mode 100644
index 000000000..637061f5c
--- /dev/null
+++ b/src/libide/tree/ide-popover-positioner.c
@@ -0,0 +1,56 @@
+/* ide-popover-positioner.c
+ *
+ * Copyright 2022 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#define G_LOG_DOMAIN "ide-popover-positioner"
+
+#include "config.h"
+
+#include "ide-popover-positioner.h"
+
+G_DEFINE_INTERFACE (IdePopoverPositioner, ide_popover_positioner, GTK_TYPE_WIDGET)
+
+static void
+ide_popover_positioner_default_init (IdePopoverPositionerInterface *iface)
+{
+}
+
+void
+ide_popover_positioner_present (IdePopoverPositioner *self,
+                                GtkPopover           *popover,
+                                GtkWidget            *relative_to,
+                                const GdkRectangle   *pointing_at)
+{
+  GdkRectangle empty;
+
+  g_return_if_fail (IDE_IS_POPOVER_POSITIONER (self));
+  g_return_if_fail (GTK_IS_POPOVER (popover));
+  g_return_if_fail (GTK_IS_WIDGET (relative_to));
+
+  if (pointing_at == NULL)
+    {
+      empty.x = 0;
+      empty.y = 0;
+      empty.width = gtk_widget_get_width (relative_to);
+      empty.height = gtk_widget_get_height (relative_to);
+      pointing_at = &empty;
+    }
+
+  IDE_POPOVER_POSITIONER_GET_IFACE (self)->present (self, popover, relative_to, pointing_at);
+}
diff --git a/src/libide/tree/ide-popover-positioner.h b/src/libide/tree/ide-popover-positioner.h
new file mode 100644
index 000000000..ac76e4a4e
--- /dev/null
+++ b/src/libide/tree/ide-popover-positioner.h
@@ -0,0 +1,50 @@
+/* ide-popover-positioner.h
+ *
+ * Copyright 2022 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+
+#include <libide-core.h>
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_POPOVER_POSITIONER (ide_popover_positioner_get_type())
+
+IDE_AVAILABLE_IN_ALL
+G_DECLARE_INTERFACE (IdePopoverPositioner, ide_popover_positioner, IDE, POPOVER_POSITIONER, GtkWidget)
+
+struct _IdePopoverPositionerInterface
+{
+  GTypeInterface parent_iface;
+
+  void (*present) (IdePopoverPositioner *self,
+                   GtkPopover           *popover,
+                   GtkWidget            *relative_to,
+                   const GdkRectangle   *pointing_to);
+};
+
+IDE_AVAILABLE_IN_ALL
+void ide_popover_positioner_present (IdePopoverPositioner *self,
+                                     GtkPopover           *popover,
+                                     GtkWidget            *relative_to,
+                                     const GdkRectangle   *pointing_to);
+
+G_END_DECLS
diff --git a/src/libide/tree/ide-tree-addin.c b/src/libide/tree/ide-tree-addin.c
index 973ce7ffa..92398cdf6 100644
--- a/src/libide/tree/ide-tree-addin.c
+++ b/src/libide/tree/ide-tree-addin.c
@@ -67,7 +67,7 @@ static void
 ide_tree_addin_real_node_dropped_async (IdeTreeAddin        *self,
                                         IdeTreeNode         *drag_node,
                                         IdeTreeNode         *drop_node,
-                                        GtkSelectionData    *selection,
+                                        const GValue        *value,
                                         GdkDragAction        actions,
                                         GCancellable        *cancellable,
                                         GAsyncReadyCallback  callback,
@@ -122,8 +122,6 @@ ide_tree_addin_default_init (IdeTreeAddinInterface *iface)
  *
  * This function will call the synchronous form of
  * IdeTreeAddin.build_children() if no asynchronous form is available.
- *
- * Since: 3.32
  */
 void
 ide_tree_addin_build_children_async (IdeTreeAddin        *self,
@@ -149,8 +147,6 @@ ide_tree_addin_build_children_async (IdeTreeAddin        *self,
  * Completes an asynchronous request to ide_tree_addin_build_children_async().
  *
  * Returns: %TRUE if successful; otherwise %FALSE and @error is set.
- *
- * Since: 3.32
  */
 gboolean
 ide_tree_addin_build_children_finish (IdeTreeAddin  *self,
@@ -176,8 +172,6 @@ ide_tree_addin_build_children_finish (IdeTreeAddin  *self,
  *
  * You may want to use ide_tree_node_holds() to determine if the node
  * contains an item that you are interested in.
- *
- * Since: 3.32
  */
 void
 ide_tree_addin_build_node (IdeTreeAddin *self,
@@ -206,8 +200,6 @@ ide_tree_addin_build_node (IdeTreeAddin *self,
  * respond to the action.
  *
  * Returns: %TRUE if the activation was handled, otherwise %FALSE
- *
- * Since: 3.32
  */
 gboolean
 ide_tree_addin_node_activated (IdeTreeAddin *self,
@@ -298,17 +290,17 @@ ide_tree_addin_node_draggable (IdeTreeAddin *self,
 }
 
 gboolean
-ide_tree_addin_node_droppable (IdeTreeAddin     *self,
-                               IdeTreeNode      *drag_node,
-                               IdeTreeNode      *drop_node,
-                               GtkSelectionData *selection)
+ide_tree_addin_node_droppable (IdeTreeAddin *self,
+                               IdeTreeNode  *drag_node,
+                               IdeTreeNode  *drop_node,
+                               const GValue *value)
 {
   g_return_val_if_fail (IDE_IS_TREE_ADDIN (self), FALSE);
   g_return_val_if_fail (!drag_node || IDE_IS_TREE_NODE (drag_node), FALSE);
   g_return_val_if_fail (!drop_node || IDE_IS_TREE_NODE (drop_node), FALSE);
 
   if (IDE_TREE_ADDIN_GET_IFACE (self)->node_droppable)
-    return IDE_TREE_ADDIN_GET_IFACE (self)->node_droppable (self, drag_node, drop_node, selection);
+    return IDE_TREE_ADDIN_GET_IFACE (self)->node_droppable (self, drag_node, drop_node, value);
 
   return FALSE;
 }
@@ -317,7 +309,7 @@ void
 ide_tree_addin_node_dropped_async (IdeTreeAddin        *self,
                                    IdeTreeNode         *drag_node,
                                    IdeTreeNode         *drop_node,
-                                   GtkSelectionData    *selection,
+                                   const GValue        *value,
                                    GdkDragAction        actions,
                                    GCancellable        *cancellable,
                                    GAsyncReadyCallback  callback,
@@ -327,13 +319,13 @@ ide_tree_addin_node_dropped_async (IdeTreeAddin        *self,
   g_return_if_fail (IDE_IS_TREE_ADDIN (self));
   g_return_if_fail (!drag_node || IDE_IS_TREE_NODE (drag_node));
   g_return_if_fail (!drop_node || IDE_IS_TREE_NODE (drop_node));
-  g_return_if_fail (selection != NULL);
+  g_return_if_fail (value != NULL);
   g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
 
   IDE_TREE_ADDIN_GET_IFACE (self)->node_dropped_async (self,
                                                        drag_node,
                                                        drop_node,
-                                                       selection,
+                                                       value,
                                                        actions,
                                                        cancellable,
                                                        callback,
diff --git a/src/libide/tree/ide-tree-addin.h b/src/libide/tree/ide-tree-addin.h
index 92275620d..6dce9936a 100644
--- a/src/libide/tree/ide-tree-addin.h
+++ b/src/libide/tree/ide-tree-addin.h
@@ -28,9 +28,9 @@
 
 G_BEGIN_DECLS
 
-#define IDE_TYPE_TREE_ADDIN (ide_tree_addin_get_type ())
+#define IDE_TYPE_TREE_ADDIN (ide_tree_addin_get_type())
 
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 G_DECLARE_INTERFACE (IdeTreeAddin, ide_tree_addin, IDE, TREE_ADDIN, GObject)
 
 struct _IdeTreeAddinInterface
@@ -72,11 +72,11 @@ struct _IdeTreeAddinInterface
   gboolean (*node_droppable)        (IdeTreeAddin         *self,
                                      IdeTreeNode          *drag_node,
                                      IdeTreeNode          *drop_node,
-                                     GtkSelectionData     *selection);
+                                     const GValue         *value);
   void     (*node_dropped_async)    (IdeTreeAddin         *self,
                                      IdeTreeNode          *drag_node,
                                      IdeTreeNode          *drop_node,
-                                     GtkSelectionData     *selection,
+                                     const GValue         *value,
                                      GdkDragAction         actions,
                                      GCancellable         *cancellable,
                                      GAsyncReadyCallback   callback,
@@ -86,62 +86,62 @@ struct _IdeTreeAddinInterface
                                      GError              **error);
 };
 
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 void     ide_tree_addin_load                  (IdeTreeAddin         *self,
                                                IdeTree              *tree,
                                                IdeTreeModel         *model);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 void     ide_tree_addin_unload                (IdeTreeAddin         *self,
                                                IdeTree              *tree,
                                                IdeTreeModel         *model);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 void     ide_tree_addin_build_node            (IdeTreeAddin         *self,
                                                IdeTreeNode          *node);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 void     ide_tree_addin_build_children_async  (IdeTreeAddin         *self,
                                                IdeTreeNode          *node,
                                                GCancellable         *cancellable,
                                                GAsyncReadyCallback   callback,
                                                gpointer              user_data);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 gboolean ide_tree_addin_build_children_finish (IdeTreeAddin         *self,
                                                GAsyncResult         *result,
                                                GError              **error);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 gboolean ide_tree_addin_node_activated        (IdeTreeAddin         *self,
                                                IdeTree              *tree,
                                                IdeTreeNode          *node);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 void     ide_tree_addin_selection_changed     (IdeTreeAddin         *self,
                                                IdeTreeNode          *selection);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 void     ide_tree_addin_node_expanded         (IdeTreeAddin         *self,
                                                IdeTreeNode          *node);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 void     ide_tree_addin_node_collapsed        (IdeTreeAddin         *self,
                                                IdeTreeNode          *node);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 gboolean ide_tree_addin_node_draggable        (IdeTreeAddin         *self,
                                                IdeTreeNode          *node);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 gboolean ide_tree_addin_node_droppable        (IdeTreeAddin         *self,
                                                IdeTreeNode          *drag_node,
                                                IdeTreeNode          *drop_node,
-                                               GtkSelectionData     *selection);
-IDE_AVAILABLE_IN_3_32
+                                               const GValue         *value);
+IDE_AVAILABLE_IN_ALL
 void     ide_tree_addin_node_dropped_async    (IdeTreeAddin         *self,
                                                IdeTreeNode          *drag_node,
                                                IdeTreeNode          *drop_node,
-                                               GtkSelectionData     *selection,
+                                               const GValue         *value,
                                                GdkDragAction         actions,
                                                GCancellable         *cancellable,
                                                GAsyncReadyCallback   callback,
                                                gpointer              user_data);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 gboolean ide_tree_addin_node_dropped_finish   (IdeTreeAddin         *self,
                                                GAsyncResult         *result,
                                                GError              **error);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 void     ide_tree_addin_cell_data_func        (IdeTreeAddin         *self,
                                                IdeTreeNode          *node,
                                                GtkCellRenderer      *cell);
diff --git a/src/libide/tree/ide-tree-model.c b/src/libide/tree/ide-tree-model.c
index a9318e871..7534cf2fc 100644
--- a/src/libide/tree/ide-tree-model.c
+++ b/src/libide/tree/ide-tree-model.c
@@ -46,7 +46,7 @@ typedef struct
 {
   IdeTreeNode      *drag_node;
   IdeTreeNode      *drop_node;
-  GtkSelectionData *selection;
+  GValue            value;
   GdkDragAction     actions;
   gint              n_active;
 } DragDataReceived;
@@ -56,9 +56,9 @@ static void tree_drag_dest_iface_init   (GtkTreeDragDestIface   *iface);
 static void tree_drag_source_iface_init (GtkTreeDragSourceIface *iface);
 
 G_DEFINE_FINAL_TYPE_WITH_CODE (IdeTreeModel, ide_tree_model, IDE_TYPE_OBJECT,
-                         G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_MODEL, tree_model_iface_init)
-                         G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_DRAG_DEST, tree_drag_dest_iface_init)
-                         G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_DRAG_SOURCE, tree_drag_source_iface_init))
+                               G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_MODEL, tree_model_iface_init)
+                               G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_DRAG_DEST, tree_drag_dest_iface_init)
+                               G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_DRAG_SOURCE, 
tree_drag_source_iface_init))
 
 enum {
   PROP_0,
@@ -80,7 +80,7 @@ drag_data_received_free (DragDataReceived *data)
 
   g_clear_object (&data->drag_node);
   g_clear_object (&data->drop_node);
-  g_clear_pointer (&data->selection, gtk_selection_data_free);
+  g_value_unset (&data->value);
   g_slice_free (DragDataReceived, data);
 }
 
@@ -227,14 +227,15 @@ ide_tree_model_dispose (GObject *object)
 {
   IdeTreeModel *self = (IdeTreeModel *)object;
 
-  /* Clear the model back-pointer for root so that it cannot emit anu
+  ide_clear_and_destroy_object (&self->addins);
+
+  /* Clear the model back-pointer for root so that it cannot emit any
    * further signals on our tree model.
    */
   if (self->root != NULL)
     _ide_tree_node_set_model (self->root, NULL);
 
   g_clear_object (&self->tree);
-  ide_clear_and_destroy_object (&self->addins);
   g_clear_object (&self->root);
   g_clear_pointer (&self->kind, g_free);
 
@@ -320,8 +321,6 @@ ide_tree_model_class_init (IdeTreeModelClass *klass)
    * The "root" property contains the root #IdeTreeNode that is used to build
    * the tree. It should contain an object for the #IdeTreeNode:item property
    * so that #IdeTreeAddin's may use it to build the node and any children.
-   *
-   * Since: 3.32
    */
   properties [PROP_ROOT] =
     g_param_spec_object ("root",
@@ -339,8 +338,6 @@ ide_tree_model_class_init (IdeTreeModelClass *klass)
    *
    * For example, to extend the project-tree, plugins should set
    * "X-Tree-Kind=project-tree" in their .plugin manifest.
-   *
-   * Since: 3.32
    */
   properties [PROP_KIND] =
     g_param_spec_string ("kind",
@@ -689,8 +686,6 @@ tree_model_iface_init (GtkTreeModelIface *iface)
  * Gets the #GtkTreePath pointing at @node.
  *
  * Returns: (transfer full) (nullable): a new #GtkTreePath
- *
- * Since: 3.32
  */
 GtkTreePath *
 ide_tree_model_get_path_for_node (IdeTreeModel *self,
@@ -716,8 +711,6 @@ ide_tree_model_get_path_for_node (IdeTreeModel *self,
  * Gets a #GtkTreeIter that points at @node.
  *
  * Returns: %TRUE if @iter was set; otherwise %FALSE
- *
- * Since: 3.32
  */
 gboolean
 ide_tree_model_get_iter_for_node (IdeTreeModel *self,
@@ -744,8 +737,6 @@ ide_tree_model_get_iter_for_node (IdeTreeModel *self,
  * is used to build the immediate children which are displayed in the tree.
  *
  * Returns: (transfer none) (not nullable): an #IdeTreeNode
- *
- * Since: 3.32
  */
 IdeTreeNode *
 ide_tree_model_get_root (IdeTreeModel *self)
@@ -824,8 +815,6 @@ ide_tree_model_set_root (IdeTreeModel *self,
  * for more information.
  *
  * Returns: (nullable): a string containing the kind, or %NULL
- *
- * Since: 3.32
  */
 const gchar *
 ide_tree_model_get_kind (IdeTreeModel *self)
@@ -845,8 +834,6 @@ ide_tree_model_get_kind (IdeTreeModel *self)
  *
  * This should be set before adding the #IdeTreeModel to an #IdeObject to
  * ensure the tree builds the proper contents.
- *
- * Since: 3.32
  */
 void
 ide_tree_model_set_kind (IdeTreeModel *self,
@@ -930,8 +917,6 @@ _ide_tree_model_row_activated (IdeTreeModel *self,
  * Gets the #IdeTreeNode found at @iter.
  *
  * Returns: (transfer none) (nullable): an #IdeTreeNode or %NULL
- *
- * Since: 3.32
  */
 IdeTreeNode *
 ide_tree_model_get_node (IdeTreeModel *self,
@@ -1120,8 +1105,6 @@ ide_tree_model_invalidate_traverse_cb (IdeTreeNode *node,
  * are rebuilt using the configured tree addins.
  *
  * If @node is %NULL, the root of the tree is invalidated.
- *
- * Since: 3.32
  */
 void
 ide_tree_model_invalidate (IdeTreeModel *self,
@@ -1271,8 +1254,6 @@ _ide_tree_model_row_collapsed (IdeTreeModel *self,
  * @self: a #IdeTreeModel
  *
  * Returns: (transfer none): an #IdeTree
- *
- * Since: 3.32
  */
 IdeTree *
 ide_tree_model_get_tree (IdeTreeModel *self)
@@ -1378,19 +1359,17 @@ ide_tree_model_row_draggable (GtkTreeDragSource *source,
   return state.draggable;
 }
 
-static gboolean
+static GdkContentProvider *
 ide_tree_model_drag_data_get (GtkTreeDragSource *source,
-                              GtkTreePath       *path,
-                              GtkSelectionData  *selection)
+                              GtkTreePath       *path)
 {
   IdeTreeModel *self = (IdeTreeModel *)source;
 
   g_assert (IDE_IS_MAIN_THREAD ());
   g_assert (IDE_IS_TREE_MODEL (self));
   g_assert (path != NULL);
-  g_assert (selection != NULL);
 
-  return gtk_tree_set_row_drag_data (selection, GTK_TREE_MODEL (self), path);
+  return gtk_tree_create_row_drag_content (GTK_TREE_MODEL (self), path);
 }
 
 static gboolean
@@ -1471,7 +1450,7 @@ ide_tree_model_drag_data_received_cb (IdeExtensionSetAdapter *set,
   ide_tree_addin_node_dropped_async (addin,
                                      state->drag_node,
                                      state->drop_node,
-                                     state->selection,
+                                     &state->value,
                                      state->actions,
                                      NULL,
                                      ide_tree_model_drag_data_received_addin_cb,
@@ -1479,9 +1458,9 @@ ide_tree_model_drag_data_received_cb (IdeExtensionSetAdapter *set,
 }
 
 static gboolean
-ide_tree_model_drag_data_received (GtkTreeDragDest  *dest,
-                                   GtkTreePath      *path,
-                                   GtkSelectionData *selection)
+ide_tree_model_drag_data_received (GtkTreeDragDest *dest,
+                                   GtkTreePath     *path,
+                                   const GValue    *value)
 {
   IdeTreeModel *self = (IdeTreeModel *)dest;
   g_autoptr(GtkTreePath) source_path = NULL;
@@ -1495,9 +1474,9 @@ ide_tree_model_drag_data_received (GtkTreeDragDest  *dest,
   g_assert (IDE_IS_MAIN_THREAD ());
   g_assert (IDE_IS_TREE_MODEL (self));
   g_assert (path != NULL);
-  g_assert (selection != NULL);
+  g_assert (value != NULL);
 
-  if (gtk_tree_get_row_drag_data (selection, &source_model, &source_path))
+  if (gtk_tree_get_row_drag_data (value, &source_model, &source_path))
     {
       if (IDE_IS_TREE_MODEL (source_model))
         {
@@ -1511,10 +1490,9 @@ ide_tree_model_drag_data_received (GtkTreeDragDest  *dest,
   state = g_slice_new0 (DragDataReceived);
   g_set_object (&state->drag_node, drag_node);
   g_set_object (&state->drop_node, drop_node);
-  state->selection = gtk_selection_data_copy (selection);
+  g_value_copy (value, &state->value);
   state->actions = _ide_tree_get_drop_actions (self->tree);
 
-
   task = ide_task_new (self, NULL, NULL, NULL);
   ide_task_set_source_tag (task, ide_tree_model_drag_data_received);
   ide_task_set_task_data (task, state, drag_data_received_free);
@@ -1538,7 +1516,7 @@ ide_tree_model_row_drop_possible_cb (IdeExtensionSetAdapter *set,
   struct {
     IdeTreeNode      *drag_node;
     IdeTreeNode      *drop_node;
-    GtkSelectionData *selection;
+    const GValue     *value;
     gboolean          drop_possible;
   } *state = user_data;
 
@@ -1547,18 +1525,18 @@ ide_tree_model_row_drop_possible_cb (IdeExtensionSetAdapter *set,
   g_assert (plugin_info != NULL);
   g_assert (IDE_IS_TREE_ADDIN (exten));
   g_assert (state != NULL);
-  g_assert (state->selection != NULL);
+  g_assert (state->value != NULL);
 
   state->drop_possible |= ide_tree_addin_node_droppable (IDE_TREE_ADDIN (exten),
                                                          state->drag_node,
                                                          state->drop_node,
-                                                         state->selection);
+                                                         state->value);
 }
 
 static gboolean
-ide_tree_model_row_drop_possible (GtkTreeDragDest  *dest,
-                                  GtkTreePath      *path,
-                                  GtkSelectionData *selection)
+ide_tree_model_row_drop_possible (GtkTreeDragDest *dest,
+                                  GtkTreePath     *path,
+                                  const GValue    *value)
 {
   IdeTreeModel *self = (IdeTreeModel *)dest;
   g_autoptr(GtkTreePath) source_path = NULL;
@@ -1569,16 +1547,16 @@ ide_tree_model_row_drop_possible (GtkTreeDragDest  *dest,
   struct {
     IdeTreeNode      *drag_node;
     IdeTreeNode      *drop_node;
-    GtkSelectionData *selection;
+    const GValue     *value;
     gboolean          drop_possible;
   } state;
 
   g_assert (IDE_IS_MAIN_THREAD ());
   g_assert (IDE_IS_TREE_MODEL (self));
   g_assert (path != NULL);
-  g_assert (selection != NULL);
+  g_assert (value != NULL);
 
-  if (gtk_tree_get_row_drag_data (selection, &source_model, &source_path))
+  if (gtk_tree_get_row_drag_data (value, &source_model, &source_path))
     {
       if (IDE_IS_TREE_MODEL (source_model))
         {
@@ -1603,7 +1581,7 @@ ide_tree_model_row_drop_possible (GtkTreeDragDest  *dest,
 
   state.drag_node = drag_node;
   state.drop_node = drop_node;
-  state.selection = selection;
+  state.value = value;
   state.drop_possible = FALSE;
 
   ide_extension_set_adapter_foreach (self->addins,
diff --git a/src/libide/tree/ide-tree-model.h b/src/libide/tree/ide-tree-model.h
index adc69d81a..fb7a4f41b 100644
--- a/src/libide/tree/ide-tree-model.h
+++ b/src/libide/tree/ide-tree-model.h
@@ -30,41 +30,41 @@ G_BEGIN_DECLS
 
 #define IDE_TYPE_TREE_MODEL (ide_tree_model_get_type())
 
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 G_DECLARE_FINAL_TYPE (IdeTreeModel, ide_tree_model, IDE, TREE_MODEL, IdeObject)
 
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 IdeTree     *ide_tree_model_get_tree          (IdeTreeModel         *self);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 IdeTreeNode *ide_tree_model_get_root          (IdeTreeModel         *self);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 void         ide_tree_model_set_root          (IdeTreeModel         *self,
                                                IdeTreeNode          *root);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 const gchar *ide_tree_model_get_kind          (IdeTreeModel         *self);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 void         ide_tree_model_set_kind          (IdeTreeModel         *self,
                                                const gchar          *kind);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 IdeTreeNode *ide_tree_model_get_node          (IdeTreeModel         *self,
                                                GtkTreeIter          *iter);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 GtkTreePath *ide_tree_model_get_path_for_node (IdeTreeModel         *self,
                                                IdeTreeNode          *node);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 gboolean     ide_tree_model_get_iter_for_node (IdeTreeModel         *self,
                                                GtkTreeIter          *iter,
                                                IdeTreeNode          *node);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 void         ide_tree_model_invalidate        (IdeTreeModel         *self,
                                                IdeTreeNode          *node);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 void         ide_tree_model_expand_async      (IdeTreeModel         *self,
                                                IdeTreeNode          *node,
                                                GCancellable         *cancellable,
                                                GAsyncReadyCallback   callback,
                                                gpointer              user_data);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 gboolean     ide_tree_model_expand_finish     (IdeTreeModel         *self,
                                                GAsyncResult         *result,
                                                GError              **error);
diff --git a/src/libide/tree/ide-tree-node.c b/src/libide/tree/ide-tree-node.c
index 200b9ff70..4fafb6e0b 100644
--- a/src/libide/tree/ide-tree-node.c
+++ b/src/libide/tree/ide-tree-node.c
@@ -24,6 +24,7 @@
 
 #include <glib/gi18n.h>
 
+#include "ide-popover-positioner.h"
 #include "ide-tree-model.h"
 #include "ide-tree-node.h"
 #include "ide-tree-private.h"
@@ -41,8 +42,6 @@
  * Plugins that want to add items to the tree should implement the
  * #IdeTreeAddin interface and register it during plugin
  * initialization.
- *
- * Since: 3.32
  */
 
 struct _IdeTreeNode
@@ -156,8 +155,6 @@ ide_tree_node_get_model (IdeTreeNode *self)
  * Create a new #IdeTreeNode.
  *
  * Returns: (transfer full): a newly created #IdeTreeNode
- *
- * Since: 3.32
  */
 IdeTreeNode *
 ide_tree_node_new (void)
@@ -377,8 +374,6 @@ ide_tree_node_class_init (IdeTreeNodeClass *klass)
    * The "children-possible" property denotes if the node may have children
    * even if it doesn't have children yet. This is useful for delayed loading
    * of children nodes.
-   *
-   * Since: 3.32
    */
   properties [PROP_CHILDREN_POSSIBLE] =
     g_param_spec_boolean ("children-possible",
@@ -392,8 +387,6 @@ ide_tree_node_class_init (IdeTreeNodeClass *klass)
    *
    * If %TRUE and #IdeTreeNode:item is an #IdeObject, it will be destroyed
    * when the node is destroyed.
-   *
-   * Since: 3.32
    */
   properties [PROP_DESTROY_ITEM] =
     g_param_spec_boolean ("destroy-item",
@@ -407,8 +400,6 @@ ide_tree_node_class_init (IdeTreeNodeClass *klass)
    *
    * The "display-name" property is the name for the node as it should be
    * displayed in the tree.
-   *
-   * Since: 3.32
    */
   properties [PROP_DISPLAY_NAME] =
     g_param_spec_string ("display-name",
@@ -422,8 +413,6 @@ ide_tree_node_class_init (IdeTreeNodeClass *klass)
    *
    * The "expanded-icon" property is the icon that should be displayed to the
    * user in the tree for this node.
-   *
-   * Since: 3.32
    */
   properties [PROP_EXPANDED_ICON] =
     g_param_spec_object ("expanded-icon",
@@ -437,8 +426,6 @@ ide_tree_node_class_init (IdeTreeNodeClass *klass)
    *
    * The "expanded-icon-name" is a convenience property to set the
    * #IdeTreeNode:expanded-icon property using an icon-name.
-   *
-   * Since: 3.32
    */
   properties [PROP_EXPANDED_ICON_NAME] =
     g_param_spec_string ("expanded-icon-name",
@@ -453,8 +440,6 @@ ide_tree_node_class_init (IdeTreeNodeClass *klass)
    * The "has-error" property is true if the node should be rendered with
    * an error styling. This is useful when errors are known by the diagnostics
    * manager for a given file or folder.
-   *
-   * Since: 3.32
    */
   properties [PROP_HAS_ERROR] =
     g_param_spec_boolean ("has-error",
@@ -468,8 +453,6 @@ ide_tree_node_class_init (IdeTreeNodeClass *klass)
    *
    * The "icon" property is the icon that should be displayed to the
    * user in the tree for this node.
-   *
-   * Since: 3.32
    */
   properties [PROP_ICON] =
     g_param_spec_object ("icon",
@@ -483,8 +466,6 @@ ide_tree_node_class_init (IdeTreeNodeClass *klass)
    *
    * The "icon-name" is a convenience property to set the #IdeTreeNode:icon
    * property using an icon-name.
-   *
-   * Since: 3.32
    */
   properties [PROP_ICON_NAME] =
     g_param_spec_string ("icon-name",
@@ -498,8 +479,6 @@ ide_tree_node_class_init (IdeTreeNodeClass *klass)
    *
    * The "is-header" property denotes the node should be styled as a group
    * header.
-   *
-   * Since: 3.32
    */
   properties [PROP_IS_HEADER] =
     g_param_spec_boolean ("is-header",
@@ -514,8 +493,6 @@ ide_tree_node_class_init (IdeTreeNodeClass *klass)
    * The "item" property is an optional #GObject that can be used to
    * store information about the node, which is sometimes useful when
    * creating #IdeTreeAddin plugins.
-   *
-   * Since: 3.32
    */
   properties [PROP_ITEM] =
     g_param_spec_object ("item",
@@ -529,8 +506,6 @@ ide_tree_node_class_init (IdeTreeNodeClass *klass)
    *
    * The "reset-on-collapse" denotes that children should be removed when
    * the node is collapsed.
-   *
-   * Since: 3.32
    */
   properties [PROP_RESET_ON_COLLAPSE] =
     g_param_spec_boolean ("reset-on-collapse",
@@ -546,8 +521,6 @@ ide_tree_node_class_init (IdeTreeNodeClass *klass)
    * object to assign to #IdeTreeNode:item.
    *
    * See ide_tree_node_is_tag() to match a tag when building.
-   *
-   * Since: 3.32
    */
   properties [PROP_TAG] =
     g_param_spec_string ("tag",
@@ -561,8 +534,6 @@ ide_tree_node_class_init (IdeTreeNodeClass *klass)
    *
    * If #TRUE, the "use-markup" property denotes that #IdeTreeNode:display-name
    * contains pango markup.
-   *
-   * Since: 3.32
    */
   properties [PROP_USE_MARKUP] =
     g_param_spec_boolean ("use-markup",
@@ -588,8 +559,6 @@ ide_tree_node_init (IdeTreeNode *self)
  * Gets the #IdeTreeNode:display-name property.
  *
  * Returns: (nullable): a string containing the display name
- *
- * Since: 3.32
  */
 const gchar *
 ide_tree_node_get_display_name (IdeTreeNode *self)
@@ -604,8 +573,6 @@ ide_tree_node_get_display_name (IdeTreeNode *self)
  *
  * Sets the #IdeTreeNode:display-name property, which is the text to
  * use when displaying the item in the tree.
- *
- * Since: 3.32
  */
 void
 ide_tree_node_set_display_name (IdeTreeNode *self,
@@ -629,8 +596,6 @@ ide_tree_node_set_display_name (IdeTreeNode *self,
  * Gets the icon associated with the tree node.
  *
  * Returns: (transfer none) (nullable): a #GIcon or %NULL
- *
- * Since: 3.32
  */
 GIcon *
 ide_tree_node_get_icon (IdeTreeNode *self)
@@ -646,8 +611,6 @@ ide_tree_node_get_icon (IdeTreeNode *self)
  * @icon: (nullable): a #GIcon or %NULL
  *
  * Sets the icon for the tree node.
- *
- * Since: 3.32
  */
 void
 ide_tree_node_set_icon (IdeTreeNode *self,
@@ -669,8 +632,6 @@ ide_tree_node_set_icon (IdeTreeNode *self,
  * Gets the expanded icon associated with the tree node.
  *
  * Returns: (transfer none) (nullable): a #GIcon or %NULL
- *
- * Since: 3.32
  */
 GIcon *
 ide_tree_node_get_expanded_icon (IdeTreeNode *self)
@@ -686,8 +647,6 @@ ide_tree_node_get_expanded_icon (IdeTreeNode *self)
  * @expanded_icon: (nullable): a #GIcon or %NULL
  *
  * Sets the expanded icon for the tree node.
- *
- * Since: 3.32
  */
 void
 ide_tree_node_set_expanded_icon (IdeTreeNode *self,
@@ -710,8 +669,6 @@ ide_tree_node_set_expanded_icon (IdeTreeNode *self,
  *
  * Returns: (transfer none) (type GObject.Object) (nullable): a #GObject
  *   if the item has been previously set.
- *
- * Since: 3.32
  */
 gpointer
 ide_tree_node_get_item (IdeTreeNode *self)
@@ -822,8 +779,6 @@ _ide_tree_node_set_model (IdeTreeNode  *self,
  * Prepends @child as a child of @self at the 0 index.
  *
  * This operation is O(1).
- *
- * Since: 3.32
  */
 void
 ide_tree_node_prepend (IdeTreeNode *self,
@@ -848,8 +803,6 @@ ide_tree_node_prepend (IdeTreeNode *self,
  * Appends @child as a child of @self at the last position.
  *
  * This operation is O(1).
- *
- * Since: 3.32
  */
 void
 ide_tree_node_append (IdeTreeNode *self,
@@ -875,8 +828,6 @@ ide_tree_node_append (IdeTreeNode *self,
  * Insert @child as a child of @self at the sorted position determined by @cmpfn
  *
  * This operation is O(n).
- *
- * Since: 3.32
  */
 void
 ide_tree_node_insert_sorted (IdeTreeNode        *self,
@@ -905,8 +856,6 @@ ide_tree_node_insert_sorted (IdeTreeNode        *self,
  * Inserts @child directly before @self by adding it to the parent of @self.
  *
  * This operation is O(1).
- *
- * Since: 3.32
  */
 void
 ide_tree_node_insert_before (IdeTreeNode *self,
@@ -932,8 +881,6 @@ ide_tree_node_insert_before (IdeTreeNode *self,
  * Inserts @child directly after @self by adding it to the parent of @self.
  *
  * This operation is O(1).
- *
- * Since: 3.32
  */
 void
 ide_tree_node_insert_after (IdeTreeNode *self,
@@ -959,8 +906,6 @@ ide_tree_node_insert_after (IdeTreeNode *self,
  * Removes the child node @child from @self. @self must be the parent of @child.
  *
  * This function is O(1).
- *
- * Since: 3.32
  */
 void
 ide_tree_node_remove (IdeTreeNode *self,
@@ -992,8 +937,6 @@ ide_tree_node_remove (IdeTreeNode *self,
  * Gets the parent node of @self.
  *
  * Returns: (transfer none) (nullable): a #IdeTreeNode or %NULL
- *
- * Since: 3.32
  */
 IdeTreeNode *
 ide_tree_node_get_parent (IdeTreeNode *self)
@@ -1011,8 +954,6 @@ ide_tree_node_get_parent (IdeTreeNode *self)
  * properties of each node.
  *
  * Returns: (transfer none) (nullable): a #IdeTreeNode or %NULL
- *
- * Since: 3.32
  */
 IdeTreeNode *
 ide_tree_node_get_root (IdeTreeNode *self)
@@ -1036,8 +977,6 @@ ide_tree_node_get_root (IdeTreeNode *self)
  * or is a subclass of @type.
  *
  * Returns: %TRUE if @self holds a @type item
- *
- * Since: 3.32
  */
 gboolean
 ide_tree_node_holds (IdeTreeNode *self,
@@ -1055,8 +994,6 @@ ide_tree_node_holds (IdeTreeNode *self,
  * Gets the position of the @self.
  *
  * Returns: the offset of @self with it's siblings.
- *
- * Since: 3.32
  */
 guint
 ide_tree_node_get_index (IdeTreeNode *self)
@@ -1077,8 +1014,6 @@ ide_tree_node_get_index (IdeTreeNode *self)
  * Gets the @nth child of the tree node or %NULL if it does not exist.
  *
  * Returns: (transfer none) (nullable): a #IdeTreeNode or %NULL
- *
- * Since: 3.32
  */
 IdeTreeNode *
 ide_tree_node_get_nth_child (IdeTreeNode *self,
@@ -1096,8 +1031,6 @@ ide_tree_node_get_nth_child (IdeTreeNode *self,
  * Gets the next sibling after @self.
  *
  * Returns: (transfer none) (nullable): a #IdeTreeNode or %NULL
- *
- * Since: 3.32
  */
 IdeTreeNode *
 ide_tree_node_get_next (IdeTreeNode *self)
@@ -1117,8 +1050,6 @@ ide_tree_node_get_next (IdeTreeNode *self)
  * Gets the previous sibling before @self.
  *
  * Returns: (transfer none) (nullable): a #IdeTreeNode or %NULL
- *
- * Since: 3.32
  */
 IdeTreeNode *
 ide_tree_node_get_previous (IdeTreeNode *self)
@@ -1139,8 +1070,6 @@ ide_tree_node_get_previous (IdeTreeNode *self)
  * It may not actually have children yet.
  *
  * Returns: %TRUE if the children may have children
- *
- * Since: 3.32
  */
 gboolean
 ide_tree_node_get_children_possible (IdeTreeNode *self)
@@ -1156,8 +1085,6 @@ ide_tree_node_get_children_possible (IdeTreeNode *self)
  * @children_possible: if children are possible
  *
  * Sets if the children are possible for the node.
- *
- * Since: 3.32
  */
 void
 ide_tree_node_set_children_possible (IdeTreeNode *self,
@@ -1196,8 +1123,6 @@ ide_tree_node_set_children_possible (IdeTreeNode *self,
  * Checks if @self has any children.
  *
  * Returns: %TRUE if @self has one or more children.
- *
- * Since: 3.32
  */
 gboolean
 ide_tree_node_has_child (IdeTreeNode *self)
@@ -1214,8 +1139,6 @@ ide_tree_node_has_child (IdeTreeNode *self)
  * Gets the number of children that @self contains.
  *
  * Returns: the number of children
- *
- * Since: 3.32
  */
 guint
 ide_tree_node_get_n_children (IdeTreeNode *self)
@@ -1235,8 +1158,6 @@ ide_tree_node_get_n_children (IdeTreeNode *self)
  * styling for group headers.
  *
  * Returns: %TRUE if @self is a header.
- *
- * Since: 3.32
  */
 gboolean
 ide_tree_node_get_is_header (IdeTreeNode *self)
@@ -1251,8 +1172,6 @@ ide_tree_node_get_is_header (IdeTreeNode *self)
  * @self: a #IdeTreeNode
  *
  * Sets the #IdeTreeNode:is-header property.
- *
- * Since: 3.32
  */
 void
 ide_tree_node_set_is_header (IdeTreeNode *self,
@@ -1343,8 +1262,6 @@ finish:
  * type, flags, and depth.
  *
  * Traversal is stopped if @traverse_func returns %TRUE.
- *
- * Since: 3.32
  */
 void
 ide_tree_node_traverse (IdeTreeNode         *self,
@@ -1381,8 +1298,6 @@ ide_tree_node_traverse (IdeTreeNode         *self,
  * "(Empty)" contents and show a proper expander arrow.
  *
  * Returns: %TRUE if @self is a synthesized empty node.
- *
- * Since: 3.32
  */
 gboolean
 ide_tree_node_is_empty (IdeTreeNode *self)
@@ -1415,8 +1330,6 @@ _ide_tree_node_set_needs_build_children (IdeTreeNode *self,
  * @icon_name: (nullable): the name of the icon, or %NULL
  *
  * Sets the #IdeTreeNode:icon property using an icon-name.
- *
- * Since: 3.32
  */
 void
 ide_tree_node_set_icon_name (IdeTreeNode *self,
@@ -1437,8 +1350,6 @@ ide_tree_node_set_icon_name (IdeTreeNode *self,
  * @expanded_icon_name: (nullable): the name of the icon, or %NULL
  *
  * Sets the #IdeTreeNode:icon property using an icon-name.
- *
- * Since: 3.32
  */
 void
 ide_tree_node_set_expanded_icon_name (IdeTreeNode *self,
@@ -1460,8 +1371,6 @@ ide_tree_node_set_expanded_icon_name (IdeTreeNode *self,
  * Checks if @self is the root node, meaning it has no parent.
  *
  * Returns: %TRUE if @self has no parent.
- *
- * Since: 3.32
  */
 gboolean
 ide_tree_node_is_root (IdeTreeNode *self)
@@ -1478,8 +1387,6 @@ ide_tree_node_is_root (IdeTreeNode *self)
  * Checks if @self is the first sibling.
  *
  * Returns: %TRUE if @self is the first sibling
- *
- * Since: 3.32
  */
 gboolean
 ide_tree_node_is_first (IdeTreeNode *self)
@@ -1496,8 +1403,6 @@ ide_tree_node_is_first (IdeTreeNode *self)
  * Checks if @self is the last sibling.
  *
  * Returns: %TRUE if @self is the last sibling
- *
- * Since: 3.32
  */
 gboolean
 ide_tree_node_is_last (IdeTreeNode *self)
@@ -1606,8 +1511,6 @@ _ide_tree_node_remove_all (IdeTreeNode *self)
  * Checks if the node should have all children removed when collapsed.
  *
  * Returns: %TRUE if children are removed on collapse
- *
- * Since: 3.32
  */
 gboolean
 ide_tree_node_get_reset_on_collapse (IdeTreeNode *self)
@@ -1623,8 +1526,6 @@ ide_tree_node_get_reset_on_collapse (IdeTreeNode *self)
  * @reset_on_collapse: if the children should be removed on collapse
  *
  * If %TRUE, then children will be removed when the row is collapsed.
- *
- * Since: 3.32
  */
 void
 ide_tree_node_set_reset_on_collapse (IdeTreeNode *self,
@@ -1648,8 +1549,6 @@ ide_tree_node_set_reset_on_collapse (IdeTreeNode *self,
  * Gets the path for the tree node.
  *
  * Returns: (transfer full) (nullable): a path or %NULL
- *
- * Since: 3.32
  */
 GtkTreePath *
 ide_tree_node_get_path (IdeTreeNode *self)
@@ -1664,10 +1563,10 @@ ide_tree_node_get_path (IdeTreeNode *self)
   return NULL;
 }
 
-static void
-ide_tree_node_get_area (IdeTreeNode  *node,
-                        IdeTree      *tree,
-                        GdkRectangle *area)
+void
+_ide_tree_node_get_area (IdeTreeNode  *node,
+                         IdeTree      *tree,
+                         GdkRectangle *area)
 {
   GtkTreeViewColumn *column;
   g_autoptr(GtkTreePath) path = NULL;
@@ -1692,6 +1591,7 @@ static gboolean
 ide_tree_node_show_popover_timeout_cb (gpointer data)
 {
   PopupRequest *popreq = data;
+  GtkWidget *positioner;
   GdkRectangle rect;
   GtkAllocation alloc;
 
@@ -1699,7 +1599,7 @@ ide_tree_node_show_popover_timeout_cb (gpointer data)
   g_assert (IDE_IS_TREE_NODE (popreq->self));
   g_assert (GTK_IS_POPOVER (popreq->popover));
 
-  ide_tree_node_get_area (popreq->self, popreq->tree, &rect);
+  _ide_tree_node_get_area (popreq->self, popreq->tree, &rect);
   gtk_widget_get_allocation (GTK_WIDGET (popreq->tree), &alloc);
 
   if ((rect.x + rect.width) > (alloc.x + alloc.width))
@@ -1723,9 +1623,11 @@ ide_tree_node_show_popover_timeout_cb (gpointer data)
       break;
     }
 
-  gtk_popover_set_relative_to (popreq->popover, GTK_WIDGET (popreq->tree));
-  gtk_popover_set_pointing_to (popreq->popover, &rect);
-  gtk_popover_popup (popreq->popover);
+  if ((positioner = gtk_widget_get_ancestor (GTK_WIDGET (popreq->tree), IDE_TYPE_POPOVER_POSITIONER)))
+    ide_popover_positioner_present (IDE_POPOVER_POSITIONER (positioner),
+                                    popreq->popover,
+                                    GTK_WIDGET (popreq->tree),
+                                    &rect);
 
   g_clear_object (&popreq->self);
   g_clear_object (&popreq->popover);
@@ -1742,13 +1644,14 @@ _ide_tree_node_show_popover (IdeTreeNode *self,
   GdkRectangle cell_area;
   GdkRectangle visible_rect;
   PopupRequest *popreq;
+  GtkAdjustment *vadj;
 
   g_return_if_fail (IDE_IS_TREE_NODE (self));
   g_return_if_fail (IDE_IS_TREE (tree));
   g_return_if_fail (GTK_IS_POPOVER (popover));
 
   gtk_tree_view_get_visible_rect (GTK_TREE_VIEW (tree), &visible_rect);
-  ide_tree_node_get_area (self, tree, &cell_area);
+  _ide_tree_node_get_area (self, tree, &cell_area);
   gtk_tree_view_convert_bin_window_to_tree_coords (GTK_TREE_VIEW (tree),
                                                    cell_area.x,
                                                    cell_area.y,
@@ -1760,37 +1663,19 @@ _ide_tree_node_show_popover (IdeTreeNode *self,
   popreq->tree = g_object_ref (tree);
   popreq->popover = g_object_ref (popover);
 
-  /*
-   * If the node is not on screen, we need to animate until we get there.
+  vadj = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (tree));
+
+  /* Animating to position in GTK appears to break, so just manually
+   * scroll to the right position.
    */
-  if ((cell_area.y < visible_rect.y) ||
-      ((cell_area.y + cell_area.height) >
-       (visible_rect.y + visible_rect.height)))
-    {
-      GtkTreePath *path;
-
-      path = ide_tree_node_get_path (self);
-      gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (tree), path, NULL, FALSE, 0, 0);
-      g_clear_pointer (&path, gtk_tree_path_free);
-
-      /*
-       * FIXME: Time period comes from gtk animation duration.
-       *        Not curently available in pubic API.
-       *        We need to be greater than the max timeout it
-       *        could take to move, since we must have it
-       *        on screen by then.
-       *
-       *        One alternative might be to check the result
-       *        and if we are still not on screen, then just
-       *        pin it to a row-height from the top or bottom.
-       */
-      g_timeout_add (300,
-                     ide_tree_node_show_popover_timeout_cb,
-                     popreq);
-
-      return;
-    }
+  if (cell_area.y < visible_rect.y)
+    gtk_adjustment_set_value (vadj, cell_area.y);
+  else if (cell_area.y + cell_area.height > visible_rect.y + visible_rect.height)
+    gtk_adjustment_set_value (vadj, cell_area.y + cell_area.height - visible_rect.height);
 
+  /* FIXME: We could get rid of our allocated state here now that we
+   * no longer animate because of breakage in GTK.
+   */
   ide_tree_node_show_popover_timeout_cb (g_steal_pointer (&popreq));
 }
 
diff --git a/src/libide/tree/ide-tree-node.h b/src/libide/tree/ide-tree-node.h
index f7e859173..c76dfaae0 100644
--- a/src/libide/tree/ide-tree-node.h
+++ b/src/libide/tree/ide-tree-node.h
@@ -26,7 +26,7 @@ G_BEGIN_DECLS
 
 #define IDE_TYPE_TREE_NODE (ide_tree_node_get_type())
 
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 G_DECLARE_FINAL_TYPE (IdeTreeNode, ide_tree_node, IDE, TREE_NODE, GObject)
 
 typedef enum
@@ -53,8 +53,6 @@ typedef enum
  * This function prototype is used to traverse a tree of #IdeTreeNode.
  *
  * Returns: #IdeTreeNodeVisit, %IDE_TREE_NODE_VISIT_BREAK to stop traversal.
- *
- * Since: 3.32
  */
 typedef IdeTreeNodeVisit (*IdeTreeTraverseFunc) (IdeTreeNode *node,
                                                  gpointer     user_data);
@@ -69,148 +67,146 @@ typedef IdeTreeNodeVisit (*IdeTreeTraverseFunc) (IdeTreeNode *node,
  * This callback function is a convenience wrapper around GCompareFunc
  *
  * Returns: int
- *
- * Since: 3.32
  */
 typedef int (*IdeTreeNodeCompare) (IdeTreeNode *node,
                                    IdeTreeNode *child);
 
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 IdeTreeNode      *ide_tree_node_new                    (void);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 gboolean          ide_tree_node_get_has_error          (IdeTreeNode         *self);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 void              ide_tree_node_set_has_error          (IdeTreeNode         *self,
                                                         gboolean             has_error);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 const gchar      *ide_tree_node_get_tag                (IdeTreeNode         *self);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 void              ide_tree_node_set_tag                (IdeTreeNode         *self,
                                                         const gchar         *tag);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 gboolean          ide_tree_node_is_tag                 (IdeTreeNode         *self,
                                                         const gchar         *tag);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 GtkTreePath      *ide_tree_node_get_path               (IdeTreeNode         *self);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 const gchar      *ide_tree_node_get_display_name       (IdeTreeNode         *self);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 void              ide_tree_node_set_display_name       (IdeTreeNode         *self,
                                                         const gchar         *display_name);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 gboolean          ide_tree_node_get_is_header          (IdeTreeNode         *self);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 void              ide_tree_node_set_is_header          (IdeTreeNode         *self,
                                                         gboolean             header);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 GIcon            *ide_tree_node_get_icon               (IdeTreeNode         *self);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 void              ide_tree_node_set_icon               (IdeTreeNode         *self,
                                                         GIcon               *icon);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 void              ide_tree_node_set_icon_name          (IdeTreeNode         *self,
                                                         const gchar         *icon_name);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 GIcon            *ide_tree_node_get_expanded_icon      (IdeTreeNode         *self);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 void              ide_tree_node_set_expanded_icon      (IdeTreeNode         *self,
                                                         GIcon               *expanded_icon);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 void              ide_tree_node_set_expanded_icon_name (IdeTreeNode         *self,
                                                         const gchar         *expanded_icon_name);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 gpointer          ide_tree_node_get_item               (IdeTreeNode         *self);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 void              ide_tree_node_set_item               (IdeTreeNode         *self,
                                                         gpointer             item);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 gboolean          ide_tree_node_get_children_possible  (IdeTreeNode         *self);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 void              ide_tree_node_set_children_possible  (IdeTreeNode         *self,
                                                         gboolean             children_possible);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 gboolean          ide_tree_node_is_empty               (IdeTreeNode         *self);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 gboolean          ide_tree_node_has_child              (IdeTreeNode         *self);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 guint             ide_tree_node_get_n_children         (IdeTreeNode         *self);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 IdeTreeNode      *ide_tree_node_get_next               (IdeTreeNode         *self);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 IdeTreeNode      *ide_tree_node_get_previous           (IdeTreeNode         *self);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 guint             ide_tree_node_get_index              (IdeTreeNode         *self);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 IdeTreeNode      *ide_tree_node_get_nth_child          (IdeTreeNode         *self,
                                                         guint                index_);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 void              ide_tree_node_prepend                (IdeTreeNode         *self,
                                                         IdeTreeNode         *child);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 void              ide_tree_node_append                 (IdeTreeNode         *self,
                                                         IdeTreeNode         *child);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 void              ide_tree_node_insert_sorted          (IdeTreeNode         *self,
                                                         IdeTreeNode         *child,
                                                         IdeTreeNodeCompare   cmpfn);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 void              ide_tree_node_insert_before          (IdeTreeNode         *self,
                                                         IdeTreeNode         *child);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 void              ide_tree_node_insert_after           (IdeTreeNode         *self,
                                                         IdeTreeNode         *child);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 void              ide_tree_node_remove                 (IdeTreeNode         *self,
                                                         IdeTreeNode         *child);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 IdeTreeNode      *ide_tree_node_get_parent             (IdeTreeNode         *self);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 gboolean          ide_tree_node_is_root                (IdeTreeNode         *self);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 gboolean          ide_tree_node_is_first               (IdeTreeNode         *self);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 gboolean          ide_tree_node_is_last                (IdeTreeNode         *self);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 IdeTreeNode      *ide_tree_node_get_root               (IdeTreeNode         *self);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 gboolean          ide_tree_node_holds                  (IdeTreeNode         *self,
                                                         GType                type);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 void              ide_tree_node_traverse               (IdeTreeNode         *self,
                                                         GTraverseType        traverse_type,
                                                         GTraverseFlags       traverse_flags,
                                                         gint                 max_depth,
                                                         IdeTreeTraverseFunc  traverse_func,
                                                         gpointer             user_data);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 void              ide_tree_node_add_emblem             (IdeTreeNode         *self,
                                                         GEmblem             *emblem);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 gboolean          ide_tree_node_get_reset_on_collapse  (IdeTreeNode         *self);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 void              ide_tree_node_set_reset_on_collapse  (IdeTreeNode         *self,
                                                         gboolean             reset_on_collapse);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 const GdkRGBA    *ide_tree_node_get_background_rgba    (IdeTreeNode         *self);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 void              ide_tree_node_set_background_rgba    (IdeTreeNode         *self,
                                                         const GdkRGBA       *background_rgba);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 const GdkRGBA    *ide_tree_node_get_foreground_rgba    (IdeTreeNode         *self);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 void              ide_tree_node_set_foreground_rgba    (IdeTreeNode         *self,
                                                         const GdkRGBA       *foreground_rgba);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 gboolean          ide_tree_node_is_selected            (IdeTreeNode         *self);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 gboolean          ide_tree_node_get_use_markup         (IdeTreeNode         *self);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 void              ide_tree_node_set_use_markup         (IdeTreeNode         *self,
                                                         gboolean             use_markup);
-IDE_AVAILABLE_IN_3_34
+IDE_AVAILABLE_IN_ALL
 void              ide_tree_node_set_flags              (IdeTreeNode         *self,
                                                         IdeTreeNodeFlags     flags);
-IDE_AVAILABLE_IN_3_34
+IDE_AVAILABLE_IN_ALL
 IdeTreeNodeFlags  ide_tree_node_get_flags              (IdeTreeNode         *self);
 
 G_END_DECLS
diff --git a/src/libide/tree/ide-tree-private.h b/src/libide/tree/ide-tree-private.h
index 77968a522..5b60d0e63 100644
--- a/src/libide/tree/ide-tree-private.h
+++ b/src/libide/tree/ide-tree-private.h
@@ -66,5 +66,8 @@ GIcon        *_ide_tree_node_apply_emblems            (IdeTreeNode     *self,
                                                        GIcon           *base);
 void          _ide_tree_node_apply_colors             (IdeTreeNode     *self,
                                                        GtkCellRenderer *cell);
+void          _ide_tree_node_get_area                 (IdeTreeNode     *self,
+                                                       IdeTree         *tree,
+                                                       GdkRectangle    *area);
 
 G_END_DECLS
diff --git a/src/libide/tree/ide-tree.c b/src/libide/tree/ide-tree.c
index 2c6dce4ec..88b81ff2e 100644
--- a/src/libide/tree/ide-tree.c
+++ b/src/libide/tree/ide-tree.c
@@ -27,11 +27,19 @@
 #include <libide-threading.h>
 
 #include "ide-cell-renderer-status.h"
+#include "ide-popover-positioner.h"
 #include "ide-tree.h"
 #include "ide-tree-model.h"
 #include "ide-tree-node.h"
 #include "ide-tree-private.h"
 
+/* FIXME-GTK4:
+ *
+ * We still need DnD work here but that has changed so much
+ * that it would help to have a plugin using this to ensure
+ * we're porting it correctly.
+ */
+
 typedef struct
 {
   /* This #GCancellable will be automatically cancelled when the widget is
@@ -136,7 +144,7 @@ state_cell_func (GtkCellLayout   *layout,
   if ((node = ide_tree_model_get_node (IDE_TREE_MODEL (model), iter)))
     flags = ide_tree_node_get_flags (node);
 
-  ide_cell_renderer_status_set_flags (IDE_CELL_RENDERER_STATUS (cell), flags);
+  ide_cell_renderer_status_set_flags (cell, flags);
 }
 
 static void
@@ -392,105 +400,87 @@ ide_tree_row_collapsed (GtkTreeView *tree_view,
 static void
 ide_tree_popup (IdeTree        *self,
                 IdeTreeNode    *node,
-                GdkEventButton *event,
-                gint            target_x,
-                gint            target_y)
+                double          target_x,
+                double          target_y)
 {
   IdeTreePrivate *priv = ide_tree_get_instance_private (self);
-  const GdkRectangle area = { target_x, target_y, 0, 0 };
+  GdkRectangle area;
+  GtkTextDirection dir;
+  GtkWidget *positioner;
 
   g_assert (IDE_IS_TREE (self));
   g_assert (IDE_IS_TREE_NODE (node));
-  g_assert (priv->popover != NULL);
 
-  gtk_popover_set_pointing_to (priv->popover, &area);
+  if (priv->context_menu == NULL)
+    return;
+
+  if (!(positioner = gtk_widget_get_ancestor (GTK_WIDGET (self), IDE_TYPE_POPOVER_POSITIONER)))
+    return;
+
+  priv->popover = GTK_POPOVER (gtk_popover_menu_new_from_model (G_MENU_MODEL (priv->context_menu)));
+  g_object_ref_sink (priv->popover);
+
+  dir = gtk_widget_get_direction (GTK_WIDGET (self));
+  gtk_popover_set_position (priv->popover, dir == GTK_TEXT_DIR_LTR ? GTK_POS_RIGHT : GTK_POS_LEFT);
 
-  ide_tree_show_popover_at_node (self, node, priv->popover);
+  _ide_tree_node_get_area (node, self, &area);
+
+  ide_popover_positioner_present (IDE_POPOVER_POSITIONER (positioner),
+                                  priv->popover,
+                                  GTK_WIDGET (self),
+                                  &area);
 }
 
-static gboolean
-ide_tree_button_press_event (GtkWidget      *widget,
-                             GdkEventButton *event)
+static void
+ide_tree_click_pressed_cb (IdeTree         *self,
+                           int              n_presses,
+                           double           x,
+                           double           y,
+                           GtkGestureClick *gesture)
 {
-  IdeTree *self = (IdeTree *)widget;
+  g_autoptr(GtkTreePath) path = NULL;
   IdeTreeModel *model;
+  int cell_y;
 
   g_assert (IDE_IS_TREE (self));
-  g_assert (event != NULL);
+  g_assert (GTK_IS_GESTURE_CLICK (gesture));
 
-  if ((model = ide_tree_get_model (self)) &&
-      (event->type == GDK_BUTTON_PRESS) &&
-      (event->button == GDK_BUTTON_SECONDARY))
-    {
-      g_autoptr(GtkTreePath) path = NULL;
-      gint cell_y;
+  if (!(model = ide_tree_get_model (self)))
+    return;
 
-      if (!gtk_widget_has_focus (GTK_WIDGET (self)))
-        gtk_widget_grab_focus (GTK_WIDGET (self));
+  if (!gtk_widget_has_focus (GTK_WIDGET (self)))
+    gtk_widget_grab_focus (GTK_WIDGET (self));
 
-      gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (self),
-                                     event->x,
-                                     event->y,
-                                     &path,
-                                     NULL,
-                                     NULL,
-                                     &cell_y);
+  gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (self),
+                                 x,
+                                 y,
+                                 &path,
+                                 NULL,
+                                 NULL,
+                                 &cell_y);
 
-      if (path == NULL)
-        {
-          ide_tree_unselect (self);
-        }
-      else
-        {
-          GtkAllocation alloc;
-          GtkTreeIter iter;
+  if (path == NULL)
+    {
+      ide_tree_unselect (self);
+    }
+  else
+    {
+      GtkAllocation alloc;
+      GtkTreeIter iter;
 
-          gtk_widget_get_allocation (GTK_WIDGET (self), &alloc);
+      gtk_widget_get_allocation (GTK_WIDGET (self), &alloc);
 
-          if (gtk_tree_model_get_iter (GTK_TREE_MODEL (model), &iter, path))
-            {
-              IdeTreeNode *node;
+      if (gtk_tree_model_get_iter (GTK_TREE_MODEL (model), &iter, path))
+        {
+          IdeTreeNode *node;
 
-              node = ide_tree_model_get_node (IDE_TREE_MODEL (model), &iter);
-              ide_tree_select (self, node);
-              ide_tree_popup (self, node, event, alloc.x + alloc.width, event->y - cell_y);
-            }
+          node = ide_tree_model_get_node (IDE_TREE_MODEL (model), &iter);
+          ide_tree_select (self, node);
+          ide_tree_popup (self, node, alloc.x + alloc.width, y - cell_y);
         }
-
-      return GDK_EVENT_STOP;
     }
 
-  return GTK_WIDGET_CLASS (ide_tree_parent_class)->button_press_event (widget, event);
-}
-
-static gboolean
-ide_tree_drag_motion (GtkWidget      *widget,
-                      GdkDragContext *context,
-                      gint            x,
-                      gint            y,
-                      guint           time_)
-{
-  IdeTree *self = (IdeTree *)widget;
-  IdeTreePrivate *priv = ide_tree_get_instance_private (self);
-  gboolean ret;
-
-  g_assert (IDE_IS_MAIN_THREAD ());
-  g_assert (IDE_IS_TREE (self));
-  g_assert (context != NULL);
-
-  ret = GTK_WIDGET_CLASS (ide_tree_parent_class)->drag_motion (widget, context, x, y, time_);
-
-  /*
-   * Cache the current drop position so we can use it
-   * later to determine how to drop on a given node.
-   */
-  g_clear_pointer (&priv->drop_path, gtk_tree_path_free);
-  gtk_tree_view_get_drag_dest_row (GTK_TREE_VIEW (self), &priv->drop_path, &priv->drop_pos);
-
-  /* Save the drag action for builders dispatch */
-  priv->drop_action = gdk_drag_context_get_selected_action (context);
-
-  return ret;
+  gtk_gesture_set_state (GTK_GESTURE (gesture),  GTK_EVENT_SEQUENCE_CLAIMED);
 }
 
 static gboolean
@@ -520,8 +510,8 @@ ide_tree_query_tooltip (GtkWidget  *widget,
           if (node != NULL)
             {
               gtk_tree_view_set_tooltip_row (tree_view, tooltip, path);
-              gtk_tooltip_set_text (tooltip,
-                                    ide_tree_node_get_display_name (node));
+              gtk_tooltip_set_markup (tooltip,
+                                      ide_tree_node_get_display_name (node));
               return TRUE;
             }
         }
@@ -530,29 +520,10 @@ ide_tree_query_tooltip (GtkWidget  *widget,
   return FALSE;
 }
 
-static gboolean
-ide_tree_popup_menu_cb (IdeTree *tree,
-                        gpointer user_data)
-{
-  IdeTreePrivate *priv = ide_tree_get_instance_private (tree);
-  IdeTreeNode *selected_node = NULL;
-
-  g_assert (priv != NULL);
-
-  selected_node = ide_tree_get_selected_node (tree);
-
-  if (selected_node)
-    {
-      ide_tree_show_popover_at_node (tree, selected_node, priv->popover);
-      return TRUE;
-    }
-  return FALSE;
-}
-
 static void
-ide_tree_destroy (GtkWidget *widget)
+ide_tree_dispose (GObject *object)
 {
-  IdeTree *self = (IdeTree *)widget;
+  IdeTree *self = (IdeTree *)object;
   IdeTreePrivate *priv = ide_tree_get_instance_private (self);
   IdeTreeModel *model;
 
@@ -561,8 +532,7 @@ ide_tree_destroy (GtkWidget *widget)
   if ((model = ide_tree_get_model (self)))
     _ide_tree_model_release_addins (model);
 
-  if (priv->popover != NULL)
-    gtk_widget_destroy (GTK_WIDGET (priv->popover));
+  g_clear_object (&priv->popover);
 
   gtk_tree_view_set_model (GTK_TREE_VIEW (self), NULL);
 
@@ -575,18 +545,18 @@ ide_tree_destroy (GtkWidget *widget)
   g_clear_pointer (&priv->header_attributes, pango_attr_list_unref);
   g_clear_pointer (&priv->drop_path, gtk_tree_path_free);
 
-  GTK_WIDGET_CLASS (ide_tree_parent_class)->destroy (widget);
+  G_OBJECT_CLASS (ide_tree_parent_class)->dispose (object);
 }
 
 static void
 ide_tree_class_init (IdeTreeClass *klass)
 {
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
   GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
 
-  widget_class->destroy = ide_tree_destroy;
-  widget_class->button_press_event = ide_tree_button_press_event;
-  widget_class->drag_motion = ide_tree_drag_motion;
+  object_class->dispose = ide_tree_dispose;
+
   widget_class->query_tooltip = ide_tree_query_tooltip;
 
   tree_view_class->row_activated = ide_tree_row_activated;
@@ -598,9 +568,20 @@ static void
 ide_tree_init (IdeTree *self)
 {
   IdeTreePrivate *priv = ide_tree_get_instance_private (self);
+  GtkGesture *gesture;
   GtkCellRenderer *cell;
   GtkTreeViewColumn *column;
 
+  /* Show popover on right-click */
+  gesture = gtk_gesture_click_new ();
+  gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (gesture), 3);
+  g_signal_connect_object (gesture,
+                           "pressed",
+                           G_CALLBACK (ide_tree_click_pressed_cb),
+                           self,
+                           G_CONNECT_SWAPPED);
+  gtk_widget_add_controller (GTK_WIDGET (self), GTK_EVENT_CONTROLLER (gesture));
+
   priv->cancellable = g_cancellable_new ();
 
   gtk_widget_set_has_tooltip (GTK_WIDGET (self), TRUE);
@@ -610,11 +591,6 @@ ide_tree_init (IdeTree *self)
                            G_CALLBACK (ide_tree_selection_changed_cb),
                            self,
                            G_CONNECT_SWAPPED);
-  g_signal_connect_object (self,
-                           "popup-menu",
-                           G_CALLBACK (ide_tree_popup_menu_cb),
-                           NULL,
-                           0);
 
   gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (self), FALSE);
   gtk_tree_view_set_activate_on_single_click (GTK_TREE_VIEW (self), TRUE);
@@ -629,11 +605,12 @@ ide_tree_init (IdeTree *self)
 
   cell = g_object_new (GTK_TYPE_CELL_RENDERER_TEXT,
                        "ellipsize", PANGO_ELLIPSIZE_END,
+                       "ypad", 6,
                        NULL);
   gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (column), cell, TRUE);
   gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (column), cell, text_cell_func, self, NULL);
 
-  cell = g_object_new (IDE_TYPE_CELL_RENDERER_STATUS, NULL);
+  cell = ide_cell_renderer_status_new ();
   gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (column), cell, state_cell_func, self, NULL);
   gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (column), cell, FALSE);
 
@@ -664,21 +641,7 @@ ide_tree_set_context_menu (IdeTree *self,
   g_return_if_fail (!menu || G_IS_MENU (menu));
 
   if (g_set_object (&priv->context_menu, menu))
-    {
-      GtkTextDirection dir;
-
-      if (priv->popover != NULL)
-        gtk_widget_destroy (GTK_WIDGET (priv->popover));
-
-      priv->popover = GTK_POPOVER (gtk_popover_new_from_model (GTK_WIDGET (self),
-                                                               G_MENU_MODEL (priv->context_menu)));
-      dir = gtk_widget_get_direction (GTK_WIDGET (self));
-      gtk_popover_set_position (priv->popover, dir == GTK_TEXT_DIR_LTR ? GTK_POS_RIGHT : GTK_POS_LEFT);
-      g_signal_connect (priv->popover,
-                        "destroy",
-                        G_CALLBACK (gtk_widget_destroyed),
-                        &priv->popover);
-    }
+    g_clear_object (&priv->popover);
 }
 
 void
@@ -689,6 +652,12 @@ ide_tree_show_popover_at_node (IdeTree     *self,
   g_return_if_fail (IDE_IS_TREE (self));
   g_return_if_fail (IDE_IS_TREE_NODE (node));
   g_return_if_fail (GTK_IS_POPOVER (popover));
+  g_return_if_fail (gtk_widget_get_parent (GTK_WIDGET (popover)) == NULL);
+
+#if 0
+  /* Once this is in GTK, uncomment */
+  gtk_widget_set_action_parent (GTK_WIDGET (popover), GTK_WIDGET (self));
+#endif
 
   _ide_tree_node_show_popover (node, self, popover);
 }
@@ -700,8 +669,6 @@ ide_tree_show_popover_at_node (IdeTree     *self,
  * Gets the currently selected node, or %NULL
  *
  * Returns: (transfer none) (nullable): an #IdeTreeNode or %NULL
- *
- * Since: 3.32
  */
 IdeTreeNode *
 ide_tree_get_selected_node (IdeTree *self)
diff --git a/src/libide/tree/ide-tree.h b/src/libide/tree/ide-tree.h
index 0b4c16b6e..c9e3d8a51 100644
--- a/src/libide/tree/ide-tree.h
+++ b/src/libide/tree/ide-tree.h
@@ -21,6 +21,7 @@
 #pragma once
 
 #include <gtk/gtk.h>
+
 #include <libide-core.h>
 
 #include "ide-tree-node.h"
@@ -29,7 +30,7 @@ G_BEGIN_DECLS
 
 #define IDE_TYPE_TREE (ide_tree_get_type())
 
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 G_DECLARE_DERIVABLE_TYPE (IdeTree, ide_tree, IDE, TREE, GtkTreeView)
 
 struct _IdeTreeClass
@@ -37,31 +38,31 @@ struct _IdeTreeClass
   GtkTreeViewClass parent_type;
 
   /*< private >*/
-  gpointer _reserved[16];
+  gpointer _reserved[8];
 };
 
-IDE_AVAILABLE_IN_3_32
-GtkWidget   *ide_tree_new                  (void);
-IDE_AVAILABLE_IN_3_32
-void         ide_tree_set_context_menu     (IdeTree     *self,
-                                            GMenu       *menu);
-IDE_AVAILABLE_IN_3_32
-void         ide_tree_show_popover_at_node (IdeTree     *self,
-                                            IdeTreeNode *node,
-                                            GtkPopover  *popover);
-IDE_AVAILABLE_IN_3_32
-IdeTreeNode *ide_tree_get_selected_node    (IdeTree     *self);
-IDE_AVAILABLE_IN_3_32
-void         ide_tree_select_node          (IdeTree     *self,
-                                            IdeTreeNode *node);
-IDE_AVAILABLE_IN_3_32
-void         ide_tree_expand_node          (IdeTree     *self,
-                                            IdeTreeNode *node);
-IDE_AVAILABLE_IN_3_32
-void         ide_tree_collapse_node        (IdeTree     *self,
-                                            IdeTreeNode *node);
-IDE_AVAILABLE_IN_3_32
-gboolean     ide_tree_node_expanded        (IdeTree     *self,
-                                            IdeTreeNode *node);
+IDE_AVAILABLE_IN_ALL
+GtkWidget     *ide_tree_new                  (void);
+IDE_AVAILABLE_IN_ALL
+void           ide_tree_set_context_menu     (IdeTree     *self,
+                                              GMenu       *menu);
+IDE_AVAILABLE_IN_ALL
+void           ide_tree_show_popover_at_node (IdeTree     *self,
+                                              IdeTreeNode *node,
+                                              GtkPopover  *popover);
+IDE_AVAILABLE_IN_ALL
+IdeTreeNode   *ide_tree_get_selected_node    (IdeTree     *self);
+IDE_AVAILABLE_IN_ALL
+void           ide_tree_select_node          (IdeTree     *self,
+                                              IdeTreeNode *node);
+IDE_AVAILABLE_IN_ALL
+void           ide_tree_expand_node          (IdeTree     *self,
+                                              IdeTreeNode *node);
+IDE_AVAILABLE_IN_ALL
+void           ide_tree_collapse_node        (IdeTree     *self,
+                                              IdeTreeNode *node);
+IDE_AVAILABLE_IN_ALL
+gboolean       ide_tree_node_expanded        (IdeTree     *self,
+                                              IdeTreeNode *node);
 
 G_END_DECLS
diff --git a/src/libide/tree/libide-tree.h b/src/libide/tree/libide-tree.h
index 515828d11..be4915a14 100644
--- a/src/libide/tree/libide-tree.h
+++ b/src/libide/tree/libide-tree.h
@@ -25,12 +25,11 @@
 G_BEGIN_DECLS
 
 #define IDE_TREE_INSIDE
-
-#include "ide-tree.h"
-#include "ide-tree-addin.h"
-#include "ide-tree-model.h"
-#include "ide-tree-node.h"
-
+# include "ide-popover-positioner.h"
+# include "ide-tree.h"
+# include "ide-tree-addin.h"
+# include "ide-tree-model.h"
+# include "ide-tree-node.h"
 #undef IDE_TREE_INSIDE
 
 G_END_DECLS
diff --git a/src/libide/tree/meson.build b/src/libide/tree/meson.build
index 5a591106c..d0a99b73d 100644
--- a/src/libide/tree/meson.build
+++ b/src/libide/tree/meson.build
@@ -6,6 +6,7 @@ libide_include_directories += include_directories('.')
 #
 
 libide_tree_public_headers = [
+  'ide-popover-positioner.h',
   'ide-tree.h',
   'ide-tree-addin.h',
   'ide-tree-model.h',
@@ -20,6 +21,7 @@ install_headers(libide_tree_public_headers, subdir: libide_tree_header_subdir)
 #
 
 libide_tree_public_sources = [
+  'ide-popover-positioner.c',
   'ide-tree.c',
   'ide-tree-addin.c',
   'ide-tree-model.c',


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]