diff --git a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Display.java b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Display.java index 1b7539be33d..5d5f4019d38 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Display.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Display.java @@ -199,6 +199,9 @@ public class Display extends Device implements Executor { Shell activeShell; boolean activePending; boolean ignoreFocus; + /* GTK4: depth of focusProc() calls, and popovers disposed meanwhile, see Shell.releaseHandle() */ + int focusProcDepth; + ArrayList disposedPopovers = new ArrayList<>(); Tracker tracker; @@ -6149,7 +6152,32 @@ boolean scrollProc(long controller, double dx, double dy, long user_data) { long handle = GTK.gtk_event_controller_get_widget(controller); Widget widget = getWidget(handle); - if (widget != null) widget.focusProc(controller, user_data); + if (widget == null) return; + focusProcDepth++; + try { + widget.focusProc(controller, user_data); + } finally { + // GTK goes on with the focus change after this, so destroy the popovers later + if (--focusProcDepth == 0 && !disposedPopovers.isEmpty() && !isDisposed()) asyncExec(this::destroyDisposedPopovers); + } +} + +/** + * GTK4: destroys the popovers of shells disposed while a focus change was dispatched. + */ +void destroyDisposedPopovers () { + // Still in a focus change, e.g. in a nested event loop of a listener, see focusProc() + if (focusProcDepth > 0) return; + ArrayList popovers = disposedPopovers; + disposedPopovers = new ArrayList<>(); + for (long popover : popovers) { + // The parent window may have destroyed the popover meanwhile + if (GTK.gtk_widget_get_parent (popover) != 0) { + GTK.gtk_popover_popdown (popover); + GTK.gtk_widget_unparent (popover); + } + OS.g_object_unref (popover); + } } void windowActiveProc(long handle, long user_data) {; diff --git a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Shell.java b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Shell.java index f9053a3bb42..0089e907e31 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Shell.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Shell.java @@ -3854,9 +3854,17 @@ void releaseHandle () { * its parent is released with destroy=false, which calls releaseHandle() but not * destroyWidget(); unparenting here covers both paths and avoids leaving the popover * attached to a finalizing parent window. + * + * GTK sends the focus leave events walking up the widget tree from the old focus + * widget, so a popover disposed by a FocusOut listener is hidden and unparented + * only once that focus change is done, see Display.focusProc(). */ - GTK.gtk_popover_popdown (shellHandle); - GTK.gtk_widget_unparent (shellHandle); + if (display.focusProcDepth > 0) { + display.disposedPopovers.add (OS.g_object_ref (shellHandle)); + } else { + GTK.gtk_popover_popdown (shellHandle); + GTK.gtk_widget_unparent (shellHandle); + } } super.releaseHandle (); shellHandle = 0; diff --git a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_widgets_Shell.java b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_widgets_Shell.java index cb403fdb2d5..623b52613b6 100644 --- a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_widgets_Shell.java +++ b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_widgets_Shell.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2025 IBM Corporation and others. + * Copyright (c) 2000, 2026 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -1063,6 +1063,28 @@ public void test_Issue450_NoShellActivateOnSetFocus() { } } +@Test +public void test_onTopChildShellDisposedInFocusOut() throws InterruptedException { + shell.setLayout(new FillLayout()); + Text text = new Text(shell, SWT.BORDER); + shell.open(); + SwtTestUtil.processEvents(); + + Shell popup = new Shell(shell, SWT.ON_TOP | SWT.TOOL); + popup.setLayout(new FillLayout()); + Text popupText = new Text(popup, SWT.BORDER); + popup.setSize(100, 100); + // Close when the focus leaves, as popups do + popupText.addListener(SWT.FocusOut, e -> popup.dispose()); + popup.open(); + popupText.forceFocus(); + SwtTestUtil.processEvents(); + + text.forceFocus(); + SwtTestUtil.processEvents(1000, popup::isDisposed); + assertTrue(popup.isDisposed()); +} + @Test @Override public void test_setLocationLorg_eclipse_swt_graphics_Point() {