JBR-3611, JBR-3633, JBR-3666, JBR-3663, JBR-3671, JBR-3673, JBR-4181, JBR-4186, JBR-4893 Interoperability with macOS desktop spaces

(cherry-picked from commits 43fdd6cd26, 75335543f2, a156c6b9bf, 9fdc75969b, 1dcc612a81, 93588d0738, 94a3885bbe, c040e05703, 67b6cd871f, 9040fd56cd, 6349b86b7f)

(cherry picked from commit 4fb4d51b89)
This commit is contained in:
Dmitry Batrak
2021-07-16 16:33:08 +03:00
committed by jbrbot
parent b0e672d5d8
commit 3312d576ba
13 changed files with 1123 additions and 83 deletions

View File

@@ -35,6 +35,7 @@ import java.awt.FontMetrics;
import java.awt.Frame;
import java.awt.GraphicsDevice;
import java.awt.Insets;
import java.awt.KeyboardFocusManager;
import java.awt.MenuBar;
import java.awt.Point;
import java.awt.Rectangle;
@@ -43,6 +44,7 @@ import java.awt.Window;
import java.awt.event.FocusEvent;
import java.awt.event.WindowEvent;
import java.awt.event.WindowStateListener;
import java.awt.peer.ComponentPeer;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.lang.reflect.InvocationTargetException;
@@ -98,6 +100,7 @@ public class CPlatformWindow extends CFRetainedResource implements PlatformWindo
private static native void nativeExitFullScreenMode(long nsWindowPtr);
static native CPlatformWindow nativeGetTopmostPlatformWindowUnderMouse();
private static native void nativeRaiseLevel(long nsWindowPtr, boolean popup, boolean onlyIfParentIsActive);
private static native boolean nativeDelayShowing(long nsWindowPtr);
// Logger to report issues happened during execution but that do not affect functionality
private static final PlatformLogger logger = PlatformLogger.getLogger("sun.lwawt.macosx.CPlatformWindow");
@@ -684,6 +687,16 @@ public class CPlatformWindow extends CFRetainedResource implements PlatformWindo
return this.visible;
}
private static LWWindowPeer getBlockerFor(Window window) {
if (window != null) {
ComponentPeer peer = AWTAccessor.getComponentAccessor().getPeer(window);
if (peer instanceof LWWindowPeer) {
return ((LWWindowPeer)peer).getBlocker();
}
}
return null;
}
@Override // PlatformWindow
public void setVisible(boolean visible) {
// Configure stuff
@@ -736,7 +749,17 @@ public class CPlatformWindow extends CFRetainedResource implements PlatformWindo
// Actually show or hide the window
LWWindowPeer blocker = (peer == null)? null : peer.getBlocker();
if (blocker == null || !visible) {
if (visible && delayShowing()) {
if (blocker == null) {
Window focusedWindow = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusedWindow();
LWWindowPeer focusedWindowBlocker = getBlockerFor(focusedWindow);
if (focusedWindowBlocker == peer) {
// try to switch to target space if we're adding a modal dialog
// that would block currently focused window
owner.execute(CWrapper.NSWindow::orderFront);
}
}
} else if (blocker == null || !visible) {
// If it ain't blocked, or is being hidden, go regular way
if (visible) {
contentView.execute(viewPtr -> {
@@ -803,7 +826,7 @@ public class CPlatformWindow extends CFRetainedResource implements PlatformWindo
// Manage parent-child relationship when showing
final ComponentAccessor acc = AWTAccessor.getComponentAccessor();
if (visible) {
if (visible && !delayShowing()) {
// Order myself above my parent
if (owner != null && owner.isVisible()) {
owner.execute(ownerPtr -> {
@@ -835,7 +858,10 @@ public class CPlatformWindow extends CFRetainedResource implements PlatformWindo
// Deal with the blocker of the window being shown
if (blocker != null && visible) {
// Make sure the blocker is above its siblings
((CPlatformWindow)blocker.getPlatformWindow()).orderAboveSiblings();
CPlatformWindow blockerWindow = (CPlatformWindow) blocker.getPlatformWindow();
if (!blockerWindow.delayShowing()) {
blockerWindow.orderAboveSiblings();
}
}
}
@@ -870,6 +896,7 @@ public class CPlatformWindow extends CFRetainedResource implements PlatformWindo
@Override // PlatformWindow
public void toFront() {
if (delayShowing()) return;
LWCToolkit lwcToolkit = (LWCToolkit) Toolkit.getDefaultToolkit();
Window w = DefaultKeyboardFocusManager.getCurrentKeyboardFocusManager().getActiveWindow();
final ComponentAccessor acc = AWTAccessor.getComponentAccessor();
@@ -923,6 +950,7 @@ public class CPlatformWindow extends CFRetainedResource implements PlatformWindo
@Override
public boolean requestWindowFocus() {
if (delayShowing()) return false;
execute(ptr -> {
if (CWrapper.NSWindow.canBecomeMainWindow(ptr)) {
CWrapper.NSWindow.makeMainWindow(ptr);
@@ -941,6 +969,17 @@ public class CPlatformWindow extends CFRetainedResource implements PlatformWindo
return ref.get();
}
// We want a window to be always shown at the same space as its owning window.
// But macOS doesn't have an API to control the target space for a window -
// it's always shown at the active space. So if the target space isn't active now,
// the only way to achieve our goal seems to be delaying the appearance of the
// window till the target space becomes active.
private boolean delayShowing() {
AtomicBoolean ref = new AtomicBoolean(false);
execute(ptr -> ref.set(nativeDelayShowing(ptr)));
return ref.get();
}
@Override
public void updateFocusableWindowState() {
setStyleBits(SHOULD_BECOME_KEY | SHOULD_BECOME_MAIN, getFocusableStyleBits()); // set both bits at once
@@ -1265,29 +1304,32 @@ public class CPlatformWindow extends CFRetainedResource implements PlatformWindo
isFocusable ? focusableStyleBits : 0); // set both bits at once
}
private boolean checkBlockingAndOrder() {
private void checkBlockingAndOrder() {
LWWindowPeer blocker = (peer == null)? null : peer.getBlocker();
if (blocker == null) {
return false;
// If it's not blocked, make sure it's above its siblings
orderAboveSiblings();
return;
}
if (blocker instanceof CPrinterDialogPeer) {
return true;
return;
}
CPlatformWindow pWindow = (CPlatformWindow)blocker.getPlatformWindow();
pWindow.orderAboveSiblings();
if (!pWindow.delayShowing()) {
pWindow.orderAboveSiblings();
pWindow.execute(ptr -> {
if (logger.isLoggable(PlatformLogger.Level.FINE)) {
logger.fine("Focus blocker " + Long.toHexString(ptr));
}
CWrapper.NSWindow.orderFrontRegardless(ptr);
CWrapper.NSWindow.makeKeyAndOrderFront(ptr);
CWrapper.NSWindow.makeMainWindow(ptr);
});
return true;
pWindow.execute(ptr -> {
if (logger.isLoggable(PlatformLogger.Level.FINE)) {
logger.fine("Focus blocker " + Long.toHexString(ptr));
}
CWrapper.NSWindow.orderFrontRegardless(ptr);
CWrapper.NSWindow.makeKeyAndOrderFront(ptr);
CWrapper.NSWindow.makeMainWindow(ptr);
});
}
}
private boolean isIconified() {
@@ -1325,7 +1367,7 @@ public class CPlatformWindow extends CFRetainedResource implements PlatformWindo
// which is going to become 'main window', are placed above their siblings.
CPlatformWindow rootOwner = getRootOwner();
if (rootOwner.isVisible() && !rootOwner.isIconified() && !rootOwner.isActive()) {
rootOwner.execute(CWrapper.NSWindow::orderFront);
rootOwner.execute(CWrapper.NSWindow::orderFrontIfOnActiveSpace);
}
// Do not order child windows of iconified owner.
@@ -1349,7 +1391,7 @@ public class CPlatformWindow extends CFRetainedResource implements PlatformWindo
if (p instanceof LWWindowPeer) {
CPlatformWindow pw = (CPlatformWindow)((LWWindowPeer)p).getPlatformWindow();
iconified = isIconified();
if (pw != null && pw.isVisible() && !iconified) {
if (pw != null && pw.isVisible() && !iconified && !pw.delayShowing()) {
// If the window is one of ancestors of 'main window' or is going to become main by itself,
// the window should be ordered above its siblings; otherwise the window is just ordered
// above its nearest parent.
@@ -1361,7 +1403,7 @@ public class CPlatformWindow extends CFRetainedResource implements PlatformWindo
}
pwUnder.execute(underPtr -> {
pw.execute(ptr -> {
CWrapper.NSWindow.orderWindow(ptr, CWrapper.NSWindow.NSWindowAbove, underPtr);
CWrapper.NSWindow.orderWindowIfOnActiveSpace(ptr, CWrapper.NSWindow.NSWindowAbove, underPtr);
});
});
pwUnder = pw;
@@ -1421,9 +1463,7 @@ public class CPlatformWindow extends CFRetainedResource implements PlatformWindo
private void windowDidBecomeMain() {
lastBecomeMainTime = System.currentTimeMillis();
if (checkBlockingAndOrder()) return;
// If it's not blocked, make sure it's above its siblings
orderAboveSiblings();
checkBlockingAndOrder();
}
private void windowWillEnterFullScreen() {

View File

@@ -56,8 +56,10 @@ final class CWrapper {
static native boolean isKeyWindow(long window);
static native void orderFront(long window);
static native void orderFrontIfOnActiveSpace(long window);
static native void orderFrontRegardless(long window);
static native void orderWindow(long window, int ordered, long relativeTo);
static native void orderWindowIfOnActiveSpace(long window, int ordered, long relativeTo);
/**
* Removes the window from the screen.

View File

@@ -67,6 +67,9 @@ static AWTWindow* lastKeyWindow = nil;
// It would be NSZeroPoint if 'Location by Platform' is not used.
static NSPoint lastTopLeftPoint;
static BOOL fullScreenTransitionInProgress = NO;
static BOOL orderingScheduled = NO;
// --------------------------------------------------------------
// NSWindow/NSPanel descendants implementation
#define AWT_NS_WINDOW_IMPLEMENTATION \
@@ -323,9 +326,11 @@ AWT_NS_WINDOW_IMPLEMENTATION
if (IS(mask, FULLSCREENABLE) && [self.nsWindow respondsToSelector:@selector(toggleFullScreen:)]) {
if (IS(bits, FULLSCREENABLE)) {
[self.nsWindow setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary];
self.nsWindow.collectionBehavior = self.nsWindow.collectionBehavior |
NSWindowCollectionBehaviorFullScreenPrimary;
} else {
[self.nsWindow setCollectionBehavior:NSWindowCollectionBehaviorDefault];
self.nsWindow.collectionBehavior = self.nsWindow.collectionBehavior &
~NSWindowCollectionBehaviorFullScreenPrimary;
}
}
@@ -394,16 +399,14 @@ AWT_ASSERT_APPKIT_THREAD;
self.ownerWindow = owner;
[self setPropertiesForStyleBits:styleBits mask:MASK(_METHOD_PROP_BITMASK)];
if (IS(self.styleBits, IS_POPUP)) {
[self.nsWindow setCollectionBehavior:NSWindowCollectionBehaviorFullScreenAuxiliary];
}
if (IS(bits, SHEET) && owner != nil) {
[self.nsWindow setStyleMask: NSWindowStyleMaskDocModalWindow];
}
self.isJustCreated = YES;
self.nsWindow.collectionBehavior = NSWindowCollectionBehaviorManaged;
return self;
}
@@ -533,6 +536,69 @@ AWT_ASSERT_APPKIT_THREAD;
return isVisible;
}
- (BOOL) delayShowing {
AWT_ASSERT_APPKIT_THREAD;
return ownerWindow != nil &&
([ownerWindow delayShowing] || !ownerWindow.nsWindow.onActiveSpace) &&
!nsWindow.visible;
}
- (BOOL) checkBlockingAndOrder {
AWT_ASSERT_APPKIT_THREAD;
JNIEnv *env = [ThreadUtilities getJNIEnv];
jobject platformWindow = (*env)->NewLocalRef(env, self.javaPlatformWindow);
if (platformWindow != NULL) {
GET_CPLATFORM_WINDOW_CLASS_RETURN(NO);
DECLARE_METHOD_RETURN(jm_checkBlockingAndOrder, jc_CPlatformWindow, "checkBlockingAndOrder", "()V", NO);
(*env)->CallVoidMethod(env, platformWindow, jm_checkBlockingAndOrder);
CHECK_EXCEPTION();
(*env)->DeleteLocalRef(env, platformWindow);
}
return YES;
}
+ (void)activeSpaceDidChange {
AWT_ASSERT_APPKIT_THREAD;
if (fullScreenTransitionInProgress) {
orderingScheduled = YES;
return;
}
// show delayed windows
for (NSWindow *window in NSApp.windows) {
if ([AWTWindow isJavaPlatformWindowVisible:window] && !window.visible) {
AWTWindow *awtWindow = (AWTWindow *)[window delegate];
while (awtWindow.ownerWindow != nil) {
awtWindow = awtWindow.ownerWindow;
}
if (awtWindow.nsWindow.visible && awtWindow.nsWindow.onActiveSpace) {
[awtWindow checkBlockingAndOrder];
}
}
}
}
- (void) processVisibleChildren:(void(^)(AWTWindow*))action {
NSEnumerator *windowEnumerator = [[NSApp windows]objectEnumerator];
NSWindow *window;
while ((window = [windowEnumerator nextObject]) != nil) {
if ([AWTWindow isJavaPlatformWindowVisible:window]) {
AWTWindow *awtWindow = (AWTWindow *)[window delegate];
AWTWindow *parent = awtWindow.ownerWindow;
while (parent != nil) {
if (parent == self) {
action(awtWindow);
break;
}
parent = parent.ownerWindow;
}
}
}
}
// Orders window children based on the current focus state
- (void) orderChildWindows:(BOOL)focus {
AWT_ASSERT_APPKIT_THREAD;
@@ -542,37 +608,23 @@ AWT_ASSERT_APPKIT_THREAD;
return;
}
NSEnumerator *windowEnumerator = [[NSApp windows]objectEnumerator];
NSWindow *window;
while ((window = [windowEnumerator nextObject]) != nil) {
if ([AWTWindow isJavaPlatformWindowVisible:window]) {
AWTWindow *awtWindow = (AWTWindow *)[window delegate];
AWTWindow *owner = awtWindow.ownerWindow;
if (IS(awtWindow.styleBits, ALWAYS_ON_TOP)) {
// Do not order 'always on top' windows
continue;
}
while (awtWindow.ownerWindow != nil) {
if (awtWindow.ownerWindow == self) {
if (focus) {
// Move the childWindow to floating level
// so it will appear in front of its
// parent which owns the focus
[window setLevel:NSFloatingWindowLevel];
} else {
// Focus owner has changed, move the childWindow
// back to normal window level
[window setLevel:NSNormalWindowLevel];
}
// The childWindow should be displayed in front of
// its nearest parentWindow
[window orderWindow:NSWindowAbove relativeTo:[owner.nsWindow windowNumber]];
break;
}
awtWindow = awtWindow.ownerWindow;
[self processVisibleChildren:^void(AWTWindow* child){
// Do not order 'always on top' windows
if (!IS(child.styleBits, ALWAYS_ON_TOP)) {
NSWindow *window = child.nsWindow;
NSWindow *owner = child.ownerWindow.nsWindow;
if (focus) {
// Move the childWindow to floating level
// so it will appear in front of its
// parent which owns the focus
[window setLevel:NSFloatingWindowLevel];
} else {
// Focus owner has changed, move the childWindow
// back to normal window level
[window setLevel:NSNormalWindowLevel];
}
}
}
}];
}
// NSWindow overrides
@@ -601,15 +653,7 @@ AWT_ASSERT_APPKIT_THREAD;
// We should bring up the modal dialog manually
[AWTToolkit eventCountPlusPlus];
JNIEnv *env = [ThreadUtilities getJNIEnv];
jobject platformWindow = (*env)->NewLocalRef(env, self.javaPlatformWindow);
if (platformWindow != NULL) {
GET_CPLATFORM_WINDOW_CLASS_RETURN(NO);
DECLARE_METHOD_RETURN(jm_checkBlockingAndOrder, jc_CPlatformWindow, "checkBlockingAndOrder", "()Z", NO);
(*env)->CallBooleanMethod(env, platformWindow, jm_checkBlockingAndOrder);
CHECK_EXCEPTION();
(*env)->DeleteLocalRef(env, platformWindow);
}
if (![self checkBlockingAndOrder]) return NO;
}
return self.isEnabled && IS(self.styleBits, SHOULD_BECOME_MAIN);
@@ -684,24 +728,14 @@ AWT_ASSERT_APPKIT_THREAD;
- (void) iconifyChildWindows:(BOOL)iconify {
AWT_ASSERT_APPKIT_THREAD;
NSEnumerator *windowEnumerator = [[NSApp windows]objectEnumerator];
NSWindow *window;
while ((window = [windowEnumerator nextObject]) != nil) {
if ([AWTWindow isJavaPlatformWindowVisible:window]) {
AWTWindow *awtWindow = (AWTWindow *)[window delegate];
while (awtWindow.ownerWindow != nil) {
if (awtWindow.ownerWindow == self) {
if (iconify) {
[window orderOut:window];
} else {
[window orderFront:window];
}
break;
}
awtWindow = awtWindow.ownerWindow;
}
[self processVisibleChildren:^void(AWTWindow* child){
NSWindow *window = child.nsWindow;
if (iconify) {
[window orderOut:window];
} else {
[window orderFront:window];
}
}
}];
}
- (void) _deliverIconify:(BOOL)iconify {
@@ -934,8 +968,33 @@ AWT_ASSERT_APPKIT_THREAD;
}
}
// this is required to move owned windows to the full-screen space when owner goes to full-screen mode
- (void)allowMovingChildrenBetweenSpaces:(BOOL)allow {
[self processVisibleChildren:^void(AWTWindow* child){
NSWindow *window = child.nsWindow;
NSWindowCollectionBehavior behavior = window.collectionBehavior;
behavior &= ~(NSWindowCollectionBehaviorManaged | NSWindowCollectionBehaviorTransient);
behavior |= allow ? NSWindowCollectionBehaviorTransient : NSWindowCollectionBehaviorManaged;
window.collectionBehavior = behavior;
}];
}
- (void) fullScreenTransitionStarted {
fullScreenTransitionInProgress = YES;
}
- (void) fullScreenTransitionFinished {
fullScreenTransitionInProgress = NO;
if (orderingScheduled) {
orderingScheduled = NO;
[self checkBlockingAndOrder];
}
}
- (void)windowWillEnterFullScreen:(NSNotification *)notification {
[self fullScreenTransitionStarted];
[self allowMovingChildrenBetweenSpaces:YES];
JNIEnv *env = [ThreadUtilities getJNIEnv];
GET_CPLATFORM_WINDOW_CLASS();
DECLARE_METHOD(jm_windowWillEnterFullScreen, jc_CPlatformWindow, "windowWillEnterFullScreen", "()V");
@@ -949,6 +1008,9 @@ AWT_ASSERT_APPKIT_THREAD;
}
- (void)windowDidEnterFullScreen:(NSNotification *)notification {
[self allowMovingChildrenBetweenSpaces:NO];
[self fullScreenTransitionFinished];
JNIEnv *env = [ThreadUtilities getJNIEnv];
GET_CPLATFORM_WINDOW_CLASS();
DECLARE_METHOD(jm_windowDidEnterFullScreen, jc_CPlatformWindow, "windowDidEnterFullScreen", "()V");
@@ -963,6 +1025,8 @@ AWT_ASSERT_APPKIT_THREAD;
}
- (void)windowWillExitFullScreen:(NSNotification *)notification {
[self fullScreenTransitionStarted];
JNIEnv *env = [ThreadUtilities getJNIEnv];
GET_CPLATFORM_WINDOW_CLASS();
DECLARE_METHOD(jm_windowWillExitFullScreen, jc_CPlatformWindow, "windowWillExitFullScreen", "()V");
@@ -981,6 +1045,8 @@ AWT_ASSERT_APPKIT_THREAD;
}
- (void)windowDidExitFullScreen:(NSNotification *)notification {
[self fullScreenTransitionFinished];
JNIEnv *env = [ThreadUtilities getJNIEnv];
jobject platformWindow = (*env)->NewLocalRef(env, self.javaPlatformWindow);
if (platformWindow != NULL) {
@@ -1815,3 +1881,26 @@ JNI_COCOA_ENTER(env);
JNI_COCOA_EXIT(env);
}
/*
* Class: sun_lwawt_macosx_CPlatformWindow
* Method: nativeDelayShowing
* Signature: (J)Z
*/
JNIEXPORT jboolean JNICALL Java_sun_lwawt_macosx_CPlatformWindow_nativeDelayShowing
(JNIEnv *env, jclass clazz, jlong windowPtr)
{
__block jboolean result = JNI_FALSE;
JNI_COCOA_ENTER(env);
NSWindow *nsWindow = (NSWindow *)jlong_to_ptr(windowPtr);
[ThreadUtilities performOnMainThreadWaiting:YES block:^(){
AWTWindow *window = (AWTWindow*)[nsWindow delegate];
result = [window delayShowing];
}];
JNI_COCOA_EXIT(env);
return result;
}

View File

@@ -37,6 +37,7 @@
#import "ThreadUtilities.h"
#import "NSApplicationAWT.h"
#import "JNIUtilities.h"
#import "AWTWindow.h"
#pragma mark App Menu helpers
@@ -268,6 +269,11 @@ AWT_ASSERT_APPKIT_THREAD;
[ctr addObserver:clz selector:@selector(_appDidUnhide) name:NSApplicationDidUnhideNotification object:nil];
[ctr addObserver:clz selector:@selector(_didChangeScreenParameters) name:NSApplicationDidChangeScreenParametersNotification object:nil];
[[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:[AWTWindow class]
selector:@selector(activeSpaceDidChange)
name:NSWorkspaceActiveSpaceDidChangeNotification
object:nil];
return self;
}

View File

@@ -154,6 +154,25 @@ JNI_COCOA_ENTER(env);
JNI_COCOA_EXIT(env);
}
/*
* Class: sun_lwawt_macosx_CWrapper$NSWindow
* Method: orderFrontIfOnActiveSpace
* Signature: (J)V
*/
JNIEXPORT void JNICALL
Java_sun_lwawt_macosx_CWrapper_00024NSWindow_orderFrontIfOnActiveSpace
(JNIEnv *env, jclass cls, jlong windowPtr)
{
JNI_COCOA_ENTER(env);
NSWindow *window = (NSWindow *)jlong_to_ptr(windowPtr);
[ThreadUtilities performOnMainThreadWaiting:NO block:^(){
if (window.onActiveSpace) [window orderFront:window];
}];
JNI_COCOA_EXIT(env);
}
/*
* Class: sun_lwawt_macosx_CWrapper$NSWindow
* Method: nativeOrderOut
@@ -231,6 +250,26 @@ JNI_COCOA_ENTER(env);
JNI_COCOA_EXIT(env);
}
/*
* Class: sun_lwawt_macosx_CWrapper$NSWindow
* Method: orderWindowIfOnActiveSpace
* Signature: (JIJ)V
*/
JNIEXPORT void JNICALL
Java_sun_lwawt_macosx_CWrapper_00024NSWindow_orderWindowIfOnActiveSpace
(JNIEnv *env, jclass cls, jlong windowPtr, jint order, jlong relativeToPtr)
{
JNI_COCOA_ENTER(env);
NSWindow *window = (NSWindow *)jlong_to_ptr(windowPtr);
NSWindow *relativeTo = (NSWindow *)jlong_to_ptr(relativeToPtr);
[ThreadUtilities performOnMainThreadWaiting:NO block:^(){
if (window.onActiveSpace) [window orderWindow:(NSWindowOrderingMode)order relativeTo:[relativeTo windowNumber]];
}];
JNI_COCOA_EXIT(env);
}
// Used for CWrapper.NSWindow.setLevel() (and level() which isn't implemented yet)
static NSInteger LEVELS[sun_lwawt_macosx_CWrapper_NSWindow_MAX_WINDOW_LEVELS];
static void initLevels()

View File

@@ -0,0 +1,106 @@
/*
* Copyright 2000-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.KeyEvent;
import java.io.File;
import java.nio.file.Files;
/**
* @test
* @summary Regression test for JBR-3671 Window order changes for a background app on macOS desktop space switch
* @key headful
* @requires (os.family == "mac")
* @compile MacSpacesUtil.java
* @run main BackgroundWindowOrderOnSpaceChange
*/
public class BackgroundWindowOrderOnSpaceChange {
private static JFrame frame1;
private static JFrame frame2;
private static Process otherProcess;
public static void main(String[] args) throws Exception {
MacSpacesUtil.ensureMoreThanOneSpaceExists();
try {
SwingUtilities.invokeAndWait(BackgroundWindowOrderOnSpaceChange::initUI);
launchProcessWithWindow();
MacSpacesUtil.switchToNextSpace();
MacSpacesUtil.switchToPreviousSpace();
Color color = new Robot().getPixelColor(400, 400);
if (!Color.green.equals(color)) {
throw new RuntimeException("Frame 1 isn't shown on top. Found color: " + color);
}
}
finally {
SwingUtilities.invokeAndWait(BackgroundWindowOrderOnSpaceChange::disposeUI);
}
}
private static void initUI() {
frame1 = new JFrame("BackgroundWindowOrderOnSpaceChange 1");
frame1.getContentPane().setBackground(Color.green);
frame1.setBounds(100, 100, 400, 400);
frame1.setVisible(true);
frame2 = new JFrame("BackgroundWindowOrderOnSpaceChange 2");
frame2.getContentPane().setBackground(Color.red);
frame2.setBounds(300, 300, 400, 400);
frame2.setVisible(true);
frame1.toFront();
}
private static void disposeUI() {
if (frame1 != null) frame1.dispose();
if (frame2 != null) frame2.dispose();
if (otherProcess != null) otherProcess.destroyForcibly();
}
private static void launchProcessWithWindow() throws Exception {
String javaPath = System.getProperty("java.home") + File.separator + "bin" + File.separator + "java";
File tmpFile = File.createTempFile("BackgroundWindowOrderOnSpaceChange", ".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(\"BackgroundWindowOrderOnSpaceChange 3\");\n" +
" f.addWindowFocusListener(new WindowAdapter() {\n" +
" @Override\n" +
" public void windowGainedFocus(WindowEvent e) {\n" +
" System.out.println();\n" +
" }\n" +
" });\n" +
" f.setBounds(800, 100, 200, 200);\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");
}
}
}

View File

@@ -0,0 +1,123 @@
/*
* 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 com.apple.eawt.Application;
import com.apple.eawt.FullScreenAdapter;
import com.apple.eawt.FullScreenUtilities;
import com.apple.eawt.event.FullScreenEvent;
import javax.swing.*;
import java.awt.*;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
/**
* @test
* @summary Regression test for JBR-3611 Unexpected workspace switch with dialog in full-screen mode on macOS
* @key headful
* @requires (os.family == "mac")
* @modules java.desktop/com.apple.eawt
* java.desktop/com.apple.eawt.event
* @compile MacSpacesUtil.java
* @run main FullScreenChildWindow
*/
public class FullScreenChildWindow {
private static final CompletableFuture<Boolean> shownAtFullScreen = new CompletableFuture<>();
private static final CompletableFuture<Boolean> dialogShown = new CompletableFuture<>();
private static Robot robot;
private static JFrame frame2;
private static JFrame frame1;
private static JButton button;
public static void main(String[] args) throws Exception {
robot = new Robot();
try {
SwingUtilities.invokeAndWait(FullScreenChildWindow::initUI);
shownAtFullScreen.get(5, TimeUnit.SECONDS);
clickAt(button);
dialogShown.get(5, TimeUnit.SECONDS);
MacSpacesUtil.switchToPreviousSpace();
if (!frame1.isFocused()) {
throw new RuntimeException("Unexpected state");
}
} finally {
SwingUtilities.invokeAndWait(FullScreenChildWindow::disposeUI);
}
}
private static void initUI() {
frame1 = new JFrame("FullScreenChildWindow(1)");
frame1.setSize(100, 100);
frame1.setLocation(100, 100);
frame1.setVisible(true);
frame2 = new JFrame("FullScreenChildWindow(2)");
button = new JButton("Open dialog");
button.addActionListener(e -> {
JDialog d = new JDialog(frame2, "dialog", false);
d.setSize(100, 100);
d.setLocationRelativeTo(null);
d.setAutoRequestFocus(false);
d.addWindowListener(new WindowAdapter() {
@Override
public void windowOpened(WindowEvent e) {
dialogShown.complete(true);
}
});
d.setVisible(true);
});
frame2.add(button);
frame2.setSize(100, 100);
frame2.setLocation(100, 300);
frame2.setVisible(true);
FullScreenUtilities.addFullScreenListenerTo(frame2, new FullScreenAdapter() {
@Override
public void windowEnteredFullScreen(FullScreenEvent e) {
shownAtFullScreen.complete(true);
}
});
Application.getApplication().requestToggleFullScreen(frame2);
}
private static void disposeUI() {
if (frame2 != null) frame2.dispose();
if (frame1 != null) frame1.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 clickAt(Component component) {
Point location = component.getLocationOnScreen();
clickAt(location.x + component.getWidth() / 2, location.y + component.getHeight() / 2);
}
}

View File

@@ -0,0 +1,78 @@
/*
* 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 com.apple.eawt.Application;
import java.awt.Window;
import javax.swing.*;
/**
* @test
* @summary Regression test for JBR-3666 Child window stays on default space
* when full-screen mode is activated for parent window on macOS
* @key headful
* @requires (os.family == "mac")
* @modules java.desktop/com.apple.eawt
* @compile MacSpacesUtil.java
* @run main FullScreenChildWindowShownBefore
*/
public class FullScreenChildWindowShownBefore {
private static JFrame frame;
private static JDialog dialog;
public static void main(String[] args) throws Exception {
try {
SwingUtilities.invokeAndWait(FullScreenChildWindowShownBefore::initUI);
Thread.sleep(1000); // wait for windows to appear
SwingUtilities.invokeAndWait(() -> Application.getApplication().requestToggleFullScreen(frame));
Thread.sleep(1000); // wait for transition to full screen to finish
ensureVisible(frame, 250, 150);
ensureVisible(dialog, 250, 250);
SwingUtilities.invokeAndWait(() -> Application.getApplication().requestToggleFullScreen(frame));
Thread.sleep(1000); // wait for transition from full screen to finish
ensureVisible(frame, 250, 150);
ensureVisible(dialog, 250, 250);
} finally {
SwingUtilities.invokeAndWait(FullScreenChildWindowShownBefore::disposeUI);
}
}
private static void initUI() {
frame = new JFrame("FullScreenChildWindowShownBefore");
frame.setBounds(100, 100, 300, 300);
frame.setVisible(true);
dialog = new JDialog(frame, false);
dialog.setBounds(200, 200, 100, 100);
dialog.setVisible(true);
}
private static void disposeUI() {
if (frame != null) frame.dispose();
}
private static void ensureVisible(Window window, int x, int y) throws Exception {
if (!MacSpacesUtil.isWindowVisibleAtPoint(window, x, y)) throw new RuntimeException(window + " isn't visible");
}
}

View File

@@ -0,0 +1,100 @@
/*
* 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 com.apple.eawt.Application;
import com.apple.eawt.FullScreenAdapter;
import com.apple.eawt.FullScreenUtilities;
import com.apple.eawt.event.FullScreenEvent;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* @test
* @summary Regression test for JBR-3633 Modal dialog is shown not at the same space as its parent
* @key headful
* @requires (os.family == "mac")
* @modules java.desktop/com.apple.eawt
* java.desktop/com.apple.eawt.event
* @compile MacSpacesUtil.java
* @run main FullScreenInactiveDialog
*/
public class FullScreenInactiveDialog {
private static final CompletableFuture<Boolean> shownAtFullScreen = new CompletableFuture<>();
private static JFrame frame;
private static JDialog dialog;
public static void main(String[] args) throws Exception {
try {
SwingUtilities.invokeAndWait(FullScreenInactiveDialog::initUI);
shownAtFullScreen.get(5, TimeUnit.SECONDS);
MacSpacesUtil.switchToPreviousSpace();
SwingUtilities.invokeLater(FullScreenInactiveDialog::openDialog);
Thread.sleep(1000);
if (MacSpacesUtil.isWindowVisible(dialog)) {
throw new RuntimeException("Dialog is showing earlier than expected");
}
Desktop.getDesktop().requestForeground(false); // simulates clicking on app icon in dock or switch using Cmd+Tab
Thread.sleep(1000); // wait for animation to finish
if (!MacSpacesUtil.isWindowVisible(dialog)) {
throw new RuntimeException("Dialog isn't showing when expected");
}
if (!MacSpacesUtil.isWindowVisible(frame)) {
throw new RuntimeException("Frame isn't showing when expected");
}
} finally {
SwingUtilities.invokeAndWait(FullScreenInactiveDialog::disposeUI);
}
}
private static void initUI() {
frame = new JFrame("FullScreenInactiveDialog");
frame.setSize(300, 100);
frame.setVisible(true);
FullScreenUtilities.addFullScreenListenerTo(frame, new FullScreenAdapter() {
@Override
public void windowEnteredFullScreen(FullScreenEvent e) {
shownAtFullScreen.complete(true);
}
});
Application.getApplication().requestToggleFullScreen(frame);
}
private static void openDialog() {
dialog = new JDialog(frame);
dialog.setSize(100, 100);
dialog.setVisible(true);
}
private static void disposeUI() {
if (frame != null) frame.dispose();
}
}

View File

@@ -0,0 +1,96 @@
/*
* Copyright 2000-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 com.apple.eawt.Application;
import com.apple.eawt.FullScreenAdapter;
import com.apple.eawt.FullScreenUtilities;
import com.apple.eawt.event.FullScreenEvent;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* @test
* @summary Regression test for JBR-3633 Modal dialog is shown not at the same space as its parent
* @key headful
* @requires (os.family == "mac")
* @modules java.desktop/com.apple.eawt
* java.desktop/com.apple.eawt.event
* @compile MacSpacesUtil.java
* @run main FullScreenInactiveModalDialog
*/
public class FullScreenInactiveModalDialog {
private static final CompletableFuture<Boolean> shownAtFullScreen = new CompletableFuture<>();
private static JFrame frame;
private static JDialog dialog;
public static void main(String[] args) throws Exception {
try {
SwingUtilities.invokeAndWait(FullScreenInactiveModalDialog::initUI);
shownAtFullScreen.get(5, TimeUnit.SECONDS);
MacSpacesUtil.switchToPreviousSpace();
SwingUtilities.invokeLater(FullScreenInactiveModalDialog::openDialog);
Thread.sleep(1000);
if (MacSpacesUtil.isWindowVisible(dialog)) {
throw new RuntimeException("Dialog is showing earlier than expected");
}
MacSpacesUtil.switchToNextSpace();
if (!MacSpacesUtil.isWindowVisible(dialog)) {
throw new RuntimeException("Dialog isn't showing when expected");
}
} finally {
SwingUtilities.invokeAndWait(FullScreenInactiveModalDialog::disposeUI);
}
}
private static void initUI() {
frame = new JFrame("FullScreenInactiveModalDialog");
frame.setSize(300, 100);
frame.setVisible(true);
FullScreenUtilities.addFullScreenListenerTo(frame, new FullScreenAdapter() {
@Override
public void windowEnteredFullScreen(FullScreenEvent e) {
shownAtFullScreen.complete(true);
}
});
Application.getApplication().requestToggleFullScreen(frame);
}
private static void openDialog() {
dialog = new JDialog(frame, true);
dialog.setSize(100, 100);
dialog.setVisible(true);
}
private static void disposeUI() {
if (frame != null) frame.dispose();
}
}

View File

@@ -0,0 +1,170 @@
/*
* Copyright 2022 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.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
public class MacSpacesUtil {
public static boolean hasMoreThanOneSpace() throws Exception {
return Runtime.getRuntime().exec(new String[]{
"plutil",
"-extract",
"SpacesDisplayConfiguration.Management Data.Monitors.0.Spaces.1",
"json",
"-o",
"-",
System.getProperty("user.home") + "/Library/Preferences/com.apple.spaces.plist"
}).waitFor() == 0;
}
public static void addSpace() throws Exception {
toggleMissionControl();
// press button at top right corner to add a new space
Rectangle screenBounds = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice()
.getDefaultConfiguration().getBounds();
int rightX = screenBounds.x + screenBounds.width;
int topY = screenBounds.y;
Robot robot = new Robot();
robot.setAutoDelay(50);
robot.mouseMove(rightX, topY);
robot.mouseMove(rightX - 5, topY + 5);
robot.mouseMove(rightX - 10, topY + 10);
robot.delay(1000);
robot.mousePress(InputEvent.BUTTON1_DOWN_MASK);
robot.mouseRelease(InputEvent.BUTTON1_DOWN_MASK);
robot.delay(1000);
toggleMissionControl();
}
public static boolean isWindowVisibleAtPoint(Window window, int x, int y) throws Exception {
CountDownLatch movementDetected = new CountDownLatch(1);
MouseMotionListener listener = new MouseMotionAdapter() {
@Override
public void mouseMoved(MouseEvent e) {
movementDetected.countDown();
}
};
try {
SwingUtilities.invokeAndWait(() -> window.addMouseMotionListener(listener));
Robot robot = new Robot();
robot.mouseMove(x, y);
robot.delay(50);
robot.mouseMove(x + 1, y + 1);
return movementDetected.await(1, TimeUnit.SECONDS);
} finally {
SwingUtilities.invokeAndWait(() -> window.removeMouseMotionListener(listener));
}
}
public static boolean isWindowVisible(Window window) throws Exception {
Rectangle bounds = window.getBounds();
Insets insets = window.getInsets();
bounds.x += insets.left;
bounds.y += insets.top;
bounds.width -= insets.left + insets.right;
bounds.height -= insets.top + insets.bottom;
return isWindowVisibleAtPoint(window, bounds.x + bounds.width / 2, bounds.y + bounds.height / 2);
}
public static void verifyAdditionalSpaceExists() throws Exception {
AtomicReference<JFrame> frameRef = new AtomicReference<>();
try {
SwingUtilities.invokeAndWait(() -> {
JFrame f = new JFrame("Spaces check");
frameRef.set(f);
f.setBounds(100, 100, 200, 200);
f.setVisible(true);
Desktop.getDesktop().requestForeground(true);
});
if (!isWindowVisibleAtPoint(frameRef.get(), 200, 200)) {
throw new RuntimeException("Test frame not visible");
}
switchToNextSpace();
if (isWindowVisibleAtPoint(frameRef.get(), 200, 200)) {
throw new RuntimeException("Extra space isn't available");
}
} finally {
switchToPreviousSpace();
SwingUtilities.invokeAndWait(() -> {
JFrame f = frameRef.get();
if (f != null) f.dispose();
});
}
}
public static void toggleMissionControl() throws Exception {
Robot robot = new Robot();
robot.setAutoDelay(50);
robot.keyPress(KeyEvent.VK_CONTROL);
robot.keyPress(KeyEvent.VK_UP);
robot.keyRelease(KeyEvent.VK_UP);
robot.keyRelease(KeyEvent.VK_CONTROL);
robot.delay(1000); // wait for animation to finish
}
public static void switchToPreviousSpace() throws Exception {
Robot robot = new Robot();
robot.setAutoDelay(50);
robot.keyPress(KeyEvent.VK_CONTROL);
robot.keyPress(KeyEvent.VK_LEFT);
robot.keyRelease(KeyEvent.VK_LEFT);
robot.keyRelease(KeyEvent.VK_CONTROL);
robot.delay(1000); // wait for animation to finish
}
public static void switchToNextSpace() throws Exception {
Robot robot = new Robot();
robot.setAutoDelay(50);
robot.keyPress(KeyEvent.VK_CONTROL);
robot.keyPress(KeyEvent.VK_RIGHT);
robot.keyRelease(KeyEvent.VK_RIGHT);
robot.keyRelease(KeyEvent.VK_CONTROL);
robot.delay(1000); // wait for animation to finish
}
// press Control+Right while holding mouse pressed over window's header
public static void moveWindowToNextSpace(Window window) throws Exception {
Robot robot = new Robot();
robot.setAutoDelay(50);
Point location = window.getLocationOnScreen();
robot.mouseMove(location.x + window.getWidth() / 2, location.y + window.getInsets().top / 2);
robot.mousePress(InputEvent.BUTTON1_DOWN_MASK);
switchToNextSpace();
robot.mouseRelease(InputEvent.BUTTON1_DOWN_MASK);
}
public static void ensureMoreThanOneSpaceExists() throws Exception {
if (hasMoreThanOneSpace()) return;
addSpace();
verifyAdditionalSpaceExists();
}
}

View File

@@ -0,0 +1,81 @@
/*
* Copyright 2022 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.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* @test
* @summary Regression test for JBR-4186 Unexpected desktop switch after moving a window to another desktop
* @key headful
* @requires (os.family == "mac")
* @compile MacSpacesUtil.java
* @run main ParentMovingToAnotherSpace
*/
public class ParentMovingToAnotherSpace {
private static JFrame frame1;
private static JFrame frame2;
public static void main(String[] args) throws Exception {
MacSpacesUtil.ensureMoreThanOneSpaceExists();
try {
SwingUtilities.invokeAndWait(ParentMovingToAnotherSpace::initUI);
Thread.sleep(1000); // wait for windows to appear
MacSpacesUtil.moveWindowToNextSpace(frame2);
if (MacSpacesUtil.isWindowVisible(frame1)) {
throw new RuntimeException("Space switch didn't work");
}
if (!MacSpacesUtil.isWindowVisible(frame2)) {
throw new RuntimeException("Frame isn't showing when expected");
}
} finally {
MacSpacesUtil.switchToPreviousSpace();
SwingUtilities.invokeAndWait(ParentMovingToAnotherSpace::disposeUI);
}
}
private static void initUI() {
frame1 = new JFrame("ParentMovingToAnotherSpace-1");
frame1.setBounds(100, 100, 300, 100);
frame1.setVisible(true);
frame2 = new JFrame("ParentMovingToAnotherSpace-2");
frame2.setBounds(100, 300, 300, 100);
frame2.setVisible(true);
JWindow window = new JWindow(frame2);
window.setBounds(100, 500, 300, 100);
window.setVisible(true);
}
private static void disposeUI() {
if (frame1 != null) frame1.dispose();
if (frame2 != null) frame2.dispose();
}
}

View File

@@ -0,0 +1,110 @@
/*
* Copyright 2000-2022 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.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* @test
* @summary Specific case for JBR-4186 Unexpected desktop switch after moving a window to another desktop
* @key headful
* @requires (os.family == "mac")
* @compile MacSpacesUtil.java
* @run main TwoFramesAndDialogOnDifferentSpaces
*/
public class TwoFramesAndDialogOnDifferentSpaces {
private static Robot robot;
private static JFrame frame1;
private static JFrame frame2;
private static JDialog dialog;
public static void main(String[] args) throws Exception {
MacSpacesUtil.ensureMoreThanOneSpaceExists();
robot = new Robot();
try {
SwingUtilities.invokeAndWait(TwoFramesAndDialogOnDifferentSpaces::initUI);
robot.delay(1000);
ensureVisibility(true, true, true);
MacSpacesUtil.moveWindowToNextSpace(frame1);
ensureVisibility(true, false, false);
MacSpacesUtil.switchToPreviousSpace();
ensureVisibility(false, true, true);
clickOn(frame2);
robot.delay(1000);
MacSpacesUtil.switchToNextSpace();
ensureVisibility(true, false, false);
} finally {
MacSpacesUtil.switchToPreviousSpace();
SwingUtilities.invokeAndWait(TwoFramesAndDialogOnDifferentSpaces::disposeUI);
}
}
private static void initUI() {
frame1 = new JFrame("TFADODS-1");
frame1.setBounds(200, 200, 300, 100);
frame1.setVisible(true);
frame2 = new JFrame("TFADODS-2");
frame2.setBounds(200, 400, 300, 100);
frame2.setVisible(true);
dialog = new JDialog(frame2, "TFADODS");
dialog.setBounds(200, 600, 300, 100);
dialog.setVisible(true);
}
private static void disposeUI() {
if (frame1 != null) frame1.dispose();
if (frame2 != null) frame2.dispose();
}
private static void ensureVisibility(boolean frame1Visible, boolean frame2Visible, boolean dialogVisible)
throws Exception {
if (frame1Visible != MacSpacesUtil.isWindowVisible(frame1)) {
throw new RuntimeException("Frame 1 is " + (frame1Visible ? "not " : "") + " visible");
}
if (frame2Visible != MacSpacesUtil.isWindowVisible(frame2)) {
throw new RuntimeException("Frame 2 is " + (frame2Visible ? "not " : "") + " visible");
}
if (dialogVisible != MacSpacesUtil.isWindowVisible(dialog)) {
throw new RuntimeException("Dialog is " + (dialogVisible ? "not " : "") + " visible");
}
}
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);
}
}