diff --git a/test/jdk/java/awt/regtesthelpers/PassFailJFrame.java b/test/jdk/java/awt/regtesthelpers/PassFailJFrame.java
index 4d8f3c6659c..d2e650b2134 100644
--- a/test/jdk/java/awt/regtesthelpers/PassFailJFrame.java
+++ b/test/jdk/java/awt/regtesthelpers/PassFailJFrame.java
@@ -29,19 +29,23 @@ import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Image;
import java.awt.Insets;
+import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
+import java.awt.event.WindowListener;
import java.awt.image.RenderedImage;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
import java.util.List;
+import java.util.Objects;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
@@ -63,9 +67,83 @@ import javax.swing.text.JTextComponent;
import javax.swing.text.html.HTMLEditorKit;
import javax.swing.text.html.StyleSheet;
+import static java.util.Collections.unmodifiableList;
import static javax.swing.SwingUtilities.invokeAndWait;
import static javax.swing.SwingUtilities.isEventDispatchThread;
+/**
+ * Provides a framework for manual tests to display test instructions and
+ * Pass/Fail buttons.
+ *
+ * Instructions for the user can be either plain text or HTML as supported
+ * by Swing. If the instructions start with {@code }, the
+ * instructions are displayed as HTML.
+ *
+ * A simple test would look like this:
+ *
{@code
+ * public class SampleManualTest {
+ * private static final String INSTRUCTIONS =
+ * "Click Pass, or click Fail if the test failed.";
+ *
+ * public static void main(String[] args) throws Exception {
+ * PassFailJFrame.builder()
+ * .instructions(INSTRUCTIONS)
+ * .testUI(() -> createTestUI())
+ * .build()
+ * .awaitAndCheck();
+ * }
+ *
+ * private static List createTestUI() {
+ * JFrame testUI = new JFrame("Test UI");
+ * testUI.setSize(250, 150);
+ * return List.of(testUI);
+ * }
+ * }
+ * }
+ *
+ * The above example uses the {@link Builder Builder} to set the parameters of
+ * the instruction frame. It is the recommended way.
+ *
+ * The framework will create instruction UI, it will call
+ * the provided {@code createTestUI} on the Event Dispatch Thread (EDT),
+ * and it will automatically position the test UI and make it visible.
+ *
+ * Alternatively, use one of the {@code PassFailJFrame} constructors to
+ * create an object, then create secondary test UI, register it
+ * with {@code PassFailJFrame}, position it and make it visible.
+ * The following sample demonstrates it:
+ *
{@code
+ * public class SampleOldManualTest {
+ * private static final String INSTRUCTIONS =
+ * "Click Pass, or click Fail if the test failed.";
+ *
+ * public static void main(String[] args) throws Exception {
+ * PassFailJFrame passFail = new PassFailJFrame(INSTRUCTIONS);
+ *
+ * SwingUtilities.invokeAndWait(() -> createTestUI());
+ *
+ * passFail.awaitAndCheck();
+ * }
+ *
+ * private static void createTestUI() {
+ * JFrame testUI = new JFrame("Test UI");
+ * testUI.setSize(250, 150);
+ * PassFailJFrame.addTestWindow(testUI);
+ * PassFailJFrame.positionTestWindow(testUI, PassFailJFrame.Position.HORIZONTAL);
+ * testUI.setVisible(true);
+ * }
+ * }
+ * }
+ *
+ * Use methods of the {@code Builder} class or constructors of the
+ * {@code PassFailJFrame} class to control other parameters:
+ *
+ * - the title of the instruction UI,
+ * - the timeout of the test,
+ * - the size of the instruction UI via rows and columns, and
+ * - to enable screenshots.
+ *
+ */
public class PassFailJFrame {
private static final String TITLE = "Test Instruction Frame";
@@ -172,21 +250,67 @@ public class PassFailJFrame {
*/
public PassFailJFrame(String title, String instructions, long testTimeOut,
int rows, int columns,
- boolean enableScreenCapture) throws InterruptedException,
- InvocationTargetException {
- if (isEventDispatchThread()) {
- createUI(title, instructions, testTimeOut, rows, columns,
- enableScreenCapture);
- } else {
- invokeAndWait(() -> createUI(title, instructions, testTimeOut,
- rows, columns, enableScreenCapture));
- }
+ boolean enableScreenCapture)
+ throws InterruptedException, InvocationTargetException {
+ invokeOnEDT(() -> createUI(title, instructions,
+ testTimeOut,
+ rows, columns,
+ enableScreenCapture));
}
private PassFailJFrame(Builder builder) throws InterruptedException,
InvocationTargetException {
this(builder.title, builder.instructions, builder.testTimeOut,
- builder.rows, builder.columns, builder.screenCapture);
+ builder.rows, builder.columns, builder.screenCapture);
+
+ if (builder.windowCreator != null) {
+ invokeOnEDT(() ->
+ builder.testWindows = builder.windowCreator.createTestUI());
+ }
+
+ if (builder.testWindows != null) {
+ addTestWindow(builder.testWindows);
+ builder.testWindows
+ .forEach(w -> w.addWindowListener(windowClosingHandler));
+
+ if (builder.positionWindows != null) {
+ positionInstructionFrame(builder.position);
+ invokeOnEDT(() -> {
+ builder.positionWindows
+ .positionTestWindows(unmodifiableList(builder.testWindows),
+ builder.instructionUIHandler);
+
+ windowList.forEach(w -> w.setVisible(true));
+ });
+ } else if (builder.testWindows.size() == 1) {
+ Window window = builder.testWindows.get(0);
+ positionTestWindow(window, builder.position);
+ window.setVisible(true);
+ } else {
+ positionTestWindow(null, builder.position);
+ }
+ }
+ }
+
+ /**
+ * Performs an operation on EDT. If called on EDT, invokes {@code run}
+ * directly, otherwise wraps into {@code invokeAndWait}.
+ *
+ * @param doRun an operation to run on EDT
+ * @throws InterruptedException if we're interrupted while waiting for
+ * the event dispatching thread to finish executing
+ * {@code doRun.run()}
+ * @throws InvocationTargetException if an exception is thrown while
+ * running {@code doRun}
+ * @see javax.swing.SwingUtilities#invokeAndWait(Runnable)
+ */
+ private static void invokeOnEDT(Runnable doRun)
+ throws InterruptedException, InvocationTargetException {
+ if (isEventDispatchThread()) {
+ doRun.run();
+ } else {
+ invokeAndWait(doRun);
+ }
}
private static void createUI(String title, String instructions,
@@ -241,16 +365,7 @@ public class PassFailJFrame {
buttonsPanel.add(createCapturePanel());
}
- frame.addWindowListener(new WindowAdapter() {
- @Override
- public void windowClosing(WindowEvent e) {
- super.windowClosing(e);
- testFailedReason = FAILURE_REASON
- + "User closed the instruction Frame";
- failed = true;
- latch.countDown();
- }
- });
+ frame.addWindowListener(windowClosingHandler);
frame.add(buttonsPanel, BorderLayout.SOUTH);
frame.pack();
@@ -284,6 +399,101 @@ public class PassFailJFrame {
return text;
}
+
+ /**
+ * Creates one or more windows for test UI.
+ */
+ @FunctionalInterface
+ public interface WindowCreator {
+ /**
+ * Creates one or more windows for test UI.
+ * This method is called by the framework on the EDT.
+ * @return a list of windows.
+ */
+ List extends Window> createTestUI();
+ }
+
+ /**
+ * Positions test UI windows.
+ */
+ @FunctionalInterface
+ public interface PositionWindows {
+ /**
+ * Positions test UI windows.
+ * This method is called by the framework on the EDT after
+ * the instruction UI frame was positioned on the screen.
+ *
+ * The list of the test windows contains the windows
+ * that were passed to the framework via
+ * {@link Builder#testUI(WindowCreator) testUI} method.
+ *
+ * @param testWindows the list of test windows
+ * @param instructionUI information about the instruction frame
+ */
+ void positionTestWindows(List extends Window> testWindows,
+ InstructionUI instructionUI);
+ }
+
+ /**
+ * Provides information about the instruction frame.
+ */
+ public interface InstructionUI {
+ /**
+ * {@return the location of the instruction frame}
+ */
+ Point getLocation();
+
+ /**
+ * {@return the size of the instruction frame}
+ */
+ Dimension getSize();
+
+ /**
+ * {@return the bounds of the instruction frame}
+ */
+ Rectangle getBounds();
+
+ /**
+ * Allows to change the location of the instruction frame.
+ *
+ * @param location the new location of the instruction frame
+ */
+ void setLocation(Point location);
+
+ /**
+ * Allows to change the location of the instruction frame.
+ *
+ * @param x the x coordinate of the new location
+ * @param y the y coordinate of the new location
+ */
+ void setLocation(int x, int y);
+
+ /**
+ * Returns the specified position that was used to set
+ * the initial location of the instruction frame.
+ *
+ * @return the specified position
+ *
+ * @see Position
+ */
+ Position getPosition();
+ }
+
+
+ private static final class WindowClosingHandler extends WindowAdapter {
+ @Override
+ public void windowClosing(WindowEvent e) {
+ testFailedReason = FAILURE_REASON
+ + "User closed a window";
+ failed = true;
+ latch.countDown();
+ }
+ }
+
+ private static final WindowListener windowClosingHandler =
+ new WindowClosingHandler();
+
+
private static JComponent createCapturePanel() {
JComboBox screenShortType = new JComboBox<>(CaptureType.values());
@@ -409,13 +619,11 @@ public class PassFailJFrame {
}
/**
- * Dispose all the window(s) i,e both the test instruction frame and
- * the window(s) that is added via addTestWindow(Window testWindow)
+ * Disposes of all the windows. It disposes of the test instruction frame
+ * and all other windows added via {@link #addTestWindow(Window)}.
*/
private static synchronized void disposeWindows() {
- for (Window win : windowList) {
- win.dispose();
- }
+ windowList.forEach(Window::dispose);
}
/**
@@ -450,6 +658,36 @@ public class PassFailJFrame {
latch.countDown();
}
+ private static void positionInstructionFrame(final Position position) {
+ Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
+
+ // Get the screen insets to position the frame by taking into
+ // account the location of taskbar or menu bar on screen.
+ GraphicsConfiguration gc = GraphicsEnvironment.getLocalGraphicsEnvironment()
+ .getDefaultScreenDevice()
+ .getDefaultConfiguration();
+ Insets screenInsets = Toolkit.getDefaultToolkit().getScreenInsets(gc);
+
+ switch (position) {
+ case HORIZONTAL:
+ int newX = ((screenSize.width / 2) - frame.getWidth());
+ frame.setLocation((newX + screenInsets.left),
+ (frame.getY() + screenInsets.top));
+ break;
+
+ case VERTICAL:
+ int newY = ((screenSize.height / 2) - frame.getHeight());
+ frame.setLocation((frame.getX() + screenInsets.left),
+ (newY + screenInsets.top));
+ break;
+
+ case TOP_LEFT_CORNER:
+ frame.setLocation(screenInsets.left, screenInsets.top);
+ break;
+ }
+ syncLocationToWindowManager();
+ }
+
/**
* Approximately positions the instruction frame relative to the test
* window as specified by the {@code position} parameter. If {@code testWindow}
@@ -480,40 +718,23 @@ public class PassFailJFrame {
*
*/
public static void positionTestWindow(Window testWindow, Position position) {
- Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
+ positionInstructionFrame(position);
- // Get the screen insets to position the frame by taking into
- // account the location of taskbar/menubars on screen.
- GraphicsConfiguration gc = GraphicsEnvironment.getLocalGraphicsEnvironment()
- .getDefaultScreenDevice().getDefaultConfiguration();
- Insets screenInsets = Toolkit.getDefaultToolkit().getScreenInsets(gc);
+ if (testWindow != null) {
+ switch (position) {
+ case HORIZONTAL:
+ case TOP_LEFT_CORNER:
+ testWindow.setLocation((frame.getX() + frame.getWidth() + 5),
+ frame.getY());
+ break;
- if (position.equals(Position.HORIZONTAL)) {
- int newX = ((screenSize.width / 2) - frame.getWidth());
- frame.setLocation((newX + screenInsets.left),
- (frame.getY() + screenInsets.top));
- syncLocationToWindowManager();
- if (testWindow != null) {
- testWindow.setLocation((frame.getX() + frame.getWidth() + 5),
- frame.getY());
- }
- } else if (position.equals(Position.VERTICAL)) {
- int newY = ((screenSize.height / 2) - frame.getHeight());
- frame.setLocation((frame.getX() + screenInsets.left),
- (newY + screenInsets.top));
- syncLocationToWindowManager();
- if (testWindow != null) {
- testWindow.setLocation(frame.getX(),
- (frame.getY() + frame.getHeight() + 5));
- }
- } else if (position.equals(Position.TOP_LEFT_CORNER)) {
- frame.setLocation(screenInsets.left, screenInsets.top);
- syncLocationToWindowManager();
- if (testWindow != null) {
- testWindow.setLocation((frame.getX() + frame.getWidth() + 5),
- frame.getY());
+ case VERTICAL:
+ testWindow.setLocation(frame.getX(),
+ (frame.getY() + frame.getHeight() + 5));
+ break;
}
}
+
// make instruction frame visible after updating
// frame & window positions
frame.setVisible(true);
@@ -553,13 +774,7 @@ public class PassFailJFrame {
throws InterruptedException, InvocationTargetException {
final Rectangle[] bounds = {null};
- if (isEventDispatchThread()) {
- bounds[0] = frame != null ? frame.getBounds() : null;
- } else {
- invokeAndWait(() -> {
- bounds[0] = frame != null ? frame.getBounds() : null;
- });
- }
+ invokeOnEDT(() -> bounds[0] = frame != null ? frame.getBounds() : null);
return bounds[0];
}
@@ -574,6 +789,16 @@ public class PassFailJFrame {
windowList.add(testWindow);
}
+ /**
+ * Adds a collection of test windows to the windowList to be disposed of
+ * when the test completes.
+ *
+ * @param testWindows the collection of test windows to be disposed of
+ */
+ public static synchronized void addTestWindow(Collection extends Window> testWindows) {
+ windowList.addAll(testWindows);
+ }
+
/**
* Forcibly pass the test.
* The sample usage:
@@ -607,13 +832,20 @@ public class PassFailJFrame {
latch.countDown();
}
- public static class Builder {
+ public static final class Builder {
private String title;
private String instructions;
private long testTimeOut;
private int rows;
private int columns;
- private boolean screenCapture = false;
+ private boolean screenCapture;
+
+ private List extends Window> testWindows;
+ private WindowCreator windowCreator;
+ private PositionWindows positionWindows;
+ private InstructionUI instructionUIHandler;
+
+ private Position position;
public Builder title(String title) {
this.title = title;
@@ -645,6 +877,51 @@ public class PassFailJFrame {
return this;
}
+ public Builder testUI(Window window) {
+ return testUI(List.of(window));
+ }
+
+ public Builder testUI(Window... windows) {
+ return testUI(List.of(windows));
+ }
+
+ public Builder testUI(List windows) {
+ if (windows == null) {
+ throw new IllegalArgumentException("The list of windows can't be null");
+ }
+ if (windows.stream()
+ .anyMatch(Objects::isNull)) {
+ throw new IllegalArgumentException("The windows list can't contain null");
+ }
+
+ if (windowCreator != null) {
+ throw new IllegalStateException("windowCreator is already set");
+ }
+ this.testWindows = windows;
+ return this;
+ }
+
+ public Builder testUI(WindowCreator windowCreator) {
+ if (windowCreator == null) {
+ throw new IllegalArgumentException("The window creator can't be null");
+ }
+ if (testWindows != null) {
+ throw new IllegalStateException("testWindows are already set");
+ }
+ this.windowCreator = windowCreator;
+ return this;
+ }
+
+ public Builder positionTestUI(PositionWindows positionWindows) {
+ this.positionWindows = positionWindows;
+ return this;
+ }
+
+ public Builder position(Position position) {
+ this.position = position;
+ return this;
+ }
+
public PassFailJFrame build() throws InterruptedException,
InvocationTargetException {
validate();
@@ -652,26 +929,76 @@ public class PassFailJFrame {
}
private void validate() {
- if (this.title == null) {
- this.title = TITLE;
+ if (title == null) {
+ title = TITLE;
}
- if (this.instructions == null || this.instructions.length() == 0) {
- throw new RuntimeException("Please provide the test " +
- "instruction for this manual test");
+ if (instructions == null || instructions.isEmpty()) {
+ throw new IllegalStateException("Please provide the test " +
+ "instructions for this manual test");
}
- if (this.testTimeOut == 0L) {
- this.testTimeOut = TEST_TIMEOUT;
+ if (testTimeOut == 0L) {
+ testTimeOut = TEST_TIMEOUT;
}
- if (this.rows == 0) {
- this.rows = ROWS;
+ if (rows == 0) {
+ rows = ROWS;
}
- if (this.columns == 0) {
- this.columns = COLUMNS;
+ if (columns == 0) {
+ columns = COLUMNS;
+ }
+
+ if (position == null
+ && (testWindows != null || windowCreator != null)) {
+
+ position = Position.HORIZONTAL;
+ }
+
+ if (positionWindows != null) {
+ if (testWindows == null && windowCreator == null) {
+ throw new IllegalStateException("To position windows, "
+ + "provide an a list of windows to the builder");
+ }
+ instructionUIHandler = new InstructionUIHandler();
+ }
+ }
+
+ private final class InstructionUIHandler implements InstructionUI {
+ @Override
+ public Point getLocation() {
+ return frame.getLocation();
+ }
+
+ @Override
+ public Dimension getSize() {
+ return frame.getSize();
+ }
+
+ @Override
+ public Rectangle getBounds() {
+ return frame.getBounds();
+ }
+
+ @Override
+ public void setLocation(Point location) {
+ setLocation(location.x, location.y);
+ }
+
+ @Override
+ public void setLocation(int x, int y) {
+ frame.setLocation(x, y);
+ }
+
+ @Override
+ public Position getPosition() {
+ return position;
}
}
}
+
+ public static Builder builder() {
+ return new Builder();
+ }
}