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: