diff --git a/src/share/classes/java/awt/Component.java b/src/share/classes/java/awt/Component.java index 1bbd4dcf45b9fbe4270c5645718700b9a6c5bf31..22fb95f5aa93eadcf65edca70311112628c4e85e 100644 --- a/src/share/classes/java/awt/Component.java +++ b/src/share/classes/java/awt/Component.java @@ -1327,12 +1327,15 @@ public abstract class Component implements ImageObserver, MenuContainer, KeyboardFocusManager.clearMostRecentFocusOwner(this); synchronized (getTreeLock()) { enabled = false; - if (isFocusOwner()) { + // A disabled lw container is allowed to contain a focus owner. + if ((isFocusOwner() || (containsFocus() && !isLightweight())) && + KeyboardFocusManager.isAutoFocusTransferEnabled()) + { // Don't clear the global focus owner. If transferFocus // fails, we want the focus to stay on the disabled // Component so that keyboard traversal, et. al. still // makes sense to the user. - autoTransferFocus(false); + transferFocus(false); } ComponentPeer peer = this.peer; if (peer != null) { @@ -1493,8 +1496,8 @@ public abstract class Component implements ImageObserver, MenuContainer, synchronized (getTreeLock()) { visible = false; mixOnHiding(isLightweight()); - if (containsFocus()) { - autoTransferFocus(true); + if (containsFocus() && KeyboardFocusManager.isAutoFocusTransferEnabled()) { + transferFocus(true); } ComponentPeer peer = this.peer; if (peer != null) { @@ -6578,12 +6581,8 @@ public abstract class Component implements ImageObserver, MenuContainer, } synchronized (getTreeLock()) { - if (isFocusOwner() - && KeyboardFocusManager.isAutoFocusTransferEnabled() - && !nextFocusHelper()) - { - KeyboardFocusManager.getCurrentKeyboardFocusManager(). - clearGlobalFocusOwner(); + if (isFocusOwner() && KeyboardFocusManager.isAutoFocusTransferEnabledFor(this)) { + transferFocus(true); } if (getContainer() != null && isAddNotifyComplete) { @@ -6718,8 +6717,8 @@ public abstract class Component implements ImageObserver, MenuContainer, firePropertyChange("focusable", oldFocusable, focusable); if (oldFocusable && !focusable) { - if (isFocusOwner()) { - autoTransferFocus(true); + if (isFocusOwner() && KeyboardFocusManager.isAutoFocusTransferEnabled()) { + transferFocus(true); } KeyboardFocusManager.clearMostRecentFocusOwner(this); } @@ -7373,69 +7372,6 @@ public abstract class Component implements ImageObserver, MenuContainer, } } - private void autoTransferFocus(boolean clearOnFailure) { - Component toTest = KeyboardFocusManager. - getCurrentKeyboardFocusManager().getFocusOwner(); - if (toTest != this) { - if (toTest != null) { - toTest.autoTransferFocus(clearOnFailure); - } - return; - } - - // Check if there are pending focus requests. We shouldn't do - // auto-transfer if user has already took care of this - // component becoming ineligible to hold focus. - if (!KeyboardFocusManager.isAutoFocusTransferEnabled()) { - return; - } - - // the following code will execute only if this Component is the focus - // owner - - if (!(isDisplayable() && isVisible() && isEnabled() && isFocusable())) { - doAutoTransfer(clearOnFailure); - return; - } - - toTest = getParent(); - - while (toTest != null && !(toTest instanceof Window)) { - if (!(toTest.isDisplayable() && toTest.isVisible() && - (toTest.isEnabled() || toTest.isLightweight()))) { - doAutoTransfer(clearOnFailure); - return; - } - toTest = toTest.getParent(); - } - } - private void doAutoTransfer(boolean clearOnFailure) { - if (focusLog.isLoggable(Level.FINER)) { - focusLog.log(Level.FINER, "this = " + this + ", clearOnFailure = " + clearOnFailure); - } - if (clearOnFailure) { - if (!nextFocusHelper()) { - if (focusLog.isLoggable(Level.FINER)) { - focusLog.log(Level.FINER, "clear global focus owner"); - } - KeyboardFocusManager.getCurrentKeyboardFocusManager(). - clearGlobalFocusOwner(); - } - } else { - transferFocus(); - } - } - - /** - * Transfers the focus to the next component, as though this Component were - * the focus owner. - * @see #requestFocus() - * @since JDK1.1 - */ - public void transferFocus() { - nextFocus(); - } - /** * Returns the Container which is the focus cycle root of this Component's * focus traversal cycle. Each focus traversal cycle has only a single @@ -7475,31 +7411,51 @@ public abstract class Component implements ImageObserver, MenuContainer, return (rootAncestor == container); } + Container getTraversalRoot() { + return getFocusCycleRootAncestor(); + } + + /** + * Transfers the focus to the next component, as though this Component were + * the focus owner. + * @see #requestFocus() + * @since JDK1.1 + */ + public void transferFocus() { + nextFocus(); + } + /** * @deprecated As of JDK version 1.1, * replaced by transferFocus(). */ @Deprecated public void nextFocus() { - nextFocusHelper(); + transferFocus(false); } - private boolean nextFocusHelper() { - Component toFocus = preNextFocusHelper(); + boolean transferFocus(boolean clearOnFailure) { if (focusLog.isLoggable(Level.FINER)) { - focusLog.log(Level.FINER, "toFocus = " + toFocus); + focusLog.finer("clearOnFailure = " + clearOnFailure); } - if (isFocusOwner() && toFocus == this) { - return false; + Component toFocus = getNextFocusCandidate(); + boolean res = false; + if (toFocus != null && !toFocus.isFocusOwner() && toFocus != this) { + res = toFocus.requestFocusInWindow(CausedFocusEvent.Cause.TRAVERSAL_FORWARD); } - return postNextFocusHelper(toFocus, CausedFocusEvent.Cause.TRAVERSAL_FORWARD); - } - - Container getTraversalRoot() { - return getFocusCycleRootAncestor(); + if (clearOnFailure && !res) { + if (focusLog.isLoggable(Level.FINER)) { + focusLog.finer("clear global focus owner"); + } + KeyboardFocusManager.getCurrentKeyboardFocusManager().clearGlobalFocusOwner(); + } + if (focusLog.isLoggable(Level.FINER)) { + focusLog.finer("returning result: " + res); + } + return res; } - final Component preNextFocusHelper() { + final Component getNextFocusCandidate() { Container rootAncestor = getTraversalRoot(); Component comp = this; while (rootAncestor != null && @@ -7511,18 +7467,19 @@ public abstract class Component implements ImageObserver, MenuContainer, rootAncestor = comp.getFocusCycleRootAncestor(); } if (focusLog.isLoggable(Level.FINER)) { - focusLog.log(Level.FINER, "comp = " + comp + ", root = " + rootAncestor); + focusLog.finer("comp = " + comp + ", root = " + rootAncestor); } + Component candidate = null; if (rootAncestor != null) { FocusTraversalPolicy policy = rootAncestor.getFocusTraversalPolicy(); Component toFocus = policy.getComponentAfter(rootAncestor, comp); if (focusLog.isLoggable(Level.FINER)) { - focusLog.log(Level.FINER, "component after is " + toFocus); + focusLog.finer("component after is " + toFocus); } if (toFocus == null) { toFocus = policy.getDefaultComponent(rootAncestor); if (focusLog.isLoggable(Level.FINER)) { - focusLog.log(Level.FINER, "default component is " + toFocus); + focusLog.finer("default component is " + toFocus); } } if (toFocus == null) { @@ -7531,23 +7488,12 @@ public abstract class Component implements ImageObserver, MenuContainer, toFocus = applet; } } - return toFocus; + candidate = toFocus; } - return null; - } - - static boolean postNextFocusHelper(Component toFocus, CausedFocusEvent.Cause cause) { - if (toFocus != null) { - if (focusLog.isLoggable(Level.FINER)) { - focusLog.log(Level.FINER, "Next component " + toFocus); - } - boolean res = toFocus.requestFocusInWindow(cause); - if (focusLog.isLoggable(Level.FINER)) { - focusLog.log(Level.FINER, "Request focus returned " + res); - } - return res; + if (focusLog.isLoggable(Level.FINER)) { + focusLog.finer("Focus transfer candidate: " + candidate); } - return false; + return candidate; } /** @@ -7557,6 +7503,10 @@ public abstract class Component implements ImageObserver, MenuContainer, * @since 1.4 */ public void transferFocusBackward() { + transferFocusBackward(false); + } + + boolean transferFocusBackward(boolean clearOnFailure) { Container rootAncestor = getTraversalRoot(); Component comp = this; while (rootAncestor != null && @@ -7567,6 +7517,7 @@ public abstract class Component implements ImageObserver, MenuContainer, comp = rootAncestor; rootAncestor = comp.getFocusCycleRootAncestor(); } + boolean res = false; if (rootAncestor != null) { FocusTraversalPolicy policy = rootAncestor.getFocusTraversalPolicy(); Component toFocus = policy.getComponentBefore(rootAncestor, comp); @@ -7574,9 +7525,19 @@ public abstract class Component implements ImageObserver, MenuContainer, toFocus = policy.getDefaultComponent(rootAncestor); } if (toFocus != null) { - toFocus.requestFocusInWindow(CausedFocusEvent.Cause.TRAVERSAL_BACKWARD); + res = toFocus.requestFocusInWindow(CausedFocusEvent.Cause.TRAVERSAL_BACKWARD); } } + if (!res) { + if (focusLog.isLoggable(Level.FINER)) { + focusLog.finer("clear global focus owner"); + } + KeyboardFocusManager.getCurrentKeyboardFocusManager().clearGlobalFocusOwner(); + } + if (focusLog.isLoggable(Level.FINER)) { + focusLog.finer("returning result: " + res); + } + return res; } /** @@ -7651,6 +7612,20 @@ public abstract class Component implements ImageObserver, MenuContainer, return hasFocus(); } + /* + * Used to disallow auto-focus-transfer on disposal of the focus owner + * in the process of disposing its parent container. + */ + private boolean autoFocusTransferOnDisposal = true; + + void setAutoFocusTransferOnDisposal(boolean value) { + autoFocusTransferOnDisposal = value; + } + + boolean isAutoFocusTransferOnDisposal() { + return autoFocusTransferOnDisposal; + } + /** * Adds the specified popup menu to the component. * @param popup the popup menu to be added to the component. diff --git a/src/share/classes/java/awt/Container.java b/src/share/classes/java/awt/Container.java index 7eba36a11688fbb69da69205a3cead333813e0e8..d79bde285f397e6e51b7c575147601a4314aa2b9 100644 --- a/src/share/classes/java/awt/Container.java +++ b/src/share/classes/java/awt/Container.java @@ -2660,9 +2660,26 @@ public class Container extends Component { synchronized (getTreeLock()) { int ncomponents = this.ncomponents; Component component[] = this.component; - for (int i = ncomponents-1 ; i >= 0 ; i--) { - if( component[i] != null ) - component[i].removeNotify(); + for (int i = ncomponents - 1; i >= 0; i--) { + if( component[i] != null ) { + // Fix for 6607170. + // We want to suppress focus change on disposal + // of the focused component. But because of focus + // is asynchronous, we should suppress focus change + // on every component in case it receives native focus + // in the process of disposal. + component[i].setAutoFocusTransferOnDisposal(false); + component[i].removeNotify(); + component[i].setAutoFocusTransferOnDisposal(true); + } + } + // If some of the children had focus before disposal then it still has. + // Auto-transfer focus to the next (or previous) component if auto-transfer + // is enabled. + if (containsFocus() && KeyboardFocusManager.isAutoFocusTransferEnabledFor(this)) { + if (!transferFocus(false)) { + transferFocusBackward(true); + } } if ( dispatcher != null ) { dispatcher.dispose(); diff --git a/src/share/classes/java/awt/DefaultKeyboardFocusManager.java b/src/share/classes/java/awt/DefaultKeyboardFocusManager.java index 9e2c6e5620d6dddf6f1614da373aa04c72bab2b1..928379ec286a1f388f88cff6ffc84d3f46e38838 100644 --- a/src/share/classes/java/awt/DefaultKeyboardFocusManager.java +++ b/src/share/classes/java/awt/DefaultKeyboardFocusManager.java @@ -155,12 +155,13 @@ public class DefaultKeyboardFocusManager extends KeyboardFocusManager { boolean clearOnFailure) { if (toFocus != vetoedComponent && toFocus.isShowing() && toFocus.isFocusable() && - toFocus.requestFocus(false, CausedFocusEvent.Cause.ROLLBACK)) { + toFocus.requestFocus(false, CausedFocusEvent.Cause.ROLLBACK)) + { return true; } else { - Component nextFocus = toFocus.preNextFocusHelper(); - if (nextFocus != vetoedComponent - && Component.postNextFocusHelper(nextFocus, CausedFocusEvent.Cause.ROLLBACK)) + Component nextFocus = toFocus.getNextFocusCandidate(); + if (nextFocus != null && nextFocus != vetoedComponent && + nextFocus.requestFocusInWindow(CausedFocusEvent.Cause.ROLLBACK)) { return true; } else if (clearOnFailure) { @@ -504,9 +505,16 @@ public class DefaultKeyboardFocusManager extends KeyboardFocusManager { { // we should not accept focus on such component, so reject it. dequeueKeyEvents(-1, newFocusOwner); - if (KeyboardFocusManager.isAutoFocusTransferEnabled()) - { - restoreFocus(fe, newFocusedWindow); + if (KeyboardFocusManager.isAutoFocusTransferEnabled()) { + // If FOCUS_GAINED is for a disposed component (however + // it shouldn't happen) its toplevel parent is null. In this + // case we have to try to restore focus in the current focused + // window (for the details: 6607170). + if (newFocusedWindow == null) { + restoreFocus(fe, currentFocusedWindow); + } else { + restoreFocus(fe, newFocusedWindow); + } } break; } diff --git a/src/share/classes/java/awt/KeyboardFocusManager.java b/src/share/classes/java/awt/KeyboardFocusManager.java index ae14e8311e826129c88ba5ec627a2311b3715b4e..d4a9e34f57db51e6e8c97b7c22cf7c9010cf7a07 100644 --- a/src/share/classes/java/awt/KeyboardFocusManager.java +++ b/src/share/classes/java/awt/KeyboardFocusManager.java @@ -2578,6 +2578,10 @@ public abstract class KeyboardFocusManager } } + static boolean isAutoFocusTransferEnabledFor(Component comp) { + return isAutoFocusTransferEnabled() && comp.isAutoFocusTransferOnDisposal(); + } + /* * Used to process exceptions in dispatching focus event (in focusLost/focusGained callbacks). * @param ex previously caught exception that may be processed right here, or null diff --git a/src/solaris/classes/sun/awt/X11/XKeyboardFocusManagerPeer.java b/src/solaris/classes/sun/awt/X11/XKeyboardFocusManagerPeer.java index ef5f81ce1cf19d2d1699380f324c9646660b4794..a0be3b5ad1d1d0c363d8d4168309de4014c2b535 100644 --- a/src/solaris/classes/sun/awt/X11/XKeyboardFocusManagerPeer.java +++ b/src/solaris/classes/sun/awt/X11/XKeyboardFocusManagerPeer.java @@ -96,12 +96,12 @@ public class XKeyboardFocusManagerPeer implements KeyboardFocusManagerPeer { Component focusOwner = activeWindow.getFocusOwner(); if (focusLog.isLoggable(Level.FINE)) focusLog.fine("Clearing global focus owner " + focusOwner); if (focusOwner != null) { - XComponentPeer nativePeer = XComponentPeer.getNativeContainer(focusOwner); - if (nativePeer != null) { +// XComponentPeer nativePeer = XComponentPeer.getNativeContainer(focusOwner); +// if (nativePeer != null) { FocusEvent fl = new CausedFocusEvent(focusOwner, FocusEvent.FOCUS_LOST, false, null, CausedFocusEvent.Cause.CLEAR_GLOBAL_FOCUS_OWNER); XWindow.sendEvent(fl); - } +// } } } } diff --git a/src/windows/native/sun/windows/awt_Component.cpp b/src/windows/native/sun/windows/awt_Component.cpp index f2f59b859ccf196e0642b5dba43fb0255b2c01fd..5dc8ff6fe9da4fbe6042e49d6c3ebcecebda6168 100644 --- a/src/windows/native/sun/windows/awt_Component.cpp +++ b/src/windows/native/sun/windows/awt_Component.cpp @@ -903,8 +903,27 @@ void AwtComponent::Show() void AwtComponent::Hide() { + JNIEnv *env = (JNIEnv *)JNU_GetEnv(jvm, JNI_VERSION_1_2); + jobject peer = GetPeer(env); + BOOL oldValue = sm_suppressFocusAndActivation; m_visible = false; + + // On disposal the focus owner actually loses focus at the moment of hiding. + // So, focus change suppression (if requested) should be made here. + if (GetHWnd() == sm_focusOwner && + !JNU_CallMethodByName(env, NULL, peer, "isAutoFocusTransferOnDisposal", "()Z").z) + { + sm_suppressFocusAndActivation = TRUE; + // The native system may autotransfer focus on hiding to the parent + // of the component. Nevertheless this focus change won't be posted + // to the Java level, we're better to avoid this. Anyway, after + // the disposal focus should be requested to the right component. + ::SetFocus(NULL); + sm_focusOwner = NULL; + } ::ShowWindow(GetHWnd(), SW_HIDE); + + sm_suppressFocusAndActivation = oldValue; } BOOL diff --git a/test/java/awt/Focus/ContainerFocusAutoTransferTest/ContainerFocusAutoTransferTest.java b/test/java/awt/Focus/ContainerFocusAutoTransferTest/ContainerFocusAutoTransferTest.java new file mode 100644 index 0000000000000000000000000000000000000000..7671bace2a527ff2d43de813b1c85c6b9453e2f1 --- /dev/null +++ b/test/java/awt/Focus/ContainerFocusAutoTransferTest/ContainerFocusAutoTransferTest.java @@ -0,0 +1,243 @@ +/* + * Copyright 2008 Sun Microsystems, Inc. All Rights Reserved. + * 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +/* + @test + @bug 6607170 + @summary Tests for focus-auto-transfer. + @author Anton Tarasov: area=awt-focus + @library ../../regtesthelpers + @build Util + @run main ContainerFocusAutoTransferTest +*/ + +import java.applet.Applet; +import java.awt.AWTEvent; +import java.awt.Component; +import java.awt.ComponentOrientation; +import java.awt.DefaultKeyboardFocusManager; +import java.awt.KeyboardFocusManager; +import java.awt.Robot; +import java.awt.Color; +import java.awt.FlowLayout; +import java.awt.Toolkit; +import java.awt.event.AWTEventListener; +import java.awt.event.FocusEvent; +import java.awt.event.WindowEvent; +import javax.swing.JButton; +import javax.swing.JFrame; +import javax.swing.JPanel; +import test.java.awt.regtesthelpers.Util; + +public class ContainerFocusAutoTransferTest extends Applet { + Robot robot; + TestFrame frame; + KeyboardFocusManager kfm; + enum TestCase { + REMOVAL { public String toString() { return "removal"; } }, + HIDING { public String toString() { return "hiding"; } }, + DISABLING { public String toString() { return "disabling"; } }, + DEFOCUSING { public String toString() { return "defocusing"; } }; + public abstract String toString(); + }; + + public static void main(String[] args) { + ContainerFocusAutoTransferTest app = new ContainerFocusAutoTransferTest(); + app.init(); + app.start(); + } + + public void init() { + robot = Util.createRobot(); + kfm = KeyboardFocusManager.getCurrentKeyboardFocusManager(); + Toolkit.getDefaultToolkit().addAWTEventListener(new AWTEventListener() { + public void eventDispatched(AWTEvent event) { + System.out.println("--> " + event); + } + }, FocusEvent.FOCUS_EVENT_MASK | WindowEvent.WINDOW_FOCUS_EVENT_MASK); + } + + public void start() { + System.out.println("*** TEST #1 ***"); + test(TestCase.HIDING); + + System.out.println("*** TEST #2 ***"); + test(TestCase.REMOVAL); + + System.out.println("*** TEST #3 ***"); + test3(TestCase.DISABLING); + + System.out.println("*** TEST #4 ***"); + test3(TestCase.DEFOCUSING); + + System.out.println("*** TEST #5 ***"); + test4(); + + System.out.println("Test passed."); + } + + void test(final TestCase t) { + showFrame(); + test1(t); // Test for correct auto-transfer + test2(t); // Test for clearing focus + } + + void test1(final TestCase t) { + Runnable action = new Runnable() { + public void run() { + KeyboardFocusManager.setCurrentKeyboardFocusManager(new TestKFM()); + if (t == TestCase.REMOVAL) { + frame.remove(frame.panel0); + + } else if (t == TestCase.HIDING) { + frame.panel0.setVisible(false); + } + frame.repaint(); + } + }; + if (!Util.trackFocusGained(frame.b3, action, 2000, false)) { + throw new TestFailedException(t + ": focus wasn't transfered as expected!"); + } + KeyboardFocusManager.setCurrentKeyboardFocusManager(kfm); + } + + void test2(TestCase t) { + frame.setFocusable(false); // exclude it from the focus cycle + if (t == TestCase.REMOVAL) { + frame.remove(frame.panel1); + + } else if (t == TestCase.HIDING) { + frame.panel1.setVisible(false); + } + frame.repaint(); + Util.waitForIdle(robot); + if (kfm.getFocusOwner() != null) { + throw new TestFailedException(t + ": focus wasn't cleared!"); + } + } + + void test3(final TestCase t) { + showFrame(); + Runnable action = new Runnable() { + public void run() { + if (t == TestCase.DISABLING) { + frame.b0.setEnabled(false); + + } else if (t == TestCase.DEFOCUSING) { + frame.b0.setFocusable(false); + } + }}; + if (!Util.trackFocusGained(frame.b1, action, 2000, false)) { + throw new TestFailedException(t + ": focus wasn't transfered as expected!"); + } + } + + void test4() { + showFrame(); + frame.setFocusableWindowState(false); + Util.waitForIdle(robot); + if (kfm.getFocusOwner() != null) { + throw new TestFailedException("defocusing the frame: focus wasn't cleared!"); + } + } + + void showFrame() { + if (frame != null) { + frame.dispose(); + Util.waitForIdle(robot); + } + frame = new TestFrame(); + frame.setVisible(true); + Util.waitTillShown(frame); + + if (!frame.b0.hasFocus()) { + Util.clickOnComp(frame.b0, robot); + Util.waitForIdle(robot); + if (!frame.b0.hasFocus()) { + throw new TestErrorException("couldn't set focus on " + frame.b2); + } + } + } + + class TestKFM extends DefaultKeyboardFocusManager { + public boolean dispatchEvent(AWTEvent e) { + if (e.getID() == FocusEvent.FOCUS_GAINED) { + System.out.println(e); + Component src = (Component)e.getSource(); + if (src == frame.b1 || src == frame.b2) { + throw new TestFailedException("wrong focus transfer on removal!"); + } + } + return super.dispatchEvent(e); + } + } +} + +class TestFrame extends JFrame { + public JPanel panel0 = new JPanel(); + public JPanel panel1 = new JPanel(); + public JButton b0 = new JButton("b0"); + public JButton b1 = new JButton("b1"); + public JButton b2 = new JButton("b2"); + public JButton b3 = new JButton("b3"); + public JButton b4 = new JButton("b4"); + + public TestFrame() { + super("TestFrame"); + + // The change of the orientation and the reverse order of + // adding the buttons to the panel is because in Container.removeNotify() + // the child components are removed in the reverse order. + // We want that the focus owner (b0) would be removed first and + // that the next traversable component would be b1. + panel0.setComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT); + panel0.add(b2); + panel0.add(b1); + panel0.add(b0); + + panel1.add(b3); + panel1.add(b4); + + setLayout(new FlowLayout()); + add(panel0); + add(panel1); + pack(); + + panel0.setBackground(Color.red); + panel1.setBackground(Color.blue); + } +} + +// Thrown when the behavior being verified is found wrong. +class TestFailedException extends RuntimeException { + TestFailedException(String msg) { + super("Test failed: " + msg); + } +} + +// Thrown when an error not related to the behavior being verified is encountered. +class TestErrorException extends RuntimeException { + TestErrorException(String msg) { + super("Unexpected error: " + msg); + } +}