diff --git a/src/java.desktop/macosx/classes/sun/lwawt/LWComponentPeer.java b/src/java.desktop/macosx/classes/sun/lwawt/LWComponentPeer.java index 14fcb6b54c6b..0b34d5ef7f20 100644 --- a/src/java.desktop/macosx/classes/sun/lwawt/LWComponentPeer.java +++ b/src/java.desktop/macosx/classes/sun/lwawt/LWComponentPeer.java @@ -955,18 +955,13 @@ public abstract class LWComponentPeer // however that is the shared code and this particular problem's reproducibility has // platform specifics. So, it was decided to narrow down the fix to lwawt (OSX) in // current release. TODO: consider fixing it in the shared code. - if (!focusedWindowChangeAllowed) { - LWWindowPeer decoratedPeer = parentPeer.isSimpleWindow() ? - LWWindowPeer.getOwnerFrameDialog(parentPeer) : parentPeer; - - if (decoratedPeer == null || !decoratedPeer.getPlatformWindow().isActive()) { - if (focusLog.isLoggable(PlatformLogger.Level.FINE)) { - focusLog.fine("request rejected, focusedWindowChangeAllowed==false, " + - "decoratedPeer is inactive: " + decoratedPeer); - } - LWKeyboardFocusManagerPeer.removeLastFocusRequest(getTarget()); - return false; + if (!focusedWindowChangeAllowed && !parentPeer.getPlatformWindow().isActive()) { + if (focusLog.isLoggable(PlatformLogger.Level.FINE)) { + focusLog.fine("request rejected, focusedWindowChangeAllowed==false, " + + "parentPeer is inactive: " + parentPeer); } + LWKeyboardFocusManagerPeer.removeLastFocusRequest(getTarget()); + return false; } boolean res = parentPeer.requestWindowFocus(cause); diff --git a/src/java.desktop/macosx/classes/sun/lwawt/LWWindowPeer.java b/src/java.desktop/macosx/classes/sun/lwawt/LWWindowPeer.java index 8d7d625eefce..f529f161dba7 100644 --- a/src/java.desktop/macosx/classes/sun/lwawt/LWWindowPeer.java +++ b/src/java.desktop/macosx/classes/sun/lwawt/LWWindowPeer.java @@ -1272,53 +1272,6 @@ public class LWWindowPeer return false; } - AppContext targetAppContext = AWTAccessor.getComponentAccessor().getAppContext(getTarget()); - KeyboardFocusManager kfm = AWTAccessor.getKeyboardFocusManagerAccessor() - .getCurrentKeyboardFocusManager(targetAppContext); - Window currentActive = kfm.getActiveWindow(); - - - Window opposite = LWKeyboardFocusManagerPeer.getInstance(). - getCurrentFocusedWindow(); - - // Make the owner active window. - if (isSimpleWindow()) { - LWWindowPeer owner = getOwnerFrameDialog(this); - - // If owner is not natively active, request native - // activation on it w/o sending events up to java. - if (owner != null && !owner.platformWindow.isActive()) { - if (focusLog.isLoggable(PlatformLogger.Level.FINE)) { - focusLog.fine("requesting native focus to the owner " + owner); - } - LWWindowPeer currentActivePeer = currentActive == null ? null : - (LWWindowPeer) AWTAccessor.getComponentAccessor().getPeer( - currentActive); - - // Ensure the opposite is natively active and suppress sending events. - if (currentActivePeer != null && currentActivePeer.platformWindow.isActive()) { - if (focusLog.isLoggable(PlatformLogger.Level.FINE)) { - focusLog.fine("the opposite is " + currentActivePeer); - } - currentActivePeer.skipNextFocusChange = true; - } - owner.skipNextFocusChange = true; - - owner.platformWindow.requestWindowFocus(); - } - - // DKFM will synthesize all the focus/activation events correctly. - changeFocusedWindow(true, opposite); - return true; - - // In case the toplevel is active but not focused, change focus directly, - // as requesting native focus on it will not have effect. - } else if (getTarget() == currentActive && !getTarget().hasFocus()) { - - changeFocusedWindow(true, opposite); - return true; - } - return platformWindow.requestWindowFocus(); } @@ -1394,7 +1347,8 @@ public class LWWindowPeer // - when the opposite (gaining focus) window is an owned/owner window. // - for a simple window in any case. if (!becomesFocused && - (isGrabbing() || this.isOneOfOwnersOf(grabbingWindow))) + (isGrabbing() || this.isOneOfOwnersOf(grabbingWindow)) && + (opposite == null || getOwnerFrameDialog(AWTAccessor.getComponentAccessor().getPeer(opposite)) != this)) { if (focusLog.isLoggable(PlatformLogger.Level.FINE)) { focusLog.fine("ungrabbing on " + grabbingWindow); diff --git a/src/java.desktop/macosx/classes/sun/lwawt/macosx/CInputMethod.java b/src/java.desktop/macosx/classes/sun/lwawt/macosx/CInputMethod.java index 8df67cb606f3..9100aa0c965e 100644 --- a/src/java.desktop/macosx/classes/sun/lwawt/macosx/CInputMethod.java +++ b/src/java.desktop/macosx/classes/sun/lwawt/macosx/CInputMethod.java @@ -297,18 +297,6 @@ public class CInputMethod extends InputMethodAdapter { if (component.getInputMethodRequests() == null) { imInstance = null; } - - LWWindowPeer windowPeer = peer.getPlatformWindow().getPeer(); - if (windowPeer.isSimpleWindow()) { - // A simple window gains focus. Cocoa won't dispatch IME events into the simple window, but into its owner. - // This IM represents the focused component in the simple window. We will use the owner as IME proxy. - // For that, this IM is set for the owner and is dropped for the simple window. - Window owner = windowPeer.getTarget().getOwner(); - assert owner != null && owner.isActive(); - long ownerPtr = getNativeViewPtr((LWComponentPeer)AWTAccessor.getComponentAccessor().getPeer(owner)); - nativeNotifyPeer(ownerPtr, this); - imInstance = null; - } } if (peer != null) { diff --git a/src/java.desktop/macosx/classes/sun/lwawt/macosx/CPlatformWindow.java b/src/java.desktop/macosx/classes/sun/lwawt/macosx/CPlatformWindow.java index a7986fb961a9..11eeed796219 100644 --- a/src/java.desktop/macosx/classes/sun/lwawt/macosx/CPlatformWindow.java +++ b/src/java.desktop/macosx/classes/sun/lwawt/macosx/CPlatformWindow.java @@ -100,6 +100,7 @@ public class CPlatformWindow extends CFRetainedResource implements PlatformWindo private static native void nativeEnterFullScreenMode(long nsWindowPtr); private static native void nativeExitFullScreenMode(long nsWindowPtr); static native CPlatformWindow nativeGetTopmostPlatformWindowUnderMouse(); + private static native void nativeRaiseLevel(long nsWindowPtr, boolean popup, boolean onlyIfParentIsActive); // Logger to report issues happened during execution but that do not affect functionality private static final PlatformLogger logger = PlatformLogger.getLogger("sun.lwawt.macosx.CPlatformWindow"); @@ -160,7 +161,6 @@ public class CPlatformWindow extends CFRetainedResource implements PlatformWindo static final int MINIMIZABLE = 1 << 8; static final int RESIZABLE = 1 << 9; // both a style bit and prop bit - static final int NONACTIVATING = 1 << 24; static final int IS_DIALOG = 1 << 25; static final int IS_MODAL = 1 << 26; static final int IS_POPUP = 1 << 27; @@ -422,10 +422,7 @@ public class CPlatformWindow extends CFRetainedResource implements PlatformWindo // defaults style bits int styleBits = DECORATED | HAS_SHADOW | CLOSEABLE | ZOOMABLE | RESIZABLE | TITLE_VISIBLE; - if (isNativelyFocusableWindow()) { - styleBits = SET(styleBits, SHOULD_BECOME_KEY, true); - styleBits = SET(styleBits, SHOULD_BECOME_MAIN, true); - } + styleBits |= getFocusableStyleBits(); final boolean isFrame = (target instanceof Frame); final boolean isDialog = (target instanceof Dialog); @@ -461,7 +458,6 @@ public class CPlatformWindow extends CFRetainedResource implements PlatformWindo if (isPopup) { styleBits = SET(styleBits, TEXTURED, false); // Popups in applets don't activate applet's process - styleBits = SET(styleBits, NONACTIVATING, true); styleBits = SET(styleBits, IS_POPUP, true); } @@ -559,9 +555,13 @@ public class CPlatformWindow extends CFRetainedResource implements PlatformWindo return styleBits; } - // this is the counter-point to -[CWindow _nativeSetStyleBit:] private void setStyleBits(final int mask, final boolean value) { - execute(ptr -> nativeSetNSWindowStyleBits(ptr, mask, value ? mask : 0)); + setStyleBits(mask, value ? mask : 0); + } + + // this is the counter-point to -[CWindow _nativeSetStyleBit:] + private void setStyleBits(final int mask, final int value) { + execute(ptr -> nativeSetNSWindowStyleBits(ptr, mask, value)); } private native void _toggleFullScreenMode(final long model); @@ -936,8 +936,7 @@ public class CPlatformWindow extends CFRetainedResource implements PlatformWindo @Override public void updateFocusableWindowState() { - final boolean isFocusable = isNativelyFocusableWindow(); - setStyleBits(SHOULD_BECOME_KEY | SHOULD_BECOME_MAIN, isFocusable); // set both bits at once + setStyleBits(SHOULD_BECOME_KEY | SHOULD_BECOME_MAIN, getFocusableStyleBits()); // set both bits at once } @Override @@ -1079,7 +1078,6 @@ public class CPlatformWindow extends CFRetainedResource implements PlatformWindo } execute(ptr -> nativeSetEnabled(ptr, !blocked)); - checkBlockingAndOrder(); } public final void invalidateShadow() { @@ -1228,16 +1226,13 @@ public class CPlatformWindow extends CFRetainedResource implements PlatformWindo } } - /* - * Our focus model is synthetic and only non-simple window - * may become natively focusable window. - */ - private boolean isNativelyFocusableWindow() { - if (peer == null) { - return false; - } - - return !peer.isSimpleWindow() && target.getFocusableWindowState(); + // returns a combination of SHOULD_BECOME_KEY/SHOULD_BECOME_MAIN relevant for the current window + private int getFocusableStyleBits() { + return (peer == null || target == null || !target.isFocusableWindow()) + ? 0 + : peer.isSimpleWindow() + ? SHOULD_BECOME_KEY + : SHOULD_BECOME_KEY | SHOULD_BECOME_MAIN; } /* @@ -1246,8 +1241,11 @@ public class CPlatformWindow extends CFRetainedResource implements PlatformWindo * circumstances. */ private void updateFocusabilityForAutoRequestFocus(boolean isFocusable) { - if (target.isAutoRequestFocus() || !isNativelyFocusableWindow()) return; - setStyleBits(SHOULD_BECOME_KEY | SHOULD_BECOME_MAIN, isFocusable); // set both bits at once + if (target.isAutoRequestFocus()) return; + int focusableStyleBits = getFocusableStyleBits(); + if (focusableStyleBits == 0) return; + setStyleBits(SHOULD_BECOME_KEY | SHOULD_BECOME_MAIN, + isFocusable ? focusableStyleBits : 0); // set both bits at once } private boolean checkBlockingAndOrder() { @@ -1367,10 +1365,10 @@ public class CPlatformWindow extends CFRetainedResource implements PlatformWindo } protected void applyWindowLevel(Window target) { - if (target.isAlwaysOnTop() && target.getType() != Window.Type.POPUP) { - execute(ptr->CWrapper.NSWindow.setLevel(ptr, CWrapper.NSWindow.NSFloatingWindowLevel)); - } else if (target.getType() == Window.Type.POPUP) { - execute(ptr->CWrapper.NSWindow.setLevel(ptr, CWrapper.NSWindow.NSPopUpMenuWindowLevel)); + boolean popup = target.getType() == Window.Type.POPUP; + boolean alwaysOnTop = target.isAlwaysOnTop(); + if (popup || alwaysOnTop || owner != null) { + execute(ptr -> nativeRaiseLevel(ptr, popup, !popup && !alwaysOnTop)); } } diff --git a/src/java.desktop/macosx/native/libawt_lwawt/awt/AWTWindow.m b/src/java.desktop/macosx/native/libawt_lwawt/awt/AWTWindow.m index 0e5abbd50d4d..b2e539bbb184 100644 --- a/src/java.desktop/macosx/native/libawt_lwawt/awt/AWTWindow.m +++ b/src/java.desktop/macosx/native/libawt_lwawt/awt/AWTWindow.m @@ -281,7 +281,6 @@ AWT_NS_WINDOW_IMPLEMENTATION if (IS(styleBits, UTILITY)) type |= NSWindowStyleMaskUtilityWindow; if (IS(styleBits, HUD)) type |= NSWindowStyleMaskHUDWindow; if (IS(styleBits, SHEET)) type |= NSWindowStyleMaskDocModalWindow; - if (IS(styleBits, NONACTIVATING)) type |= NSWindowStyleMaskNonactivatingPanel; return type; } @@ -367,7 +366,6 @@ AWT_ASSERT_APPKIT_THREAD; if (self == nil) return nil; // no hope if (IS(bits, UTILITY) || - IS(bits, NONACTIVATING) || IS(bits, HUD) || IS(bits, HIDES_ON_DEACTIVATE) || IS(bits, SHEET)) @@ -777,9 +775,7 @@ AWT_ASSERT_APPKIT_THREAD; NSLog(@"became main: %d %@ %@", [self.nsWindow isKeyWindow], [self.nsWindow title], [self menuBarForWindow]); #endif - if (![self.nsWindow isKeyWindow]) { - [self activateWindowMenuBar]; - } + [self activateWindowMenuBar]; JNIEnv *env = [ThreadUtilities getJNIEnv]; jobject platformWindow = (*env)->NewLocalRef(env, self.javaPlatformWindow); @@ -790,6 +786,8 @@ AWT_ASSERT_APPKIT_THREAD; CHECK_EXCEPTION(); (*env)->DeleteLocalRef(env, platformWindow); } + + [self orderChildWindows:YES]; } - (void) windowDidBecomeKey: (NSNotification *) notification { @@ -801,13 +799,26 @@ AWT_ASSERT_APPKIT_THREAD; AWTWindow *opposite = [AWTWindow lastKeyWindow]; if (![self.nsWindow isMainWindow]) { - [self activateWindowMenuBar]; + [self makeRelevantAncestorMain]; } [AWTWindow setLastKeyWindow:nil]; [self _deliverWindowFocusEvent:YES oppositeWindow: opposite]; - [self orderChildWindows:YES]; +} + +- (void) makeRelevantAncestorMain { + NSWindow *nativeWindow; + AWTWindow *awtWindow = self; + + do { + nativeWindow = awtWindow.nsWindow; + if ([nativeWindow canBecomeMainWindow]) { + [nativeWindow makeMainWindow]; + break; + } + awtWindow = awtWindow.ownerWindow; + } while (awtWindow); } - (void) activateWindowMenuBar { @@ -865,6 +876,9 @@ AWT_ASSERT_APPKIT_THREAD; if (![self.nsWindow isKeyWindow]) { [self deactivateWindow]; } + + [self.javaMenuBar deactivate]; + [self orderChildWindows:NO]; } - (void) deactivateWindow { @@ -872,7 +886,6 @@ AWT_ASSERT_APPKIT_THREAD; #ifdef DEBUG NSLog(@"deactivating window: %@", [self.nsWindow title]); #endif - [self.javaMenuBar deactivate]; // the new key window NSWindow *keyWindow = [NSApp keyWindow]; @@ -885,7 +898,6 @@ AWT_ASSERT_APPKIT_THREAD; } [self _deliverWindowFocusEvent:NO oppositeWindow: opposite]; - [self orderChildWindows:NO]; } - (BOOL)windowShouldClose:(id)sender { @@ -1241,7 +1253,7 @@ JNI_COCOA_ENTER(env); AWTWindow *window = (AWTWindow*)[nsWindow delegate]; - if ([nsWindow isKeyWindow] || [nsWindow isMainWindow]) { + if ([nsWindow isMainWindow]) { [window.javaMenuBar deactivate]; } @@ -1252,7 +1264,7 @@ JNI_COCOA_ENTER(env); actualMenuBar = [[ApplicationDelegate sharedDelegate] defaultMenuBar]; } - if ([nsWindow isKeyWindow] || [nsWindow isMainWindow]) { + if ([nsWindow isMainWindow]) { [CMenuBar activate:actualMenuBar modallyDisabled:NO]; } }]; @@ -1774,3 +1786,25 @@ JNI_COCOA_ENTER(env); JNI_COCOA_EXIT(env); } +JNIEXPORT void JNICALL Java_sun_lwawt_macosx_CPlatformWindow_nativeRaiseLevel +(JNIEnv *env, jclass clazz, jlong windowPtr, jboolean popup, jboolean onlyIfParentIsActive) +{ +JNI_COCOA_ENTER(env); + + NSWindow *nsWindow = OBJC(windowPtr); + [ThreadUtilities performOnMainThreadWaiting:NO block:^(){ + AWTWindow *window = (AWTWindow*)[nsWindow delegate]; + if (onlyIfParentIsActive) { + AWTWindow *parent = window; + do { + parent = parent.ownerWindow; + } while (parent != nil && !parent.nsWindow.isMainWindow); + if (parent == nil) { + return; + } + } + [nsWindow setLevel: popup ? NSPopUpMenuWindowLevel : NSFloatingWindowLevel]; + }]; + +JNI_COCOA_EXIT(env); +} diff --git a/src/java.desktop/share/classes/java/awt/Window.java b/src/java.desktop/share/classes/java/awt/Window.java index 6a08b3ff90d6..ebf040b7cc53 100644 --- a/src/java.desktop/share/classes/java/awt/Window.java +++ b/src/java.desktop/share/classes/java/awt/Window.java @@ -1081,7 +1081,13 @@ public class Window extends Container implements Accessible { } else { // fix for 6532736: after this window is shown, its blocker // should be raised to front - modalBlocker.toFront_NoClientCode(); + boolean storedValue = modalBlocker.isAutoRequestFocus(); + modalBlocker.setAutoRequestFocus(false); + try { + modalBlocker.toFront_NoClientCode(); + } finally { + modalBlocker.setAutoRequestFocus(storedValue); + } } if (this instanceof Frame || this instanceof Dialog) { updateChildFocusableWindowState(this); diff --git a/test/jdk/jb/java/awt/Focus/PopupIncomingFocusTest.java b/test/jdk/jb/java/awt/Focus/PopupIncomingFocusTest.java new file mode 100644 index 000000000000..24acbd6fd0f2 --- /dev/null +++ b/test/jdk/jb/java/awt/Focus/PopupIncomingFocusTest.java @@ -0,0 +1,152 @@ +/* + * Copyright 2000-2023 JetBrains s.r.o. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import javax.swing.*; +import java.awt.*; +import java.awt.event.*; +import java.io.File; +import java.nio.file.Files; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; + +/** + * @test + * @summary Regression test for JBR-2533 Popup is not focused on click when switching from another application on macOS + * @key headful + */ + +public class PopupIncomingFocusTest { + private static final CompletableFuture windowOpened = new CompletableFuture<>(); + private static final CompletableFuture popupOpened = new CompletableFuture<>(); + private static final CompletableFuture result = new CompletableFuture<>(); + private static Robot robot; + private static Process otherProcess; + private static JFrame frame; + private static JButton button; + private static JWindow popup; + private static JTextField field; + + public static void main(String[] args) throws Exception { + robot = new Robot(); + robot.setAutoWaitForIdle(true); + try { + SwingUtilities.invokeAndWait(PopupIncomingFocusTest::init); + windowOpened.get(10, TimeUnit.SECONDS); + launchProcessWithWindow(); + clickAt(button); + popupOpened.get(10, TimeUnit.SECONDS); + clickAt(400, 100); // other process' window + clickAt(field); + pressEnter(); + result.get(10, TimeUnit.SECONDS); + } + finally { + SwingUtilities.invokeAndWait(PopupIncomingFocusTest::shutdown); + } + } + + private static void init() { + button = new JButton("Open popup"); + button.addActionListener(e -> { + popup.setVisible(true); + }); + + frame = new JFrame(); + frame.add(button); + frame.setBounds(50, 50, 200, 100); + frame.addWindowFocusListener(new WindowAdapter() { + @Override + public void windowGainedFocus(WindowEvent e) { + windowOpened.complete(Boolean.TRUE); + } + }); + + field = new JTextField(10); + field.getCaret().setBlinkRate(0); // prevent caret blink timer from keeping event thread running + field.addActionListener(e -> result.complete(Boolean.TRUE)); + + popup = new JWindow(frame); + popup.setType(Window.Type.POPUP); + popup.add(field); + popup.pack(); + popup.setLocation(50, 200); + popup.addWindowFocusListener(new WindowAdapter() { + @Override + public void windowGainedFocus(WindowEvent e) { + popupOpened.complete(Boolean.TRUE); + } + }); + + frame.setVisible(true); + } + + private static void shutdown() { + if (frame != null) frame.dispose(); + if (otherProcess != null) otherProcess.destroyForcibly(); + } + + private static void clickAt(int x, int y) { + robot.delay(1000); // needed for GNOME, to give it some time to update internal state after window showing + robot.mouseMove(x, y); + robot.mousePress(InputEvent.BUTTON1_DOWN_MASK); + robot.mouseRelease(InputEvent.BUTTON1_DOWN_MASK); + } + + private static void clickAt(Component component) { + Point location = component.getLocationOnScreen(); + clickAt(location.x + component.getWidth() / 2, location.y + component.getHeight() / 2); + } + + private static void pressEnter() { + robot.keyPress(KeyEvent.VK_ENTER); + robot.keyRelease(KeyEvent.VK_ENTER); + } + + private static void launchProcessWithWindow() throws Exception { + String javaPath = System.getProperty("java.home") + File.separator + "bin" + File.separator + "java"; + File tmpFile = File.createTempFile("PopupIncomingFocusTest", ".java"); + tmpFile.deleteOnExit(); + Files.writeString(tmpFile.toPath(), "import javax.swing.*;\n" + + "import java.awt.event.*;\n" + + "\n" + + "public class TestWindow {\n" + + " public static void main(String[] args) {\n" + + " SwingUtilities.invokeLater(() -> {\n" + + " JFrame f = new JFrame();\n" + + " f.addWindowFocusListener(new WindowAdapter() {\n" + + " @Override\n" + + " public void windowGainedFocus(WindowEvent e) {\n" + + " System.out.println();\n" + + " }\n" + + " });\n" + + " f.setBounds(300, 50, 200, 100);\n" + + " f.setVisible(true);\n" + + " });\n" + + " }\n" + + "}\n"); + otherProcess = Runtime.getRuntime().exec(new String[]{javaPath, tmpFile.getAbsolutePath()}); + if (otherProcess.getInputStream().read() == -1) { + throw new RuntimeException("Error starting process"); + } + } +} diff --git a/test/jdk/jb/java/awt/Focus/RequestFocusInParent.java b/test/jdk/jb/java/awt/Focus/RequestFocusInParent.java new file mode 100644 index 000000000000..82a3db661449 --- /dev/null +++ b/test/jdk/jb/java/awt/Focus/RequestFocusInParent.java @@ -0,0 +1,97 @@ +/* + * Copyright 2021 JetBrains s.r.o. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import javax.swing.*; +import java.awt.*; +import java.awt.event.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; + +/** + * @test + * @summary Regression test for JBR-3979 Focus is not transferred to parent window + * @key headful + */ + +public class RequestFocusInParent { + private static CompletableFuture result; + + private static Robot robot; + private static JFrame frame; + private static JButton button1; + private static JButton button2; + + public static void main(String[] args) throws Exception { + robot = new Robot(); + try { + SwingUtilities.invokeAndWait(RequestFocusInParent::initUI); + robot.delay(1000); // wait for frame to appear + clickOn(button1); + robot.delay(1000); // wait for popup to appear + SwingUtilities.invokeAndWait(() -> result = new CompletableFuture<>()); + clickOn(button2); + result.get(5, TimeUnit.SECONDS); + } finally { + SwingUtilities.invokeAndWait(RequestFocusInParent::disposeUI); + } + } + + private static void initUI() { + frame = new JFrame("RequestFocusInParent"); + button1 = new JButton("Open popup"); + button1.addActionListener(e -> { + JWindow popup = new JWindow(frame); + button2 = new JButton("Return focus"); + button2.addActionListener(ee -> button1.requestFocus()); + popup.add(button2); + popup.setSize(100, 100); + popup.setLocation(100, 400); + popup.setVisible(true); + }); + button1.addFocusListener(new FocusAdapter() { + @Override + public void focusGained(FocusEvent e) { + if (result != null) result.complete(true); + } + }); + frame.add(button1); + frame.setSize(100, 100); + frame.setLocation(100, 100); + frame.setVisible(true); + } + + private static void disposeUI() { + if (frame != null) frame.dispose(); + } + + private static void clickAt(int x, int y) { + robot.mouseMove(x, y); + robot.mousePress(InputEvent.BUTTON1_DOWN_MASK); + robot.mouseRelease(InputEvent.BUTTON1_DOWN_MASK); + } + + private static void clickOn(Component component) { + Point location = component.getLocationOnScreen(); + clickAt(location.x + component.getWidth() / 2, location.y + component.getHeight() / 2); + } +} diff --git a/test/jdk/jb/java/awt/Focus/WindowWithoutParentTest.java b/test/jdk/jb/java/awt/Focus/WindowWithoutParentTest.java new file mode 100644 index 000000000000..277f0e6faca8 --- /dev/null +++ b/test/jdk/jb/java/awt/Focus/WindowWithoutParentTest.java @@ -0,0 +1,113 @@ +/* + * Copyright 2000-2023 JetBrains s.r.o. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import javax.swing.*; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import java.awt.*; +import java.awt.event.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; + +/** + * @test + * @summary Regression test for JBR-2854 [macOS] Undecorated window without parent steals focus on showing + * @key headful + */ + +public class WindowWithoutParentTest { + private static final CompletableFuture initFinished = new CompletableFuture<>(); + private static final CompletableFuture typedInField = new CompletableFuture<>(); + private static Robot robot; + private static JFrame frame; + private static JTextField field; + private static JWindow window; + + public static void main(String[] args) throws Exception { + robot = new Robot(); + robot.setAutoDelay(50); // ensure different timestamps for key events (can impact typeahead logic) + try { + SwingUtilities.invokeAndWait(WindowWithoutParentTest::initUI); + initFinished.get(10, TimeUnit.SECONDS); + clickOn(field); + pressAndRelease(KeyEvent.VK_ENTER); + pressAndRelease(KeyEvent.VK_A); + typedInField.get(10, TimeUnit.SECONDS); + } finally { + SwingUtilities.invokeAndWait(WindowWithoutParentTest::disposeUI); + } + } + + private static void initUI() { + frame = new JFrame("WindowWithoutParentTest"); + field = new JTextField(20); + field.addFocusListener(new FocusAdapter() { + @Override + public void focusGained(FocusEvent e) { + initFinished.complete(true); + } + }); + field.addActionListener(e -> { + window = new JWindow(); + window.setSize(50, 50); + window.setVisible(true); + }); + field.getDocument().addDocumentListener(new DocumentListener() { + @Override + public void insertUpdate(DocumentEvent e) { + typedInField.complete(true); + } + + @Override + public void removeUpdate(DocumentEvent e) {} + + @Override + public void changedUpdate(DocumentEvent e) {} + }); + frame.add(field); + frame.pack(); + frame.setVisible(true); + } + + private static void disposeUI() { + if (window != null) window.dispose(); + if (frame != null) frame.dispose(); + } + + private static void pressAndRelease(int keyCode) { + robot.keyPress(keyCode); + robot.keyRelease(keyCode); + } + + private static void clickAt(int x, int y) { + robot.delay(1000); // needed for GNOME, to give it some time to update internal state after window showing + robot.mouseMove(x, y); + robot.mousePress(InputEvent.BUTTON1_DOWN_MASK); + robot.mouseRelease(InputEvent.BUTTON1_DOWN_MASK); + } + + private static void clickOn(Component component) { + Point location = component.getLocationOnScreen(); + clickAt(location.x + component.getWidth() / 2, location.y + component.getHeight() / 2); + } +}