vcl/unx/gtk3/gtkframe.cxx |    6 -
 vcl/unx/gtk3/gtkinst.cxx  |  155 ++++++++++++++++++++++++++++++++++++++--------
 2 files changed, 132 insertions(+), 29 deletions(-)

New commits:
commit 0a9cf4d7eb7f4a0626528f93ae0e74583ef4859b
Author:     Caolán McNamara <caolan.mcnam...@collabora.com>
AuthorDate: Tue Jun 24 12:53:31 2025 +0100
Commit:     Adolfo Jayme Barrientos <fit...@ubuntu.com>
CommitDate: Wed Jun 25 00:11:09 2025 +0200

    Resolves: tdf#160415 vcl popups misplaced under x11 gtk3
    
    revert:
    
    commit 698935c220131bc761eb9cf25e01fa91087a788e
        tdf#152155 vcl: fix gtk popup listbox positions on sidebar
    
    and implement an alternative fix for the original motivating problem
    under gtk3 x11
    
    Change-Id: I12eb41a232ba6588ff9eb933b475755cae68dcb0
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/186901
    Reviewed-by: Adolfo Jayme Barrientos <fit...@ubuntu.com>
    Tested-by: Jenkins

diff --git a/vcl/unx/gtk3/gtkframe.cxx b/vcl/unx/gtk3/gtkframe.cxx
index 2f4c56c5b2b5..b93996085d27 100644
--- a/vcl/unx/gtk3/gtkframe.cxx
+++ b/vcl/unx/gtk3/gtkframe.cxx
@@ -3112,12 +3112,8 @@ void GtkSalFrame::EndSetClipRegion()
 
 void GtkSalFrame::PositionByToolkit(const tools::Rectangle& rRect, 
FloatWinPopupFlags nFlags)
 {
-    if ( ImplGetSVData()->maNWFData.mbCanDetermineWindowPosition &&
-        // tdf#152155 cannot determine window positions of popup listboxes on 
sidebar
-        nFlags != LISTBOX_FLOATWINPOPUPFLAGS )
-    {
+    if (ImplGetSVData()->maNWFData.mbCanDetermineWindowPosition)
         return;
-    }
 
     m_aFloatRect = rRect;
     m_nFloatFlags = nFlags;
diff --git a/vcl/unx/gtk3/gtkinst.cxx b/vcl/unx/gtk3/gtkinst.cxx
index f000864d082e..6e8aaa2c9e75 100644
--- a/vcl/unx/gtk3/gtkinst.cxx
+++ b/vcl/unx/gtk3/gtkinst.cxx
@@ -5840,16 +5840,38 @@ public:
     }
 };
 
+bool isPositioningAllowed(GtkWidget* pWidget)
+{
+    // no X/Y positioning under Wayland
+    GdkDisplay *pDisplay = gtk_widget_get_display(pWidget);
+    return !DLSYM_GDK_IS_WAYLAND_DISPLAY(pDisplay);
+}
+
+// This allow sidebar extensions (and similar cases, e.g.  extension provided
+// options dialog pages) to work within an otherwise native gtk UI by embedding
+// a SalGtkFrame within which vcl windows can then exist inside the gtk widget
+// hierarchy.
 class ChildFrame : public WorkWindow
 {
 private:
-    Idle  maLayoutIdle;
+    Idle maLayoutIdle;
+    Link<VclWindowEvent&, void> maWindowEventHdl;
+    gulong mnSizeAllocateSignalId;
 
     DECL_LINK(ImplHandleLayoutTimerHdl, Timer*, void);
+    DECL_LINK(WindowEventHdl, VclWindowEvent&, void);
+
+    GtkWidget* getWindow()
+    {
+        GtkSalFrame* pGtkFrame = dynamic_cast<GtkSalFrame*>(ImplGetFrame());
+        assert(pGtkFrame);
+        return pGtkFrame->getWindow();
+    }
 public:
     ChildFrame(vcl::Window* pParent, WinBits nStyle)
         : WorkWindow(pParent, nStyle)
         , maLayoutIdle( "ChildFrame maLayoutIdle" )
+        , mnSizeAllocateSignalId(0)
     {
         maLayoutIdle.SetPriority(TaskPriority::RESIZE);
         maLayoutIdle.SetInvokeHandler( LINK( this, ChildFrame, 
ImplHandleLayoutTimerHdl ) );
@@ -5858,6 +5880,26 @@ public:
     virtual void dispose() override
     {
         maLayoutIdle.Stop();
+
+        GtkWidget* pEmbeddedWidget = getWindow();
+
+        if (mnSizeAllocateSignalId)
+        {
+            g_signal_handler_disconnect(G_OBJECT(pEmbeddedWidget), 
mnSizeAllocateSignalId);
+            mnSizeAllocateSignalId = 0;
+        }
+
+        if (maWindowEventHdl.IsSet())
+        {
+            GtkWidget* pTopLevel = widget_get_toplevel(pEmbeddedWidget);
+            GtkSalFrame* pParentFrame = GtkSalFrame::getFromWindow(pTopLevel);
+            if (pParentFrame)
+                
pParentFrame->GetWindow()->RemoveEventListener(maWindowEventHdl);
+            else
+                SAL_WARN( "vcl.gtk", "cannot get parent frame
");
+            maWindowEventHdl = Link<VclWindowEvent&, void>();
+        }
+
         WorkWindow::dispose();
     }
 
@@ -5881,6 +5923,85 @@ public:
         Layout();
         WorkWindow::Resize();
     }
+
+    // See tdf#152155 and tdf#160415. Under x11 gtk3 update the embedded
+    // GtkSalFrame child position when its parent GtkSalFrame position changes
+    // (and when the intermediate GtkContainer sets a relative position). Under
+    // x11 vcl depends on knowing that position in order to calculate where to
+    // position vcl popups. (We use a different approach under wayland so that
+    // case isn't relevant here.)
+    static void updateFrameGeom(GtkWidget* pWidget)
+    {
+        GtkSalFrame* pEmbededFrame = GtkSalFrame::getFromWindow(pWidget);
+        if (!pEmbededFrame)
+        {
+            SAL_WARN( "vcl.gtk", "cannot get embedded frame
");
+            return;
+        }
+
+        GtkWidget* pTopLevel = widget_get_toplevel(pWidget);
+        GtkSalFrame* pParentFrame = GtkSalFrame::getFromWindow(pTopLevel);
+        if (!pParentFrame)
+        {
+            SAL_WARN( "vcl.gtk", "cannot get parent frame
");
+            return;
+        }
+
+        gtk_coord x, y;
+        if (!gtk_widget_translate_coordinates(pWidget, pTopLevel, 0, 0, &x, 
&y))
+        {
+            SAL_WARN( "vcl.gtk", "cannot translate coordinates
");
+            return;
+        }
+
+        SalFrameGeometry aParentGeom = pParentFrame->GetGeometry();
+
+        pEmbededFrame->SetPosSize(aParentGeom.x() + x - 
aParentGeom.leftDecoration(),
+                                  aParentGeom.y() + y - 
aParentGeom.topDecoration(),
+                                  0, 0, SAL_FRAME_POSSIZE_X | 
SAL_FRAME_POSSIZE_Y);
+    }
+
+    static void frameSizeAllocated(GtkWidget* pWidget, GdkRectangle*, gpointer)
+    {
+        updateFrameGeom(pWidget);
+    }
+
+    // Move the associated GtkWidget of the GtkSalFrame of this window into 
pContainer so
+    // it's embedded in that destination widget.
+    void Relocate(GtkWidget* pContainer)
+    {
+        GtkWidget* pWindow = getWindow();
+
+        GtkWidget* pOrigParent = gtk_widget_get_parent(pWindow);
+
+        g_object_ref(pWindow);
+        container_remove(pOrigParent, pWindow);
+
+        container_add(pContainer, pWindow);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+        gtk_container_child_set(GTK_CONTAINER(pContainer), pWindow, "expand", 
true, "fill", true, nullptr);
+#endif
+        gtk_widget_set_hexpand(pWindow, true);
+        gtk_widget_set_vexpand(pWindow, true);
+        gtk_widget_realize(pWindow);
+        gtk_widget_set_can_focus(pWindow, true);
+        g_object_unref(pWindow);
+
+        // for x11 we have to keep the relative geometry of the embedded 
GtkSalFrame up to date when
+        // the parent geometry changes (and when the GtkContainer positions 
the embedded GtkSalFrame)
+        if (isPositioningAllowed(pWindow))
+        {
+            GtkWidget* pTopLevel = widget_get_toplevel(pWindow);
+            if (GtkSalFrame* pParentFrame = 
GtkSalFrame::getFromWindow(pTopLevel))
+            {
+                maWindowEventHdl = LINK(this, ChildFrame, WindowEventHdl);
+                pParentFrame->GetWindow()->AddEventListener(maWindowEventHdl);
+            }
+            else
+                SAL_WARN("vcl.gtk", "missing parent frame");
+            mnSizeAllocateSignalId = g_signal_connect_after(G_OBJECT(pWindow), 
"size-allocate", G_CALLBACK(frameSizeAllocated), nullptr);
+        }
+    }
 };
 
 IMPL_LINK_NOARG(ChildFrame, ImplHandleLayoutTimerHdl, Timer*, void)
@@ -5888,6 +6009,13 @@ IMPL_LINK_NOARG(ChildFrame, ImplHandleLayoutTimerHdl, 
Timer*, void)
     Layout();
 }
 
+IMPL_LINK(ChildFrame, WindowEventHdl, VclWindowEvent&, rEvent, void)
+{
+    VclEventId nEventID = rEvent.GetId();
+    if (nEventID == VclEventId::WindowMove)
+        updateFrameGeom(getWindow());
+}
+
 class GtkInstanceContainer : public GtkInstanceWidget, public virtual 
weld::Container
 {
 private:
@@ -6007,26 +6135,7 @@ public:
         // This will cause a GtkSalFrame to be created. With 
WB_SYSTEMCHILDWINDOW set it
         // will create a toplevel GtkEventBox window
         auto xEmbedWindow = VclPtr<ChildFrame>::Create(ImplGetDefaultWindow(), 
WB_SYSTEMCHILDWINDOW | WB_DIALOGCONTROL | WB_CHILDDLGCTRL);
-        SalFrame* pFrame = xEmbedWindow->ImplGetFrame();
-        GtkSalFrame* pGtkFrame = dynamic_cast<GtkSalFrame*>(pFrame);
-        assert(pGtkFrame);
-
-        // relocate that toplevel GtkEventBox into this widget
-        GtkWidget* pWindow = pGtkFrame->getWindow();
-
-        GtkWidget* pParent = gtk_widget_get_parent(pWindow);
-
-        g_object_ref(pWindow);
-        container_remove(pParent, pWindow);
-        container_add(GTK_WIDGET(m_pContainer), pWindow);
-#if !GTK_CHECK_VERSION(4, 0, 0)
-        gtk_container_child_set(m_pContainer, pWindow, "expand", true, "fill", 
true, nullptr);
-#endif
-        gtk_widget_set_hexpand(pWindow, true);
-        gtk_widget_set_vexpand(pWindow, true);
-        gtk_widget_realize(pWindow);
-        gtk_widget_set_can_focus(pWindow, true);
-        g_object_unref(pWindow);
+        xEmbedWindow->Relocate(GTK_WIDGET(m_pContainer));
 
         // NoActivate otherwise Show grab focus to this widget
         xEmbedWindow->Show(true, ShowFlags::NoActivate);
@@ -6349,9 +6458,7 @@ protected:
 
     bool isPositioningAllowed() const
     {
-        // no X/Y positioning under Wayland
-        GdkDisplay *pDisplay = gtk_widget_get_display(m_pWidget);
-        return !DLSYM_GDK_IS_WAYLAND_DISPLAY(pDisplay);
+        return ::isPositioningAllowed(m_pWidget);
     }
 
 protected:

Reply via email to