vcl/unx/gtk3/gtkinst.cxx |  248 ++++++++++++++++++++++++++++++++++++-----------
 1 file changed, 194 insertions(+), 54 deletions(-)

New commits:
commit 50affeebd9e631a63708e55674ca2e16ae568178
Author:     Caolán McNamara <caol...@redhat.com>
AuthorDate: Thu Nov 18 20:06:01 2021 +0000
Commit:     Caolán McNamara <caol...@redhat.com>
CommitDate: Fri Nov 19 16:10:28 2021 +0100

    gtk3: treat standalone GtkPopover similarly to the GtkMenuButton managed one
    
    so do the same replacement with GtkWindow under X11 for gtk3 that we do
    in MenuButtons for the popover with direct popovers when the constraint
    is GTK_POPOVER_CONSTRAINT_NONE
    
    Change-Id: Ia903b24c54e6d3acc18df1ab912600e07b7f844d
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/125543
    Tested-by: Jenkins
    Reviewed-by: Caolán McNamara <caol...@redhat.com>

diff --git a/vcl/unx/gtk3/gtkinst.cxx b/vcl/unx/gtk3/gtkinst.cxx
index fbf98d830c11..6853ca6728f4 100644
--- a/vcl/unx/gtk3/gtkinst.cxx
+++ b/vcl/unx/gtk3/gtkinst.cxx
@@ -9639,19 +9639,19 @@ void do_ungrab(GtkWidget* pWidget)
     gdk_seat_ungrab(pSeat);
 }
 
-GtkPositionType show_menu_older_gtk(GtkWidget* pMenuButton, GtkWindow* pMenu)
+GtkPositionType show_menu_older_gtk(GtkWidget* pMenuButton, GtkWindow* pMenu, 
const GdkRectangle& rAnchor)
 {
     //place the toplevel just below its launcher button
     GtkWidget* pToplevel = widget_get_toplevel(pMenuButton);
     gtk_coord x, y, absx, absy;
-    gtk_widget_translate_coordinates(pMenuButton, pToplevel, 0, 0, &x, &y);
+    gtk_widget_translate_coordinates(pMenuButton, pToplevel, rAnchor.x, 
rAnchor.y, &x, &y);
     GdkSurface* pWindow = widget_get_surface(pToplevel);
     gdk_window_get_position(pWindow, &absx, &absy);
 
     x += absx;
     y += absy;
 
-    gint nButtonHeight = gtk_widget_get_allocated_height(pMenuButton);
+    gint nButtonHeight = rAnchor.height;
     y += nButtonHeight;
 
     gtk_window_group_add_window(gtk_window_get_group(GTK_WINDOW(pToplevel)), 
pMenu);
@@ -9673,7 +9673,7 @@ GtkPositionType show_menu_older_gtk(GtkWidget* 
pMenuButton, GtkWindow* pMenu)
     bool bSwapForRTL = SwapForRTL(pMenuButton);
     if (bSwapForRTL)
     {
-        gint nButtonWidth = gtk_widget_get_allocated_width(pMenuButton);
+        gint nButtonWidth = rAnchor.width;
         x += nButtonWidth;
         x -= nMenuWidth;
     }
@@ -9722,7 +9722,7 @@ GtkPositionType show_menu_older_gtk(GtkWidget* 
pMenuButton, GtkWindow* pMenu)
     return ePosUsed;
 }
 
-bool show_menu_newer_gtk(GtkWidget* pComboBox, GtkWindow* pMenu)
+bool show_menu_newer_gtk(GtkWidget* pComboBox, GtkWindow* pMenu, const 
GdkRectangle &rAnchor)
 {
     static auto window_move_to_rect = reinterpret_cast<void (*) (GdkWindow*, 
const GdkRectangle*, GdkGravity,
                                                                  GdkGravity, 
GdkAnchorHints, gint, gint)>(
@@ -9741,20 +9741,17 @@ bool show_menu_newer_gtk(GtkWidget* pComboBox, 
GtkWindow* pMenu)
     //place the toplevel just below its launcher button
     GtkWidget* pToplevel = widget_get_toplevel(pComboBox);
     gtk_coord x, y;
-    gtk_widget_translate_coordinates(pComboBox, pToplevel, 0, 0, &x, &y);
+    gtk_widget_translate_coordinates(pComboBox, pToplevel, rAnchor.x, 
rAnchor.y, &x, &y);
 
     gtk_widget_realize(GTK_WIDGET(pMenu));
     gtk_window_group_add_window(gtk_window_get_group(GTK_WINDOW(pToplevel)), 
pMenu);
     gtk_window_set_transient_for(pMenu, GTK_WINDOW(pToplevel));
 
-    gint nComboWidth = gtk_widget_get_allocated_width(pComboBox);
-    gint nComboHeight = gtk_widget_get_allocated_height(pComboBox);
-
     bool bSwapForRTL = SwapForRTL(GTK_WIDGET(pComboBox));
 
     GdkGravity rect_anchor = !bSwapForRTL ? GDK_GRAVITY_SOUTH_WEST : 
GDK_GRAVITY_SOUTH_EAST;
     GdkGravity menu_anchor = !bSwapForRTL ? GDK_GRAVITY_NORTH_WEST : 
GDK_GRAVITY_NORTH_EAST;
-    GdkRectangle rect {x, y, nComboWidth, nComboHeight };
+    GdkRectangle rect {x, y, rAnchor.width, rAnchor.height};
     GdkSurface* toplevel = widget_get_surface(GTK_WIDGET(pMenu));
 
     window_move_to_rect(toplevel, &rect, rect_anchor, menu_anchor,
@@ -9765,7 +9762,7 @@ bool show_menu_newer_gtk(GtkWidget* pComboBox, GtkWindow* 
pMenu)
     return true;
 }
 
-GtkPositionType show_menu(GtkWidget* pMenuButton, GtkWindow* pMenu)
+GtkPositionType show_menu(GtkWidget* pMenuButton, GtkWindow* pMenu, const 
GdkRectangle& rAnchor)
 {
     // we only use ePosUsed in the replacement-for-X-popover case of a
     // MenuButton, so we only need it when show_menu_older_gtk is used
@@ -9786,8 +9783,8 @@ GtkPositionType show_menu(GtkWidget* pMenuButton, 
GtkWindow* pMenu)
     }
 
     // try with gdk_window_move_to_rect, but if that's not available, try 
without
-    if (!show_menu_newer_gtk(pMenuButton, pMenu))
-        ePosUsed = show_menu_older_gtk(pMenuButton, pMenu);
+    if (!show_menu_newer_gtk(pMenuButton, pMenu, rAnchor))
+        ePosUsed = show_menu_older_gtk(pMenuButton, pMenu, rAnchor);
     gtk_widget_show_all(GTK_WIDGET(pMenu));
     gtk_widget_grab_focus(GTK_WIDGET(pMenu));
     do_grab(GTK_WIDGET(pMenu));
@@ -9825,6 +9822,46 @@ bool button_release_is_outside(GtkWidget* pWidget, 
GtkWidget* pMenuHack, GdkEven
 
     return true;
 }
+
+GtkPositionType MovePopoverContentsToWindow(GtkWidget* pPopover, GtkWindow* 
pMenuHack, GtkWidget* pAnchor, const GdkRectangle& rAnchor)
+{
+    //set border width
+    gtk_container_set_border_width(GTK_CONTAINER(pMenuHack), 
gtk_container_get_border_width(GTK_CONTAINER(pPopover)));
+
+    //steal popover contents and smuggle into toplevel display window
+    GtkWidget* pChild = gtk_bin_get_child(GTK_BIN(pPopover));
+    g_object_ref(pChild);
+    gtk_container_remove(GTK_CONTAINER(pPopover), pChild);
+    gtk_container_add(GTK_CONTAINER(pMenuHack), pChild);
+    g_object_unref(pChild);
+
+    return show_menu(pAnchor, pMenuHack, rAnchor);
+}
+
+void MoveWindowContentsToPopover(GtkWindow* pMenuHack, GtkWidget* pPopover, 
GtkWidget* pAnchor)
+{
+    do_ungrab(GTK_WIDGET(pMenuHack));
+
+    gtk_widget_hide(GTK_WIDGET(pMenuHack));
+    //put contents back from where the came from
+    GtkWidget* pChild = gtk_bin_get_child(GTK_BIN(pMenuHack));
+    g_object_ref(pChild);
+    gtk_container_remove(GTK_CONTAINER(pMenuHack), pChild);
+    gtk_container_add(GTK_CONTAINER(pPopover), pChild);
+    g_object_unref(pChild);
+
+    // so gdk_window_move_to_rect will work again the next time
+    gtk_widget_unrealize(GTK_WIDGET(pMenuHack));
+
+    gtk_widget_set_size_request(GTK_WIDGET(pMenuHack), -1, -1);
+
+    // undo show_menu tooltip blocking
+    GtkWidget* pParent = widget_get_toplevel(pAnchor);
+    GtkSalFrame* pFrame = pParent ? GtkSalFrame::getFromWindow(pParent) : 
nullptr;
+    if (pFrame)
+        pFrame->UnblockTooltip();
+}
+
 #endif
 
 /* four types of uses of this
@@ -9855,6 +9892,7 @@ private:
     GtkWindow* m_pMenuHack;
     //when doing so, if it's a toolbar menubutton align the menu to the full 
toolitem
     GtkWidget* m_pMenuHackAlign;
+    bool m_nButtonPressSeen;
     gulong m_nSignalId;
 #endif
     GtkWidget* m_pPopover;
@@ -9880,40 +9918,14 @@ private:
             return;
         if (!get_active())
         {
-            do_ungrab(GTK_WIDGET(m_pMenuHack));
-
-            gtk_widget_hide(GTK_WIDGET(m_pMenuHack));
-            //put contents back from where the came from
-            GtkWidget* pChild = gtk_bin_get_child(GTK_BIN(m_pMenuHack));
-            g_object_ref(pChild);
-            gtk_container_remove(GTK_CONTAINER(m_pMenuHack), pChild);
-            gtk_container_add(GTK_CONTAINER(m_pPopover), pChild);
-            g_object_unref(pChild);
-
-            // so gdk_window_move_to_rect will work again the next time
-            gtk_widget_unrealize(GTK_WIDGET(m_pMenuHack));
-
-            gtk_widget_set_size_request(GTK_WIDGET(m_pMenuHack), -1, -1);
-
-            // undo show_menu tooltip blocking
-            GtkWidget* pParent = 
widget_get_toplevel(GTK_WIDGET(m_pMenuButton));
-            GtkSalFrame* pFrame = pParent ? 
GtkSalFrame::getFromWindow(pParent) : nullptr;
-            if (pFrame)
-                pFrame->UnblockTooltip();
+            m_nButtonPressSeen = false;
+            MoveWindowContentsToPopover(m_pMenuHack, m_pPopover, 
GTK_WIDGET(m_pMenuButton));
         }
         else
         {
-            //set border width
-            gtk_container_set_border_width(GTK_CONTAINER(m_pMenuHack), 
gtk_container_get_border_width(GTK_CONTAINER(m_pPopover)));
-
-            //steal popover contents and smuggle into toplevel display window
-            GtkWidget* pChild = gtk_bin_get_child(GTK_BIN(m_pPopover));
-            g_object_ref(pChild);
-            gtk_container_remove(GTK_CONTAINER(m_pPopover), pChild);
-            gtk_container_add(GTK_CONTAINER(m_pMenuHack), pChild);
-            g_object_unref(pChild);
-
-            GtkPositionType ePosUsed = show_menu(m_pMenuHackAlign ? 
m_pMenuHackAlign : GTK_WIDGET(m_pMenuButton), m_pMenuHack);
+            GtkWidget* pAnchor = m_pMenuHackAlign ? m_pMenuHackAlign : 
GTK_WIDGET(m_pMenuButton);
+            GdkRectangle aAnchor {0, 0, 
gtk_widget_get_allocated_width(pAnchor), 
gtk_widget_get_allocated_height(pAnchor) };
+            GtkPositionType ePosUsed = MovePopoverContentsToWindow(m_pPopover, 
m_pMenuHack, pAnchor, aAnchor);
             // tdf#132540 keep the placeholder popover on this same side as 
the replacement menu
             
gtk_popover_set_position(gtk_menu_button_get_popover(m_pMenuButton), ePosUsed);
         }
@@ -9942,10 +9954,17 @@ private:
         }
     }
 
+    static gboolean signalButtonPress(GtkWidget* /*pWidget*/, GdkEventButton* 
/*pEvent*/, gpointer widget)
+    {
+        GtkInstanceMenuButton* pThis = 
static_cast<GtkInstanceMenuButton*>(widget);
+        pThis->m_nButtonPressSeen = true;
+        return false;
+    }
+
     static gboolean signalButtonRelease(GtkWidget* pWidget, GdkEventButton* 
pEvent, gpointer widget)
     {
         GtkInstanceMenuButton* pThis = 
static_cast<GtkInstanceMenuButton*>(widget);
-        if (button_release_is_outside(pWidget, GTK_WIDGET(pThis->m_pMenuHack), 
pEvent))
+        if (pThis->m_nButtonPressSeen && button_release_is_outside(pWidget, 
GTK_WIDGET(pThis->m_pMenuHack), pEvent))
             pThis->set_active(false);
         return false;
     }
@@ -10017,6 +10036,7 @@ public:
 #if !GTK_CHECK_VERSION(4, 0, 0)
         , m_pMenuHack(nullptr)
         , m_pMenuHackAlign(pMenuAlign)
+        , m_nButtonPressSeen(true)
         , m_nSignalId(0)
 #endif
         , m_pPopover(nullptr)
@@ -10220,22 +10240,33 @@ public:
         return;
 #else
 
+        if (!m_pPopover)
+        {
+            gtk_menu_button_set_popover(m_pMenuButton, nullptr);
+            return;
+        }
+
 #if defined(GDK_WINDOWING_X11)
         if (!m_pMenuHack)
         {
             //under wayland a Popover will work to "escape" the parent dialog, 
not
             //so under X, so come up with this hack to use a raw GtkWindow
             GdkDisplay *pDisplay = gtk_widget_get_display(m_pWidget);
-            if (DLSYM_GDK_IS_X11_DISPLAY(pDisplay))
+            if (DLSYM_GDK_IS_X11_DISPLAY(pDisplay) && 
gtk_popover_get_constrain_to(GTK_POPOVER(m_pPopover)) == 
GTK_POPOVER_CONSTRAINT_NONE)
             {
                 m_pMenuHack = GTK_WINDOW(gtk_window_new(GTK_WINDOW_POPUP));
                 gtk_window_set_type_hint(m_pMenuHack, 
GDK_WINDOW_TYPE_HINT_COMBO);
-                gtk_window_set_modal(m_pMenuHack, m_pPopover ? 
gtk_popover_get_modal(GTK_POPOVER(m_pPopover)) : true);
+                bool bModal = gtk_popover_get_modal(GTK_POPOVER(m_pPopover));
+                gtk_window_set_modal(m_pMenuHack, bModal);
                 gtk_window_set_resizable(m_pMenuHack, false);
                 m_nSignalId = 
g_signal_connect(GTK_TOGGLE_BUTTON(m_pMenuButton), "toggled", 
G_CALLBACK(signalMenuButtonToggled), this);
-                g_signal_connect(m_pMenuHack, "grab-broken-event", 
G_CALLBACK(signalGrabBroken), this);
-                g_signal_connect(m_pMenuHack, "button-release-event", 
G_CALLBACK(signalButtonRelease), this);
                 g_signal_connect(m_pMenuHack, "key-press-event", 
G_CALLBACK(keyPress), this);
+                if (bModal)
+                {
+                    g_signal_connect(m_pMenuHack, "grab-broken-event", 
G_CALLBACK(signalGrabBroken), this);
+                    g_signal_connect(m_pMenuHack, "button-press-event", 
G_CALLBACK(signalButtonPress), this);
+                    g_signal_connect(m_pMenuHack, "button-release-event", 
G_CALLBACK(signalButtonRelease), this);
+                }
             }
         }
 #endif
@@ -10258,10 +10289,7 @@ public:
         else
         {
             gtk_menu_button_set_popover(m_pMenuButton, m_pPopover);
-            if (m_pPopover)
-            {
-                gtk_widget_show_all(m_pPopover);
-            }
+            gtk_widget_show_all(m_pPopover);
         }
 #endif
     }
@@ -19810,7 +19838,8 @@ private:
             if (m_nMaxMRUCount)
                 tree_view_set_cursor(0);
 
-            show_menu(pComboBox, m_pMenuWindow);
+            GdkRectangle aAnchor {0, 0, 
gtk_widget_get_allocated_width(pComboBox), 
gtk_widget_get_allocated_height(pComboBox) };
+            show_menu(pComboBox, m_pMenuWindow, aAnchor);
         }
     }
 
@@ -21854,6 +21883,11 @@ namespace {
 class GtkInstancePopover : public GtkInstanceContainer, public virtual 
weld::Popover
 {
 private:
+#if !GTK_CHECK_VERSION(4, 0, 0)
+    //popover cannot escape dialog under X so we might need to stick up own 
window instead
+    GtkWindow* m_pMenuHack;
+    bool m_nButtonPressSeen;
+#endif
     GtkPopover* m_pPopover;
     gulong m_nSignalId;
     ImplSVEvent* m_pClosedEvent;
@@ -21875,10 +21909,67 @@ private:
         m_pClosedEvent = Application::PostUserEvent(LINK(this, 
GtkInstancePopover, async_signal_closed));
     }
 
+#if !GTK_CHECK_VERSION(4, 0, 0)
+    static gboolean keyPress(GtkWidget*, GdkEventKey* pEvent, gpointer widget)
+    {
+        GtkInstancePopover* pThis = static_cast<GtkInstancePopover*>(widget);
+        return pThis->key_press(pEvent);
+    }
+
+    bool key_press(const GdkEventKey* pEvent)
+    {
+        if (pEvent->keyval == GDK_KEY_Escape)
+        {
+            popdown();
+            return true;
+        }
+        return false;
+    }
+
+    static gboolean signalButtonPress(GtkWidget* /*pWidget*/, GdkEventButton* 
/*pEvent*/, gpointer widget)
+    {
+        GtkInstancePopover* pThis = static_cast<GtkInstancePopover*>(widget);
+        pThis->m_nButtonPressSeen = true;
+        return false;
+    }
+
+    static gboolean signalButtonRelease(GtkWidget* pWidget, GdkEventButton* 
pEvent, gpointer widget)
+    {
+        GtkInstancePopover* pThis = static_cast<GtkInstancePopover*>(widget);
+        if (pThis->m_nButtonPressSeen && button_release_is_outside(pWidget, 
GTK_WIDGET(pThis->m_pMenuHack), pEvent))
+            pThis->popdown();
+        return false;
+    }
+
+    static void signalGrabBroken(GtkWidget*, GdkEventGrabBroken *pEvent, 
gpointer widget)
+    {
+        GtkInstancePopover* pThis = static_cast<GtkInstancePopover*>(widget);
+        pThis->grab_broken(pEvent);
+    }
+
+    void grab_broken(const GdkEventGrabBroken *event)
+    {
+        if (event->grab_window == nullptr)
+        {
+            popdown();
+        }
+        else
+        {
+            //try and regrab, so when we lose the grab to the menu of the 
color palette
+            //combobox we regain it so the color palette doesn't itself 
disappear on next
+            //click on the color palette combobox
+            do_grab(GTK_WIDGET(m_pMenuHack));
+        }
+    }
+
+#endif
+
 public:
     GtkInstancePopover(GtkPopover* pPopover, GtkInstanceBuilder* pBuilder, 
bool bTakeOwnership)
 #if !GTK_CHECK_VERSION(4, 0, 0)
         : GtkInstanceContainer(GTK_CONTAINER(pPopover), pBuilder, 
bTakeOwnership)
+        , m_pMenuHack(nullptr)
+        , m_nButtonPressSeen(false)
 #else
         : GtkInstanceContainer(GTK_WIDGET(pPopover), pBuilder, bTakeOwnership)
 #endif
@@ -21904,16 +21995,65 @@ public:
         gtk_popover_set_relative_to(m_pPopover, pWidget);
 #endif
         gtk_popover_set_pointing_to(m_pPopover, &aRect);
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+#if defined(GDK_WINDOWING_X11)
+        //under wayland a Popover will work to "escape" the parent dialog, not
+        //so under X, so come up with this hack to use a raw GtkWindow
+        GdkDisplay *pDisplay = gtk_widget_get_display(GTK_WIDGET(m_pPopover));
+        if (DLSYM_GDK_IS_X11_DISPLAY(pDisplay) && 
gtk_popover_get_constrain_to(m_pPopover) == GTK_POPOVER_CONSTRAINT_NONE)
+        {
+            if (!m_pMenuHack)
+            {
+                m_pMenuHack = GTK_WINDOW(gtk_window_new(GTK_WINDOW_POPUP));
+                gtk_window_set_type_hint(m_pMenuHack, 
GDK_WINDOW_TYPE_HINT_COMBO);
+                bool bModal = gtk_popover_get_modal(m_pPopover);
+                gtk_window_set_modal(m_pMenuHack, bModal);
+                gtk_window_set_resizable(m_pMenuHack, false);
+                g_signal_connect(m_pMenuHack, "key-press-event", 
G_CALLBACK(keyPress), this);
+                if (bModal)
+                {
+                     g_signal_connect(m_pMenuHack, "grab-broken-event", 
G_CALLBACK(signalGrabBroken), this);
+                     g_signal_connect(m_pMenuHack, "button-press-event", 
G_CALLBACK(signalButtonPress), this);
+                     g_signal_connect(m_pMenuHack, "button-release-event", 
G_CALLBACK(signalButtonRelease), this);
+                }
+            }
+
+            MovePopoverContentsToWindow(GTK_WIDGET(m_pPopover), m_pMenuHack, 
pWidget, aRect);
+            return;
+        }
+#endif
+#endif
+
         gtk_popover_popup(m_pPopover);
     }
 
     virtual void popdown() override
     {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+#if defined(GDK_WINDOWING_X11)
+        //under wayland a Popover will work to "escape" the parent dialog, not
+        //so under X, so come up with this hack to use a raw GtkWindow
+        GdkDisplay *pDisplay = gtk_widget_get_display(GTK_WIDGET(m_pPopover));
+        if (DLSYM_GDK_IS_X11_DISPLAY(pDisplay))
+        {
+            m_nButtonPressSeen = false;
+            MoveWindowContentsToPopover(m_pMenuHack, GTK_WIDGET(m_pPopover), 
gtk_popover_get_relative_to(m_pPopover));
+            signal_closed();
+            return;
+        }
+#endif
+#endif
+
         gtk_popover_popdown(m_pPopover);
     }
 
     virtual ~GtkInstancePopover() override
     {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+        if (m_pMenuHack)
+            gtk_widget_destroy(GTK_WIDGET(m_pMenuHack));
+#endif
         if (m_pClosedEvent)
             Application::RemoveUserEvent(m_pClosedEvent);
         g_signal_handler_disconnect(m_pPopover, m_nSignalId);

Reply via email to