6607170: Focus not set by requestFocus
Summary: fixing/refactoring focus auto-transfer mechanism.
Reviewed-by: son
--- a/jdk/src/share/classes/java/awt/Component.java Tue Apr 08 12:46:39 2008 +0400
+++ b/jdk/src/share/classes/java/awt/Component.java Tue Apr 08 13:32:30 2008 +0400
@@ -1327,12 +1327,15 @@
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 @@
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 @@
}
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 @@
firePropertyChange("focusable", oldFocusable, focusable);
if (oldFocusable && !focusable) {
- if (isFocusOwner()) {
- autoTransferFocus(true);
+ if (isFocusOwner() && KeyboardFocusManager.isAutoFocusTransferEnabled()) {
+ transferFocus(true);
}
KeyboardFocusManager.clearMostRecentFocusOwner(this);
}
@@ -7373,69 +7372,6 @@
}
}
- 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 @@
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();
- }
-
- private boolean nextFocusHelper() {
- Component toFocus = preNextFocusHelper();
+ transferFocus(false);
+ }
+
+ boolean transferFocus(boolean clearOnFailure) {
if (focusLog.isLoggable(Level.FINER)) {
- focusLog.log(Level.FINER, "toFocus = " + toFocus);
- }
- if (isFocusOwner() && toFocus == this) {
- return false;
- }
- return postNextFocusHelper(toFocus, CausedFocusEvent.Cause.TRAVERSAL_FORWARD);
- }
-
- Container getTraversalRoot() {
- return getFocusCycleRootAncestor();
- }
-
- final Component preNextFocusHelper() {
+ focusLog.finer("clearOnFailure = " + clearOnFailure);
+ }
+ Component toFocus = getNextFocusCandidate();
+ boolean res = false;
+ if (toFocus != null && !toFocus.isFocusOwner() && toFocus != this) {
+ res = toFocus.requestFocusInWindow(CausedFocusEvent.Cause.TRAVERSAL_FORWARD);
+ }
+ 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 getNextFocusCandidate() {
Container rootAncestor = getTraversalRoot();
Component comp = this;
while (rootAncestor != null &&
@@ -7511,18 +7467,19 @@
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 @@
toFocus = applet;
}
}
- return 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;
- }
- return false;
+ candidate = toFocus;
+ }
+ if (focusLog.isLoggable(Level.FINER)) {
+ focusLog.finer("Focus transfer candidate: " + candidate);
+ }
+ return candidate;
}
/**
@@ -7557,6 +7503,10 @@
* @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 @@
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 @@
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 @@
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.
--- a/jdk/src/share/classes/java/awt/Container.java Tue Apr 08 12:46:39 2008 +0400
+++ b/jdk/src/share/classes/java/awt/Container.java Tue Apr 08 13:32:30 2008 +0400
@@ -2660,9 +2660,26 @@
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();
--- a/jdk/src/share/classes/java/awt/DefaultKeyboardFocusManager.java Tue Apr 08 12:46:39 2008 +0400
+++ b/jdk/src/share/classes/java/awt/DefaultKeyboardFocusManager.java Tue Apr 08 13:32:30 2008 +0400
@@ -155,12 +155,13 @@
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 @@
{
// 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;
}
--- a/jdk/src/share/classes/java/awt/KeyboardFocusManager.java Tue Apr 08 12:46:39 2008 +0400
+++ b/jdk/src/share/classes/java/awt/KeyboardFocusManager.java Tue Apr 08 13:32:30 2008 +0400
@@ -2578,6 +2578,10 @@
}
}
+ 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
--- a/jdk/src/solaris/classes/sun/awt/X11/XKeyboardFocusManagerPeer.java Tue Apr 08 12:46:39 2008 +0400
+++ b/jdk/src/solaris/classes/sun/awt/X11/XKeyboardFocusManagerPeer.java Tue Apr 08 13:32:30 2008 +0400
@@ -96,12 +96,12 @@
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);
- }
+// }
}
}
}
--- a/jdk/src/windows/native/sun/windows/awt_Component.cpp Tue Apr 08 12:46:39 2008 +0400
+++ b/jdk/src/windows/native/sun/windows/awt_Component.cpp Tue Apr 08 13:32:30 2008 +0400
@@ -903,8 +903,27 @@
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
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/awt/Focus/ContainerFocusAutoTransferTest/ContainerFocusAutoTransferTest.java Tue Apr 08 13:32:30 2008 +0400
@@ -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);
+ }
+}