[gtk/nested-popover-menu: 3/5] Add nesting popover menus



commit ea44eade21a896c75b02bf8a148ba726f248f965
Author: Matthias Clasen <mclasen redhat com>
Date:   Tue Aug 27 09:28:29 2019 +0100

    Add nesting popover menus
    
    Add a variant of popover menus that are nesting
    like traditional menus. This is a better fit for
    replacing traditional main menus.

 docs/reference/gtk/gtk4-sections.txt |   2 +
 gtk/gtkmenusectionbox.c              |  82 +++++++++++------
 gtk/gtkmenusectionboxprivate.h       |   5 +-
 gtk/gtkmodelbutton.c                 | 167 ++++++++++++++++++++++++++++-------
 gtk/gtkpopover.c                     |  16 +++-
 gtk/gtkpopovermenu.c                 | 148 +++++++++++++++++++++++++++++--
 gtk/gtkpopovermenu.h                 |  18 ++++
 gtk/gtkpopovermenubar.c              |   6 +-
 gtk/gtkpopovermenuprivate.h          |  11 ++-
 tests/popover2.ui                    |  38 +++-----
 tests/testpopover.c                  |  10 +++
 11 files changed, 407 insertions(+), 96 deletions(-)
---
diff --git a/docs/reference/gtk/gtk4-sections.txt b/docs/reference/gtk/gtk4-sections.txt
index b72906d7ab..1b24d886da 100644
--- a/docs/reference/gtk/gtk4-sections.txt
+++ b/docs/reference/gtk/gtk4-sections.txt
@@ -1787,6 +1787,8 @@ gtk_map_list_model_get_type
 GtkMenu
 gtk_menu_new
 gtk_menu_new_from_model
+GtkPopoverMenuFlags
+gtk_menu_new_from_model_full
 gtk_menu_reorder_child
 gtk_menu_popup_at_rect
 gtk_menu_popup_at_widget
diff --git a/gtk/gtkmenusectionbox.c b/gtk/gtkmenusectionbox.c
index 0bb33cd68b..502de3ffc0 100644
--- a/gtk/gtkmenusectionbox.c
+++ b/gtk/gtkmenusectionbox.c
@@ -38,18 +38,19 @@ typedef GtkBoxClass GtkMenuSectionBoxClass;
 
 struct _GtkMenuSectionBox
 {
-  GtkBox             parent_instance;
-
-  GtkMenuSectionBox *toplevel;
-  GtkMenuTracker    *tracker;
-  GtkBox            *item_box;
-  GtkWidget         *separator;
-  guint              separator_sync_idle;
-  gboolean           iconic;
-  gboolean           inline_buttons;
-  gboolean           circular;
-  gint               depth;
-  GtkSizeGroup      *indicators;
+  GtkBox               parent_instance;
+
+  GtkMenuSectionBox   *toplevel;
+  GtkMenuTracker      *tracker;
+  GtkBox              *item_box;
+  GtkWidget           *separator;
+  guint                separator_sync_idle;
+  gboolean             iconic;
+  gboolean             inline_buttons;
+  gboolean             circular;
+  gint                 depth;
+  GtkPopoverMenuFlags  flags;
+  GtkSizeGroup        *indicators;
 };
 
 typedef struct
@@ -295,22 +296,43 @@ gtk_menu_section_box_insert_func (GtkMenuTrackerItem *item,
     }
   else if (gtk_menu_tracker_item_get_has_link (item, G_MENU_LINK_SUBMENU))
     {
-      GtkWidget *stack = NULL;
-      GtkWidget *parent = NULL;
-      gchar *name;
+      if (box->flags & GTK_POPOVER_MENU_NESTED)
+        {
+          GMenuModel *model;
+          GtkWidget *submenu;
 
-      widget = g_object_new (GTK_TYPE_MODEL_BUTTON,
-                             "menu-name", gtk_menu_tracker_item_get_label (item),
-                             "indicator-size-group", box->indicators,
-                             NULL);
-      g_object_bind_property (item, "label", widget, "text", G_BINDING_SYNC_CREATE);
-      g_object_bind_property (item, "icon", widget, "icon", G_BINDING_SYNC_CREATE);
-      g_object_bind_property (item, "sensitive", widget, "sensitive", G_BINDING_SYNC_CREATE);
+          model = _gtk_menu_tracker_item_get_link (item, G_MENU_LINK_SUBMENU);
 
-      get_ancestors (GTK_WIDGET (box->toplevel), GTK_TYPE_STACK, &stack, &parent);
-      g_object_get (gtk_stack_get_page (GTK_STACK (stack), parent), "name", &name, NULL);
-      gtk_menu_section_box_new_submenu (item, box->toplevel, widget, name);
-      g_free (name);
+          submenu = gtk_popover_menu_new_from_model_full (NULL, model, box->flags);
+          gtk_popover_set_has_arrow (GTK_POPOVER (submenu), FALSE);
+          gtk_widget_set_valign (submenu, GTK_ALIGN_START);
+
+          widget = g_object_new (GTK_TYPE_MODEL_BUTTON,
+                                 "popover", submenu,
+                                 NULL);
+          g_object_bind_property (item, "label", widget, "text", G_BINDING_SYNC_CREATE);
+          g_object_bind_property (item, "icon", widget, "icon", G_BINDING_SYNC_CREATE);
+          g_object_bind_property (item, "sensitive", widget, "sensitive", G_BINDING_SYNC_CREATE);
+        }
+      else
+        {
+          GtkWidget *stack = NULL;
+          GtkWidget *parent = NULL;
+          gchar *name;
+
+          widget = g_object_new (GTK_TYPE_MODEL_BUTTON,
+                                 "menu-name", gtk_menu_tracker_item_get_label (item),
+                                 "indicator-size-group", box->indicators,
+                                 NULL);
+          g_object_bind_property (item, "label", widget, "text", G_BINDING_SYNC_CREATE);
+          g_object_bind_property (item, "icon", widget, "icon", G_BINDING_SYNC_CREATE);
+          g_object_bind_property (item, "sensitive", widget, "sensitive", G_BINDING_SYNC_CREATE);
+
+          get_ancestors (GTK_WIDGET (box->toplevel), GTK_TYPE_STACK, &stack, &parent);
+          g_object_get (gtk_stack_get_page (GTK_STACK (stack), parent), "name", &name, NULL);
+          gtk_menu_section_box_new_submenu (item, box->toplevel, widget, name);
+          g_free (name);
+        }
     }
   else
     {
@@ -453,13 +475,15 @@ update_popover_position_cb (GObject    *source,
 }
 
 void
-gtk_menu_section_box_new_toplevel (GtkPopoverMenu *popover,
-                                   GMenuModel     *model)
+gtk_menu_section_box_new_toplevel (GtkPopoverMenu      *popover,
+                                   GMenuModel          *model,
+                                   GtkPopoverMenuFlags  flags)
 {
   GtkMenuSectionBox *box;
 
   box = g_object_new (GTK_TYPE_MENU_SECTION_BOX,  NULL);
   box->indicators = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
+  box->flags = flags;
 
   gtk_popover_menu_add_submenu (popover, GTK_WIDGET (box), "main");
 
@@ -482,6 +506,7 @@ gtk_menu_section_box_new_submenu (GtkMenuTrackerItem *item,
 
   box = g_object_new (GTK_TYPE_MENU_SECTION_BOX, NULL);
   box->indicators = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
+  box->flags = toplevel->flags;
 
   button = g_object_new (GTK_TYPE_MODEL_BUTTON,
                          "menu-name", name,
@@ -521,6 +546,7 @@ gtk_menu_section_box_new_section (GtkMenuTrackerItem *item,
   box->indicators = g_object_ref (parent->indicators);
   box->toplevel = parent->toplevel;
   box->depth = parent->depth + 1;
+  box->flags = parent->flags;
 
   label = gtk_menu_tracker_item_get_label (item);
   hint = gtk_menu_tracker_item_get_display_hint (item);
diff --git a/gtk/gtkmenusectionboxprivate.h b/gtk/gtkmenusectionboxprivate.h
index 01508fe43a..05d6bd294e 100644
--- a/gtk/gtkmenusectionboxprivate.h
+++ b/gtk/gtkmenusectionboxprivate.h
@@ -41,8 +41,9 @@ G_BEGIN_DECLS
 typedef struct _GtkMenuSectionBox                           GtkMenuSectionBox;
 
 GType                   gtk_menu_section_box_get_type                   (void) G_GNUC_CONST;
-void                    gtk_menu_section_box_new_toplevel               (GtkPopoverMenu *popover,
-                                                                         GMenuModel     *model);
+void                    gtk_menu_section_box_new_toplevel               (GtkPopoverMenu      *popover,
+                                                                         GMenuModel          *model,
+                                                                         GtkPopoverMenuFlags  flags);
 
 G_END_DECLS
 
diff --git a/gtk/gtkmodelbutton.c b/gtk/gtkmodelbutton.c
index 80552ec33b..98d70e2226 100644
--- a/gtk/gtkmodelbutton.c
+++ b/gtk/gtkmodelbutton.c
@@ -43,6 +43,7 @@
 #include "gtkactionable.h"
 #include "gtkeventcontrollermotion.h"
 #include "gtkeventcontrollerkey.h"
+#include "gtknative.h"
 
 /**
  * SECTION:gtkmodelbutton
@@ -162,6 +163,7 @@ struct _GtkModelButton
   GtkWidget *start_indicator;
   GtkWidget *end_box;
   GtkWidget *end_indicator;
+  GtkWidget *popover;
   gboolean active;
   gboolean centered;
   gboolean iconic;
@@ -184,6 +186,7 @@ enum
   PROP_USE_MARKUP,
   PROP_ACTIVE,
   PROP_MENU_NAME,
+  PROP_POPOVER,
   PROP_ICONIC,
   PROP_ACCEL,
   PROP_INDICATOR_SIZE_GROUP,
@@ -275,7 +278,8 @@ gtk_model_button_update_state (GtkModelButton *button)
 
     case GTK_BUTTON_ROLE_NORMAL:
       start_type = GTK_CSS_IMAGE_BUILTIN_NONE;
-      if (button->menu_name != NULL)
+      if (button->menu_name != NULL ||
+          button->popover != NULL)
         end_type = GTK_CSS_IMAGE_BUILTIN_ARROW_RIGHT;
       else
         end_type = GTK_CSS_IMAGE_BUILTIN_NONE;
@@ -344,7 +348,7 @@ update_node_name (GtkModelButton *button)
     case GTK_BUTTON_ROLE_NORMAL:
       a11y_role = ATK_ROLE_PUSH_BUTTON;
       start_name = I_("none");
-      if (button->menu_name)
+      if (button->menu_name || button->popover)
         end_name = I_("arrow");
       else
         end_name = I_("none");
@@ -519,6 +523,28 @@ gtk_model_button_set_iconic (GtkModelButton *button,
   g_object_notify_by_pspec (G_OBJECT (button), properties[PROP_ICONIC]);
 }
 
+static void
+gtk_model_button_set_popover (GtkModelButton *button,
+                              GtkWidget      *popover)
+{
+  if (button->popover)
+    gtk_popover_set_relative_to (GTK_POPOVER (button->popover), NULL);
+
+  button->popover = popover;
+
+  if (button->popover)
+    {
+      gtk_popover_set_relative_to (GTK_POPOVER (button->popover), GTK_WIDGET (button));
+      gtk_popover_set_position (GTK_POPOVER (button->popover), GTK_POS_RIGHT);
+    }
+
+  update_node_name (button);
+  gtk_model_button_update_state (button);
+
+  gtk_widget_queue_resize (GTK_WIDGET (button));
+  g_object_notify_by_pspec (G_OBJECT (button), properties[PROP_POPOVER]);
+}
+
 static void
 update_accel (GtkModelButton *button,
               const char     *accel)
@@ -591,6 +617,10 @@ gtk_model_button_get_property (GObject    *object,
       g_value_set_string (value, button->menu_name);
       break;
 
+    case PROP_POPOVER:
+      g_value_set_object (value, button->popover);
+      break;
+
     case PROP_ICONIC:
       g_value_set_boolean (value, button->iconic);
       break;
@@ -643,6 +673,10 @@ gtk_model_button_set_property (GObject      *object,
       gtk_model_button_set_menu_name (button, g_value_get_string (value));
       break;
 
+    case PROP_POPOVER:
+      gtk_model_button_set_popover (button, (GtkWidget *)g_value_get_object (value));
+      break;
+
     case PROP_ICONIC:
       gtk_model_button_set_iconic (button, g_value_get_boolean (value));
       break;
@@ -783,7 +817,9 @@ gtk_model_button_size_allocate (GtkWidget *widget,
                                 int        height,
                                 int        baseline)
 {
-  if (GTK_MODEL_BUTTON (widget)->iconic)
+  GtkModelButton *button = GTK_MODEL_BUTTON (widget);
+
+  if (button->iconic)
     {
       GTK_WIDGET_CLASS (gtk_model_button_parent_class)->size_allocate (widget,
                                                                        width,
@@ -792,14 +828,12 @@ gtk_model_button_size_allocate (GtkWidget *widget,
     }
   else
     {
-      GtkModelButton *button;
       GtkAllocation child_allocation;
       GtkWidget *child;
       int start_width, start_height;
       int end_width, end_height;
       int min;
 
-      button = GTK_MODEL_BUTTON (widget);
       child = gtk_bin_get_child (GTK_BIN (widget));
 
       gtk_widget_measure (button->start_box,
@@ -860,6 +894,9 @@ gtk_model_button_size_allocate (GtkWidget *widget,
           gtk_widget_size_allocate (child, &child_allocation, baseline);
         }
     }
+
+  if (button->popover)
+    gtk_native_check_resize (GTK_NATIVE (button->popover));
 }
 
 static void
@@ -888,10 +925,18 @@ close_menu (GtkModelButton *button)
   GtkWidget *popover;
 
   popover = gtk_widget_get_ancestor (GTK_WIDGET (button), GTK_TYPE_POPOVER);
-  if (popover != NULL)
-    gtk_popover_popdown (GTK_POPOVER (popover));
+  while (popover != NULL)
+    {
+      gtk_popover_popdown (GTK_POPOVER (popover));
+      if (GTK_IS_POPOVER_MENU (popover))
+        popover = gtk_popover_menu_get_parent_menu (GTK_POPOVER_MENU (popover));
+      else
+        popover = NULL;
+    }
 }
 
+static void open_submenu (GtkPopover *popover);
+
 static void
 gtk_model_button_clicked (GtkButton *button)
 {
@@ -901,6 +946,17 @@ gtk_model_button_clicked (GtkButton *button)
     {
       switch_menu (model_button);
     }
+  else if (model_button->popover != NULL)
+    {
+      GtkPopoverMenu *menu;
+      GtkWidget *submenu;
+
+      menu = (GtkPopoverMenu *)gtk_widget_get_ancestor (GTK_WIDGET (button), GTK_TYPE_POPOVER_MENU);
+      submenu = model_button->popover;
+      gtk_popover_popup (GTK_POPOVER (submenu));
+      gtk_popover_menu_set_open_submenu (menu, submenu);
+      gtk_popover_menu_set_parent_menu (GTK_POPOVER_MENU (submenu), GTK_WIDGET (menu));
+    }
   else if (model_button->role == GTK_BUTTON_ROLE_NORMAL)
     {
       close_menu (model_button);
@@ -915,6 +971,7 @@ gtk_model_button_finalize (GObject *object)
   gtk_widget_unparent (button->start_box);
   gtk_widget_unparent (button->end_box);
   g_free (button->accel);
+  g_clear_pointer (&button->popover, gtk_widget_unparent);
 
   G_OBJECT_CLASS (gtk_model_button_parent_class)->finalize (object);
 }
@@ -979,6 +1036,20 @@ gtk_model_button_focus (GtkWidget        *widget,
           switch_menu (button);
           return TRUE;
         }
+      else if (direction == GTK_DIR_RIGHT &&
+               button->role == GTK_BUTTON_ROLE_NORMAL &&
+               button->popover != NULL)
+        {
+          GtkPopoverMenu *menu;
+          GtkWidget *submenu;
+
+          menu = GTK_POPOVER_MENU (gtk_widget_get_ancestor (GTK_WIDGET (button), GTK_TYPE_POPOVER_MENU));
+          submenu = button->popover;
+          gtk_popover_popup (GTK_POPOVER (submenu));
+          gtk_popover_menu_set_open_submenu (menu, submenu);
+          gtk_popover_menu_set_parent_menu (GTK_POPOVER_MENU (submenu), GTK_WIDGET (menu));
+          return TRUE;
+        }
     }
   else
     {
@@ -1080,8 +1151,7 @@ gtk_model_button_class_init (GtkModelButtonClass *class)
   /**
    * GtkModelButton:menu-name:
    *
-   * The name of a submenu to open when the button is activated.
-   * If this is set, the button should not have an action associated with it.
+   * The name of a submenu to open when the button is activated.  * If this is set, the button should not 
have an action associated with it.
    */
   properties[PROP_MENU_NAME] =
     g_param_spec_string ("menu-name",
@@ -1090,6 +1160,13 @@ gtk_model_button_class_init (GtkModelButtonClass *class)
                          NULL,
                          G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
 
+ properties[PROP_POPOVER] =
+   g_param_spec_object ("popover",
+                        P_("Popover"),
+                        P_("Popover to open"),
+                        GTK_TYPE_POPOVER,
+                        G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
+
   /**
    * GtkModelButton:iconic:
    *
@@ -1130,32 +1207,56 @@ gtk_model_button_class_init (GtkModelButtonClass *class)
 }
 
 static void
-enter_cb (GtkEventController *controller,
-          double              x,
-          double              y,
-          GdkCrossingMode     mode,
-          GdkNotifyType       type,
-          gpointer            data)
+close_submenus (GtkPopover *popover)
 {
-  GtkWidget *target;
-  GtkWidget *popover;
-  gboolean is;
-  gboolean contains;
+  GtkPopoverMenu *menu;
 
-  target = gtk_event_controller_get_widget (controller);
-  popover = gtk_widget_get_ancestor (target, GTK_TYPE_POPOVER_MENU);
+  if (GTK_IS_POPOVER_MENU (popover))
+    {
+      GtkWidget *submenu;
 
-  g_object_get (controller,
-                "is-pointer-focus", &is,
-                "contains-pointer-focus", &contains,
-                NULL);
+      menu = GTK_POPOVER_MENU (popover);
+      submenu = gtk_popover_menu_get_open_submenu (menu);
+      if (submenu)
+        {
+          close_submenus (GTK_POPOVER (submenu));
+          gtk_popover_popdown (GTK_POPOVER (submenu));
+          gtk_popover_menu_set_open_submenu (menu, NULL);
+        }
+    }
+}
 
-  if (popover && (is || contains))
-    gtk_popover_menu_set_active_item (GTK_POPOVER_MENU (popover), target);
+static void
+open_submenu (GtkPopover *popover)
+{
+  if (GTK_IS_POPOVER_MENU (popover))
+    {
+      GtkWidget *active_item;
+
+      active_item = gtk_popover_menu_get_active_item (GTK_POPOVER_MENU (popover));
+      if (GTK_IS_MODEL_BUTTON (active_item) &&
+          GTK_MODEL_BUTTON (active_item)->popover)
+        {
+          GtkWidget *submenu;
+
+          submenu = GTK_MODEL_BUTTON (active_item)->popover;
+          if (gtk_popover_menu_get_open_submenu (GTK_POPOVER_MENU (popover)) != submenu)
+{
+g_print ("close submenus %p %p\n", gtk_popover_menu_get_open_submenu (GTK_POPOVER_MENU (popover)), submenu);
+            close_submenus (popover);
+}
+
+          gtk_popover_popup (GTK_POPOVER (submenu));
+          gtk_popover_menu_set_open_submenu (GTK_POPOVER_MENU (popover), submenu);
+          gtk_popover_menu_set_parent_menu (GTK_POPOVER_MENU (submenu), GTK_WIDGET (popover));
+        }
+    }
 }
 
 static void
-leave_cb (GtkEventController *controller,
+enter_cb (GtkEventController *controller,
+          double              x,
+          double              y,
           GdkCrossingMode     mode,
           GdkNotifyType       type,
           gpointer            data)
@@ -1173,8 +1274,11 @@ leave_cb (GtkEventController *controller,
                 "contains-pointer-focus", &contains,
                 NULL);
 
-  if (popover && !(is || contains))
-    gtk_popover_menu_set_active_item (GTK_POPOVER_MENU (popover), NULL);
+  if (popover && (is || contains))
+    {
+      gtk_popover_menu_set_active_item (GTK_POPOVER_MENU (popover), target);
+      open_submenu (GTK_POPOVER (popover));
+    }
 }
 
 static void
@@ -1231,8 +1335,7 @@ gtk_model_button_init (GtkModelButton *button)
   update_node_ordering (button);
 
   controller = gtk_event_controller_motion_new ();
-  g_signal_connect (controller, "enter", G_CALLBACK (enter_cb), NULL);
-  g_signal_connect (controller, "leave", G_CALLBACK (leave_cb), NULL);
+  g_signal_connect (controller, "enter", G_CALLBACK (enter_cb), button);
   gtk_widget_add_controller (GTK_WIDGET (button), controller);
 
   controller = gtk_event_controller_key_new ();
diff --git a/gtk/gtkpopover.c b/gtk/gtkpopover.c
index 3f487a1172..e7d97c55f5 100644
--- a/gtk/gtkpopover.c
+++ b/gtk/gtkpopover.c
@@ -107,6 +107,7 @@
 #include "config.h"
 
 #include "gtkpopoverprivate.h"
+#include "gtkpopovermenuprivate.h"
 #include "gtknative.h"
 #include "gtkwidgetprivate.h"
 #include "gtkeventcontrollerkey.h"
@@ -398,6 +399,19 @@ gtk_popover_focus_out (GtkWidget *widget)
 {
 }
 
+static void
+close_menu (GtkPopover *popover)
+{
+  while (popover)
+    {
+      gtk_popover_popdown (popover);
+      if (GTK_IS_POPOVER_MENU (popover))
+        popover = (GtkPopover *)gtk_popover_menu_get_parent_menu (GTK_POPOVER_MENU (popover));
+      else
+        popover = NULL;
+    }
+}
+
 static gboolean
 gtk_popover_key_pressed (GtkWidget       *widget,
                          guint            keyval,
@@ -406,7 +420,7 @@ gtk_popover_key_pressed (GtkWidget       *widget,
 {
   if (keyval == GDK_KEY_Escape)
     {
-      gtk_popover_popdown (GTK_POPOVER (widget));
+      close_menu (GTK_POPOVER (widget));
       return TRUE;
     }
 
diff --git a/gtk/gtkpopovermenu.c b/gtk/gtkpopovermenu.c
index 856b017cb5..98b52058bc 100644
--- a/gtk/gtkpopovermenu.c
+++ b/gtk/gtkpopovermenu.c
@@ -29,9 +29,12 @@
 #include "gtkpopoverprivate.h"
 #include "gtkwidgetprivate.h"
 #include "gtkeventcontrollerkey.h"
+#include "gtkeventcontrollermotion.h"
 #include "gtkmain.h"
 #include "gtktypebuiltins.h"
 #include "gtkbindings.h"
+#include "gtkmodelbutton.h"
+#include "gtkpopovermenubar.h"
 
 
 /**
@@ -124,6 +127,8 @@ struct _GtkPopoverMenu
   GtkPopover parent_instance;
 
   GtkWidget *active_item;
+  GtkWidget *open_submenu;
+  GtkWidget *parent_menu;
 };
 
 struct _GtkPopoverMenuClass
@@ -137,6 +142,38 @@ enum {
 
 G_DEFINE_TYPE (GtkPopoverMenu, gtk_popover_menu, GTK_TYPE_POPOVER)
 
+GtkWidget *
+gtk_popover_menu_get_parent_menu (GtkPopoverMenu *menu)
+{
+  return menu->parent_menu;
+}
+
+void
+gtk_popover_menu_set_parent_menu (GtkPopoverMenu *menu,
+                                  GtkWidget      *parent)
+{
+  menu->parent_menu = parent;
+}
+
+GtkWidget *
+gtk_popover_menu_get_open_submenu (GtkPopoverMenu *menu)
+{
+  return menu->open_submenu;
+}
+
+void
+gtk_popover_menu_set_open_submenu (GtkPopoverMenu *menu,
+                                   GtkWidget      *submenu)
+{
+  menu->open_submenu = submenu;
+}
+
+GtkWidget *
+gtk_popover_menu_get_active_item (GtkPopoverMenu *menu)
+{
+  return menu->active_item;
+}
+
 void
 gtk_popover_menu_set_active_item (GtkPopoverMenu *menu,
                                   GtkWidget      *item)
@@ -150,8 +187,16 @@ gtk_popover_menu_set_active_item (GtkPopoverMenu *menu,
 
       if (menu->active_item)
         {
+          GtkWidget *popover;
+
           gtk_widget_set_state_flags (menu->active_item, GTK_STATE_FLAG_SELECTED, FALSE);
-          gtk_widget_grab_focus (menu->active_item);
+          if (GTK_IS_MODEL_BUTTON (item))
+            g_object_get (item, "popover", &popover, NULL);
+
+          if (!popover || popover != menu->open_submenu)
+            gtk_widget_grab_focus (menu->active_item);
+
+          g_clear_object (&popover);
        }
     }
 }
@@ -168,14 +213,40 @@ static void
 focus_out (GtkEventController *controller,
            GdkCrossingMode     mode,
            GdkNotifyType       detail,
-           GtkPopover         *popover)
+           GtkPopoverMenu     *menu)
 {
   gboolean contains_focus;
 
   g_object_get (controller, "contains-focus", &contains_focus, NULL);
 
   if (!contains_focus)
-    gtk_popover_popdown (popover);
+    {
+      if (menu->parent_menu &&
+          GTK_POPOVER_MENU (menu->parent_menu)->open_submenu == (GtkWidget*)menu)
+        GTK_POPOVER_MENU (menu->parent_menu)->open_submenu = NULL;
+      gtk_popover_popdown (GTK_POPOVER (menu));
+    }
+}
+
+static void
+leave_cb (GtkEventController *controller,
+          GdkCrossingMode     mode,
+          GdkNotifyType       type,
+          gpointer            data)
+{
+  GtkWidget *target;
+  gboolean is;
+  gboolean contains;
+
+  target = gtk_event_controller_get_widget (controller);
+
+  g_object_get (controller,
+                "is-pointer-focus", &is,
+                "contains-pointer-focus", &contains,
+                NULL);
+
+  if (!(is || contains))
+    gtk_popover_menu_set_active_item (GTK_POPOVER_MENU (target), NULL);
 }
 
 static void
@@ -199,6 +270,11 @@ gtk_popover_menu_init (GtkPopoverMenu *popover)
   controller = gtk_event_controller_key_new ();
   g_signal_connect (controller, "focus-out", G_CALLBACK (focus_out), popover);
   gtk_widget_add_controller (GTK_WIDGET (popover), controller);
+
+  controller = gtk_event_controller_motion_new ();
+  g_signal_connect (controller, "leave", G_CALLBACK (leave_cb), popover);
+  gtk_widget_add_controller (GTK_WIDGET (popover), controller);
+
 }
 
 static void
@@ -310,10 +386,35 @@ gtk_popover_menu_focus (GtkWidget        *widget,
     }
   else
     {
+      if (GTK_POPOVER_MENU (widget)->open_submenu)
+        {
+          if (gtk_widget_child_focus (GTK_POPOVER_MENU (widget)->open_submenu, direction))
+            return TRUE;
+          if (direction == GTK_DIR_LEFT)
+            {
+              gtk_widget_grab_focus (GTK_POPOVER_MENU (widget)->active_item);
+              return TRUE;
+            }
+          return FALSE;
+        }
+
       if (gtk_widget_focus_move (widget, direction))
         return TRUE;
 
-      if (direction == GTK_DIR_UP || direction == GTK_DIR_DOWN)
+      if (direction == GTK_DIR_LEFT || direction == GTK_DIR_RIGHT)
+        {
+          /* If we are part of a menubar, we want to let the
+           * menubar use left/right arrows for cycling, else
+           * we eat them.
+           */
+          if (gtk_widget_get_ancestor (widget, GTK_TYPE_POPOVER_MENU_BAR) ||
+              (gtk_popover_menu_get_parent_menu (GTK_POPOVER_MENU (widget)) &&
+               direction == GTK_DIR_LEFT))
+            return FALSE;
+          else
+            return TRUE;
+        }
+      else if (direction == GTK_DIR_UP || direction == GTK_DIR_DOWN)
         {
           GtkWidget *p;
 
@@ -503,11 +604,47 @@ gtk_popover_menu_add_submenu (GtkPopoverMenu *popover,
  * Actions can also be added using gtk_widget_insert_action_group()
  * on the menus attach widget or on any of its parent widgets.
  *
+ * This function creates menus with sliding submenus.
+ * See gtk_popover_menu_new_from_model_full() for a way
+ * to control this.
+ *
  * Returns: the new #GtkPopoverMenu
  */
 GtkWidget *
 gtk_popover_menu_new_from_model (GtkWidget  *relative_to,
                                  GMenuModel *model)
+
+{
+  return gtk_popover_menu_new_from_model_full (relative_to, model, 0);
+}
+
+/**
+ * gtk_popover_menu_new_from_model_full:
+ * @relative_to: (allow-none): #GtkWidget the popover is related to
+ * @model: a #GMenuModel
+ * @flags: flags that affect how the menu is created
+ *
+ * Creates a #GtkPopoverMenu and populates it according to
+ * @model. The popover is pointed to the @relative_to widget.
+ *
+ * The created buttons are connected to actions found in the
+ * #GtkApplicationWindow to which the popover belongs - typically
+ * by means of being attached to a widget that is contained within
+ * the #GtkApplicationWindows widget hierarchy.
+ *
+ * Actions can also be added using gtk_widget_insert_action_group()
+ * on the menus attach widget or on any of its parent widgets.
+ *
+ * The only flag that is supported currently is
+ * #GTK_POPOVER_MENU_NESTED, which makes GTK create traditional,
+ * nested submenus instead of the default sliding submenus.
+ *
+ * Returns: the new #GtkPopoverMenu
+ */
+GtkWidget *
+gtk_popover_menu_new_from_model_full (GtkWidget           *relative_to,
+                                      GMenuModel          *model,
+                                      GtkPopoverMenuFlags  flags)
 {
   GtkWidget *popover;
 
@@ -515,7 +652,8 @@ gtk_popover_menu_new_from_model (GtkWidget  *relative_to,
   g_return_val_if_fail (G_IS_MENU_MODEL (model), NULL);
 
   popover = gtk_popover_menu_new (relative_to);
-  gtk_menu_section_box_new_toplevel (GTK_POPOVER_MENU (popover), model);
+  gtk_menu_section_box_new_toplevel (GTK_POPOVER_MENU (popover), model, flags);
 
   return popover;
 }
+
diff --git a/gtk/gtkpopovermenu.h b/gtk/gtkpopovermenu.h
index 143a190b70..e2c1d3b3ef 100644
--- a/gtk/gtkpopovermenu.h
+++ b/gtk/gtkpopovermenu.h
@@ -42,6 +42,24 @@ GDK_AVAILABLE_IN_ALL
 GtkWidget * gtk_popover_menu_new_from_model (GtkWidget  *relative_to,
                                              GMenuModel *model);
 
+/**
+ * GtkPopoverMenuFlags:
+ * @GTK_POPOVER_MENU_NESTED: Create submenus as nested
+ *    popovers. Without this flag, submenus are created as
+ *    sliding pages that replace the main menu.
+ *
+ * Flags that affect how popover menus are created from
+ * a menu model.
+ */
+typedef enum {
+  GTK_POPOVER_MENU_NESTED = 1 << 0
+} GtkPopoverMenuFlags;
+
+GDK_AVAILABLE_IN_ALL
+GtkWidget * gtk_popover_menu_new_from_model_full (GtkWidget           *relative_to,
+                                                  GMenuModel          *model,
+                                                  GtkPopoverMenuFlags  flags);
+
 GDK_AVAILABLE_IN_ALL
 void        gtk_popover_menu_add_submenu (GtkPopoverMenu *popover,
                                           GtkWidget      *submenu,
diff --git a/gtk/gtkpopovermenubar.c b/gtk/gtkpopovermenubar.c
index 7716397dfe..956f63eced 100644
--- a/gtk/gtkpopovermenubar.c
+++ b/gtk/gtkpopovermenubar.c
@@ -314,7 +314,11 @@ gtk_popover_menu_bar_item_size_allocate (GtkWidget *widget,
 static void
 gtk_popover_menu_bar_item_activate (GtkPopoverMenuBarItem *item)
 {
-  gtk_popover_popup (GTK_POPOVER (item->popover));
+  GtkPopoverMenuBar *bar;
+
+  bar = GTK_POPOVER_MENU_BAR (gtk_widget_get_ancestor (GTK_WIDGET (item), GTK_TYPE_POPOVER_MENU_BAR));
+
+  set_active_item (bar, item, TRUE);
 }
 
 static void
diff --git a/gtk/gtkpopovermenuprivate.h b/gtk/gtkpopovermenuprivate.h
index 024d20c91c..898dc60f30 100644
--- a/gtk/gtkpopovermenuprivate.h
+++ b/gtk/gtkpopovermenuprivate.h
@@ -22,8 +22,15 @@
 
 G_BEGIN_DECLS
 
-void gtk_popover_menu_set_active_item (GtkPopoverMenu *popover,
-                                       GtkWidget      *item);
+GtkWidget *gtk_popover_menu_get_active_item  (GtkPopoverMenu *menu);
+void       gtk_popover_menu_set_active_item  (GtkPopoverMenu *menu,
+                                              GtkWidget      *item);
+GtkWidget *gtk_popover_menu_get_open_submenu (GtkPopoverMenu *menu);
+void       gtk_popover_menu_set_open_submenu (GtkPopoverMenu *menu,
+                                              GtkWidget      *submenu);
+GtkWidget *gtk_popover_menu_get_parent_menu  (GtkPopoverMenu *menu);
+void       gtk_popover_menu_set_parent_menu  (GtkPopoverMenu *menu,
+                                              GtkWidget      *parent);
 
 G_END_DECLS
 
diff --git a/tests/popover2.ui b/tests/popover2.ui
index d934e6f45a..49cd3f72e2 100644
--- a/tests/popover2.ui
+++ b/tests/popover2.ui
@@ -1,5 +1,6 @@
 <interface>
   <object class="GtkPopoverMenu" id="popover">
+    <property name="autohide">True</property>
     <child>
       <object class="GtkBox">
         <property name="orientation">vertical</property>
@@ -105,14 +106,14 @@
         </child>
         <child>
           <object class="GtkModelButton">
-            <property name="text">Submenu 1</property>
-            <property name="menu-name">submenu1</property>
+            <property name="label">Submenu 1</property>
+            <property name="popover">submenu1</property>
           </object>
         </child>
         <child>
           <object class="GtkModelButton">
-            <property name="text">Submenu 2</property>
-            <property name="menu-name">submenu2</property>
+            <property name="label">Submenu 2</property>
+            <property name="popover">submenu2</property>
           </object>
         </child>
         <child>
@@ -151,17 +152,13 @@
         </child>
       </object>
     </child>
+  </object>
+  <object class="GtkPopoverMenu" id="submenu1">
     <child>
       <object class="GtkBox">
         <property name="name">submenu1</property>
         <property name="orientation">vertical</property>
         <property name="margin">10</property>
-        <child>
-          <object class="GtkModelButton">
-            <property name="text">Submenu 1</property>
-            <property name="menu-name">main</property>
-          </object>
-        </child>
         <child>
           <object class="GtkBox">
             <property name="orientation">vertical</property>
@@ -352,17 +349,13 @@
         </child>
       </object>
     </child>
+  </object>
+  <object class="GtkPopoverMenu" id="submenu2">
     <child>
       <object class="GtkBox">
         <property name="name">submenu2</property>
         <property name="orientation">vertical</property>
         <property name="margin">10</property>
-        <child>
-          <object class="GtkModelButton">
-            <property name="text">Submenu 2</property>
-            <property name="menu-name">main</property>
-          </object>
-        </child>
         <child>
           <object class="GtkModelButton">
             <property name="action-name">top.action7</property>
@@ -377,24 +370,19 @@
         </child>
         <child>
           <object class="GtkModelButton">
-            <property name="text">Subsubmenu</property>
-            <property name="icon">icon9</property>
-            <property name="menu-name">subsubmenu</property>
+            <property name="label">Subsubmenu</property>
+            <property name="popover">subsubmenu</property>
           </object>
         </child>
       </object>
     </child>
+  </object>
+  <object class="GtkPopoverMenu" id="subsubmenu">
     <child>
       <object class="GtkBox">
         <property name="name">subsubmenu</property>
         <property name="orientation">vertical</property>
         <property name="margin">10</property>
-        <child>
-          <object class="GtkModelButton">
-            <property name="text">Subsubmenu</property>
-            <property name="menu-name">submenu2</property>
-          </object>
-        </child>
         <child>
           <object class="GtkModelButton">
             <property name="action-name">action8</property>
diff --git a/tests/testpopover.c b/tests/testpopover.c
index 3ece11139b..ce23dc0636 100644
--- a/tests/testpopover.c
+++ b/tests/testpopover.c
@@ -37,6 +37,7 @@ main (int argc, char *argv[])
   GtkWidget *win;
   GtkWidget *box;
   GtkWidget *button;
+  GtkWidget *button1;
   GtkWidget *button2;
   GtkBuilder *builder;
   GMenuModel *model;
@@ -44,6 +45,7 @@ main (int argc, char *argv[])
   GtkWidget *overlay;
   GtkWidget *grid;
   GtkWidget *popover;
+  GtkWidget *popover1;
   GtkWidget *popover2;
   GtkWidget *label;
   GtkWidget *check;
@@ -93,6 +95,8 @@ main (int argc, char *argv[])
   box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
   button = gtk_menu_button_new ();
   gtk_container_add (GTK_CONTAINER (box), button);
+  button1 = gtk_menu_button_new ();
+  gtk_container_add (GTK_CONTAINER (box), button1);
   button2 = gtk_menu_button_new ();
   gtk_container_add (GTK_CONTAINER (box), button2);
 
@@ -100,6 +104,9 @@ main (int argc, char *argv[])
   gtk_menu_button_set_use_popover (GTK_MENU_BUTTON (button), TRUE);
   popover = GTK_WIDGET (gtk_menu_button_get_popover (GTK_MENU_BUTTON (button)));
 
+  popover1 = gtk_popover_menu_new_from_model_full (NULL, model, GTK_POPOVER_MENU_NESTED);
+  gtk_menu_button_set_popover (GTK_MENU_BUTTON (button1), popover1);
+
   builder = gtk_builder_new_from_file ("popover2.ui");
   popover2 = (GtkWidget *)gtk_builder_get_object (builder, "popover");
   gtk_menu_button_set_popover (GTK_MENU_BUTTON (button2), popover2);
@@ -110,6 +117,7 @@ main (int argc, char *argv[])
   label = gtk_label_new ("Popover hexpand");
   check = gtk_check_button_new ();
   g_object_bind_property (check, "active", popover, "hexpand", G_BINDING_SYNC_CREATE);
+  g_object_bind_property (check, "active", popover1, "hexpand", G_BINDING_SYNC_CREATE);
   g_object_bind_property (check, "active", popover2, "hexpand", G_BINDING_SYNC_CREATE);
   gtk_grid_attach (GTK_GRID (grid), label , 1, 1, 1, 1);
   gtk_grid_attach (GTK_GRID (grid), check, 2, 1, 1, 1);
@@ -117,6 +125,7 @@ main (int argc, char *argv[])
   label = gtk_label_new ("Popover vexpand");
   check = gtk_check_button_new ();
   g_object_bind_property (check, "active", popover, "vexpand", G_BINDING_SYNC_CREATE);
+  g_object_bind_property (check, "active", popover1, "vexpand", G_BINDING_SYNC_CREATE);
   g_object_bind_property (check, "active", popover2, "vexpand", G_BINDING_SYNC_CREATE);
   gtk_grid_attach (GTK_GRID (grid), label , 1, 2, 1, 1);
   gtk_grid_attach (GTK_GRID (grid), check, 2, 2, 1, 1);
@@ -129,6 +138,7 @@ main (int argc, char *argv[])
   gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (combo), "right", "Right");
   gtk_combo_box_set_active (GTK_COMBO_BOX (combo), 1);
   g_object_bind_property (combo, "active", button, "direction", G_BINDING_SYNC_CREATE);
+  g_object_bind_property (combo, "active", button1, "direction", G_BINDING_SYNC_CREATE);
   g_object_bind_property (combo, "active", button2, "direction", G_BINDING_SYNC_CREATE);
   gtk_grid_attach (GTK_GRID (grid), label , 1, 3, 1, 1);
   gtk_grid_attach (GTK_GRID (grid), combo, 2, 3, 1, 1);


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