5454import java .util .concurrent .atomic .AtomicInteger ;
5555
5656import javax .imageio .ImageIO ;
57+ import javax .swing .Box ;
5758import javax .swing .JButton ;
5859import javax .swing .JComboBox ;
5960import javax .swing .JComponent ;
156157 * <li>the title of the instruction UI,</li>
157158 * <li>the timeout of the test,</li>
158159 * <li>the size of the instruction UI via rows and columns, and</li>
160+ * <li>to add a log area</li>,
159161 * <li>to enable screenshots.</li>
160162 * </ul>
161163 */
@@ -205,6 +207,8 @@ public final class PassFailJFrame {
205207
206208 private static Robot robot ;
207209
210+ private static JTextArea logArea ;
211+
208212 public enum Position {HORIZONTAL , VERTICAL , TOP_LEFT_CORNER }
209213
210214 public PassFailJFrame (String instructions ) throws InterruptedException ,
@@ -374,6 +378,20 @@ private static void invokeOnEDT(Runnable doRun)
374378 }
375379 }
376380
381+ /**
382+ * Does the same as {@link #invokeOnEDT(Runnable)}, but does not throw
383+ * any checked exceptions.
384+ *
385+ * @param doRun an operation to run on EDT
386+ */
387+ private static void invokeOnEDTUncheckedException (Runnable doRun ) {
388+ try {
389+ invokeOnEDT (doRun );
390+ } catch (InterruptedException | InvocationTargetException e ) {
391+ throw new RuntimeException (e );
392+ }
393+ }
394+
377395 private static void createUI (String title , String instructions ,
378396 long testTimeOut , int rows , int columns ,
379397 boolean enableScreenCapture ) {
@@ -385,7 +403,8 @@ private static void createUI(String title, String instructions,
385403 frame .add (createInstructionUIPanel (instructions ,
386404 testTimeOut ,
387405 rows , columns ,
388- enableScreenCapture ),
406+ enableScreenCapture ,
407+ false , 0 ),
389408 BorderLayout .CENTER );
390409 frame .pack ();
391410 frame .setLocationRelativeTo (null );
@@ -402,8 +421,9 @@ private static void createUI(Builder builder) {
402421 createInstructionUIPanel (builder .instructions ,
403422 builder .testTimeOut ,
404423 builder .rows , builder .columns ,
405- builder .screenCapture );
406-
424+ builder .screenCapture ,
425+ builder .addLogArea ,
426+ builder .logAreaRows );
407427 if (builder .splitUI ) {
408428 JSplitPane splitPane = new JSplitPane (
409429 builder .splitUIOrientation ,
@@ -422,7 +442,9 @@ private static void createUI(Builder builder) {
422442 private static JComponent createInstructionUIPanel (String instructions ,
423443 long testTimeOut ,
424444 int rows , int columns ,
425- boolean enableScreenCapture ) {
445+ boolean enableScreenCapture ,
446+ boolean addLogArea ,
447+ int logAreaRows ) {
426448 JPanel main = new JPanel (new BorderLayout ());
427449 timeoutHandlerPanel = new TimeoutHandlerPanel (testTimeOut );
428450 main .add (timeoutHandlerPanel , BorderLayout .NORTH );
@@ -454,7 +476,20 @@ private static JComponent createInstructionUIPanel(String instructions,
454476 buttonsPanel .add (createCapturePanel ());
455477 }
456478
457- main .add (buttonsPanel , BorderLayout .SOUTH );
479+ if (addLogArea ) {
480+ logArea = new JTextArea (logAreaRows , columns );
481+ logArea .setEditable (false );
482+
483+ Box buttonsLogPanel = Box .createVerticalBox ();
484+
485+ buttonsLogPanel .add (buttonsPanel );
486+ buttonsLogPanel .add (new JScrollPane (logArea ));
487+
488+ main .add (buttonsLogPanel , BorderLayout .SOUTH );
489+ } else {
490+ main .add (buttonsPanel , BorderLayout .SOUTH );
491+ }
492+
458493 main .setMinimumSize (main .getPreferredSize ());
459494
460495 return main ;
@@ -1072,13 +1107,45 @@ public static void forceFail(String reason) {
10721107 latch .countDown ();
10731108 }
10741109
1110+ /**
1111+ * Adds a {@code message} to the log area, if enabled by
1112+ * {@link Builder#logArea()} or {@link Builder#logArea(int)}.
1113+ *
1114+ * @param message to log
1115+ */
1116+ public static void log (String message ) {
1117+ System .out .println ("PassFailJFrame: " + message );
1118+ invokeOnEDTUncheckedException (() -> logArea .append (message + "\n " ));
1119+ }
1120+
1121+ /**
1122+ * Clears the log area, if enabled by
1123+ * {@link Builder#logArea()} or {@link Builder#logArea(int)}.
1124+ */
1125+ public static void logClear () {
1126+ System .out .println ("\n PassFailJFrame: log cleared\n " );
1127+ invokeOnEDTUncheckedException (() -> logArea .setText ("" ));
1128+ }
1129+
1130+ /**
1131+ * Replaces the log area content with provided {@code text}, if enabled by
1132+ * {@link Builder#logArea()} or {@link Builder#logArea(int)}.
1133+ * @param text new text for the log area
1134+ */
1135+ public static void logSet (String text ) {
1136+ System .out .println ("\n PassFailJFrame: log set to:\n " + text + "\n " );
1137+ invokeOnEDTUncheckedException (() -> logArea .setText (text ));
1138+ }
1139+
10751140 public static final class Builder {
10761141 private String title ;
10771142 private String instructions ;
10781143 private long testTimeOut ;
10791144 private int rows ;
10801145 private int columns ;
10811146 private boolean screenCapture ;
1147+ private boolean addLogArea ;
1148+ private int logAreaRows = 10 ;
10821149
10831150 private List <? extends Window > testWindows ;
10841151 private WindowListCreator windowListCreator ;
@@ -1120,6 +1187,37 @@ public Builder screenCapture() {
11201187 return this ;
11211188 }
11221189
1190+ /**
1191+ * Adds a log area below the "Pass", "Fail" buttons.
1192+ * <p>
1193+ * The log area can be controlled by {@link #log(String)},
1194+ * {@link #logClear()} and {@link #logSet(String)}.
1195+ *
1196+ * @return this builder
1197+ */
1198+ public Builder logArea () {
1199+ this .addLogArea = true ;
1200+ return this ;
1201+ }
1202+
1203+ /**
1204+ * Adds a log area below the "Pass", "Fail" buttons.
1205+ * <p>
1206+ * The log area can be controlled by {@link #log(String)},
1207+ * {@link #logClear()} and {@link #logSet(String)}.
1208+ * <p>
1209+ * The number of columns is taken from the number of
1210+ * columns in the instructional JTextArea.
1211+ *
1212+ * @param rows of the log area
1213+ * @return this builder
1214+ */
1215+ public Builder logArea (int rows ) {
1216+ this .addLogArea = true ;
1217+ this .logAreaRows = rows ;
1218+ return this ;
1219+ }
1220+
11231221 /**
11241222 * Adds a {@code WindowCreator} which the framework will use
11251223 * to create the test UI window.
0 commit comments