// Copyright 2013 SICK AG. All rights reserved.
package de.sick.guicheck.swing;
import java.awt.Dialog;
import java.awt.Frame;
import java.awt.Window;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import javafx.application.Platform;
import javax.swing.SwingUtilities;
import de.sick.guicheck.GcAssertException;
import de.sick.guicheck.GcUtils;
import de.sick.guicheck.GcUtils.IEvaluator;
/**
* General helpers for GUIcheck tests based on Swing.
*
* @author linggol (created)
*/
public final class GcUtilsSwing
{
private static final int EVALUATION_RETRIES = 10;
private static final int EVALUATION_DELAY = 50;
private static final int IDLE_COUNT = 3;
private static final int RUN_LATER_AND_WAIT_TIMEOUT = 500;
// EasyMock needs a litte time for synchronisation between UI and mocked objects
private static int ms_slowMotionFactor = 10;
public static void setSlowMotion(int factor)
{
ms_slowMotionFactor = factor;
}
private GcUtilsSwing()
{
// Prevent instantiation
}
/**
* Wait for the EDT thread to become idle.
*/
public static void waitForIdle()
{
waitForIdle(IDLE_COUNT, ms_slowMotionFactor);
}
/**
* Wait the given sleep cycles for the EDT to become idle.
*/
public static void waitForIdle(int count, int sleep)
{
for (int i = 0; i < count; i++)
{
runLaterAndWait(GcUtils.NOOP_RUNNABLE);
GcUtils.sleepAndIgnoreInterrupts(sleep);
}
}
/**
* Run the given runnable in the EDT and wait until its finished.
*/
public static void runLaterAndWait(final Runnable runnable)
{
final CountDownLatch l_latch = new CountDownLatch(1);
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
runnable.run();
l_latch.countDown();
}
});
while (true)
{
try
{
if (l_latch.await(RUN_LATER_AND_WAIT_TIMEOUT, TimeUnit.MILLISECONDS))
{
break;
}
}
catch (InterruptedException e)
{
}
}
}
/**
* Use this method to stop the test at any point and wait until all windows get closed by the program or user. The
* program will exit after waiting. This method is especially useful while debugging with GUI tests.
*/
public static void waitAndExitWhenAllWindowsClosed()
{
while (Window.getWindows().length > 0)
{
GcUtils.sleepAndIgnoreInterrupts(500);
}
Platform.exit();
System.exit(0);
}
/**
* Return the window with the given title.
*
* @param titleRegEx <code>null</code> means the title is not set
*/
public static final GcWindowSwing window(final String titleRegEx)
{
return eval(new GcUtils.IEvaluator<GcWindowSwing>()
{
@Override
public GcWindowSwing eval()
{
final GcWindowSwing l_window = getWindowRaw(titleRegEx);
if (l_window == null)
{
throw new GcAssertException("Cannot find window with title: " + titleRegEx);
}
return l_window;
}
});
}
/**
* Return the window with the given title.
*
* @param titleRegEx <code>null</code> means the title is not set
*/
public static final GcWindowSwing window(final String titleRegEx, int evalRetries, int evalDelay)
{
return eval(new GcUtils.IEvaluator<GcWindowSwing>()
{
@Override
public GcWindowSwing eval()
{
final GcWindowSwing l_window = getWindowRaw(titleRegEx);
if (l_window == null)
{
throw new GcAssertException("Cannot find window with title: " + titleRegEx);
}
return l_window;
}
}, evalRetries, evalDelay);
}
/**
* Return the window with the given title. Use a more immediate mode without retries and timeouts. It also does not
* throw any exception instead it returns <code>null</code> if the window is not found.
* <p>
* <b>Warning: Use this method only if you are sure it works in your case.</b>
*
* @param titleRegEx <code>null</code> means the title is not set
*/
public static final GcWindowSwing getWindowRaw(final String titleRegEx)
{
for (Window l_window : Window.getWindows())
{
if (l_window instanceof Frame)
{
if (((Frame)l_window).isVisible() && GcUtils.startsWithOrMatches(((Frame)l_window).getTitle(), titleRegEx))
{
return new GcWindowSwing(l_window);
}
}
else if (l_window instanceof Dialog)
{
if (((Dialog)l_window).isVisible() && GcUtils.startsWithOrMatches(((Dialog)l_window).getTitle(), titleRegEx))
{
return new GcWindowSwing(l_window);
}
}
}
return null;
}
/**
* Evaluate the given evaluator with retries and timeouts. Retries are only done automatically if the evaluator
* throws a {@link GcAssertException}. After each try this method waits for the EDT to become idle.
*/
public static <T> T eval(IEvaluator<T> e)
{
for (int i = 0; i < EVALUATION_RETRIES - 1; i++)
{
try
{
return e.eval();
}
catch (GcAssertException ex)
{
GcUtils.sleepAndIgnoreInterrupts(EVALUATION_DELAY);
waitForIdle();
}
}
// The last time we try it without catching any exceptions
return e.eval();
}
/**
* Evaluate the given evaluator with retries and timeouts. Retries are only done automatically if the evaluator
* throws a {@link GcAssertException}. After each try this method waits for the EDT to become idle.
*/
public static <T> T eval(IEvaluator<T> e, int evalRetries, int evalDelay)
{
for (int i = 0; i < evalRetries - 1; i++)
{
try
{
return e.eval();
}
catch (GcAssertException ex)
{
GcUtils.sleepAndIgnoreInterrupts(evalDelay);
waitForIdle();
}
}
// The last time we try it without catching any exceptions
return e.eval();
}
}