Resolves: tdf#160415 vcl popups misplaced under x11 gtk3

revert:

commit 698935c220
    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/+/186891
Reviewed-by: Caolán McNamara <caolan.mcnamara@collabora.com>
Tested-by: Jenkins
This commit is contained in:
Caolán McNamara
2025-06-24 17:28:48 +02:00
parent 07fa13b331
commit bb90e0ffc3
2 changed files with 132 additions and 29 deletions
+1 -5
View File
@@ -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;
+131 -24
View File
@@ -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\n");
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\n");
return;
}
GtkWidget* pTopLevel = widget_get_toplevel(pWidget);
GtkSalFrame* pParentFrame = GtkSalFrame::getFromWindow(pTopLevel);
if (!pParentFrame)
{
SAL_WARN( "vcl.gtk", "cannot get parent frame\n");
return;
}
gtk_coord x, y;
if (!gtk_widget_translate_coordinates(pWidget, pTopLevel, 0, 0, &x, &y))
{
SAL_WARN( "vcl.gtk", "cannot translate coordinates\n");
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: