[gtk/kill-tree-menu: 2/2] combobox: Replace GtkTreeMenu with a popover



commit 43b184af76fe8f2c7478a2a572b3d6e35d50fa8d
Author: Matthias Clasen <mclasen redhat com>
Date:   Thu Dec 26 00:06:48 2019 -0500

    combobox: Replace GtkTreeMenu with a popover
    
    This does not currently try to reproduce the exact
    placement, since GtkPopover doesn't have to have
    the necessary placement hints.

 gtk/gtkcombobox.c           |  129 ++--
 gtk/gtktreemenu.c           | 1367 -------------------------------------------
 gtk/gtktreemenu.h           |  100 ----
 gtk/gtktreepopover.c        |  868 +++++++++++++++++++++++++++
 gtk/gtktreepopoverprivate.h |   43 ++
 gtk/meson.build             |    2 +-
 gtk/ui/gtkcombobox.ui       |    4 +-
 7 files changed, 950 insertions(+), 1563 deletions(-)
---
diff --git a/gtk/gtkcombobox.c b/gtk/gtkcombobox.c
index c10a6835ce..caf9a04f3e 100644
--- a/gtk/gtkcombobox.c
+++ b/gtk/gtkcombobox.c
@@ -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/gtktreepopover.c b/gtk/gtktreepopover.c
new file mode 100644
index 0000000000..a9803a7d87
--- /dev/null
+++ b/gtk/gtktreepopover.c
@@ -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
index 0000000000..a43bfcb15f
--- /dev/null
+++ b/gtk/gtktreepopoverprivate.h
@@ -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__ */
diff --git a/gtk/meson.build b/gtk/meson.build
index 827c7b2708..00a3068864 100644
--- a/gtk/meson.build
+++ b/gtk/meson.build
@@ -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',
diff --git a/gtk/ui/gtkcombobox.ui b/gtk/ui/gtkcombobox.ui
index 41b5754b75..24cafedb21 100644
--- a/gtk/ui/gtkcombobox.ui
+++ b/gtk/ui/gtkcombobox.ui
@@ -24,8 +24,10 @@
     </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"/>



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