package games.strategy.util;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Toolkit;
import java.util.Optional;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.IntSupplier;
import java.util.function.Supplier;
import javax.swing.BorderFactory;
import javax.swing.Icon;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;
import com.google.common.annotations.VisibleForTesting;
/**
* Blocking JOptionPane calls that do their work in the swing event thread (to be thread safe).
*/
public final class EventThreadJOptionPane {
private EventThreadJOptionPane() {
// do nothing
}
public static void showMessageDialog(final Component parentComponent, final Object message, final String title,
final int messageType, final CountDownLatchHandler latchHandler) {
showMessageDialog(parentComponent, message, title, messageType, false, latchHandler);
}
public static void showMessageDialog(final Component parentComponent, final Object message, final String title,
final int messageType, final boolean useJLabel, final CountDownLatchHandler latchHandler) {
invokeAndWait(
latchHandler,
() -> JOptionPane.showMessageDialog(parentComponent,
useJLabel ? createJLabelInScrollPane((String) message) : message, title, messageType));
}
private static void invokeAndWait(final CountDownLatchHandler latchHandler, final Runnable runnable) {
invokeAndWait(latchHandler, () -> {
runnable.run();
return Optional.empty();
});
}
@VisibleForTesting
static <T> Optional<T> invokeAndWait(final CountDownLatchHandler latchHandler, final Supplier<Optional<T>> supplier) {
if (SwingUtilities.isEventDispatchThread()) {
return supplier.get();
}
final CountDownLatch latch = new CountDownLatch(1);
final AtomicReference<Optional<T>> result = new AtomicReference<>();
SwingUtilities.invokeLater(() -> {
result.set(supplier.get());
latch.countDown();
});
if (latchHandler != null) {
latchHandler.addShutdownLatch(latch);
}
awaitLatch(latchHandler, latch);
return result.get();
}
private static JScrollPane createJLabelInScrollPane(final String message) {
final JLabel label = new JLabel(message);
final JScrollPane scroll = new JScrollPane(label);
scroll.setBorder(BorderFactory.createEmptyBorder());
final Dimension screenResolution = Toolkit.getDefaultToolkit().getScreenSize();
final int availWidth = screenResolution.width - 40;
final int availHeight = screenResolution.height - 140;
// add 25 for scrollbars
final int newWidth = (scroll.getPreferredSize().width > availWidth ? availWidth
: (scroll.getPreferredSize().width + (scroll.getPreferredSize().height > availHeight ? 25 : 0)));
final int newHeight = (scroll.getPreferredSize().height > availHeight ? availHeight
: (scroll.getPreferredSize().height + (scroll.getPreferredSize().width > availWidth ? 25 : 0)));
scroll.setPreferredSize(new Dimension(newWidth, newHeight));
return scroll;
}
private static void awaitLatch(final CountDownLatchHandler latchHandler, final CountDownLatch latch) {
boolean done = false;
while (!done) {
try {
latch.await();
done = true;
} catch (final InterruptedException e) {
if (latchHandler != null) {
latchHandler.interruptLatch(latch);
}
}
}
if (latchHandler != null) {
latchHandler.removeShutdownLatch(latch);
}
}
public static void showMessageDialog(final Component parentComponent, final Object message,
final CountDownLatchHandler latchHandler) {
invokeAndWait(
latchHandler,
() -> JOptionPane.showMessageDialog(parentComponent, message));
}
public static int showOptionDialog(final Component parentComponent, final Object message, final String title,
final int optionType, final int messageType, final Icon icon, final Object[] options, final Object initialValue,
final CountDownLatchHandler latchHandler) {
return invokeAndWait(
latchHandler,
() -> JOptionPane.showOptionDialog(parentComponent, message, title, optionType, messageType, icon, options,
initialValue));
}
@VisibleForTesting
static int invokeAndWait(final CountDownLatchHandler latchHandler, final IntSupplier supplier) {
return invokeAndWait(latchHandler, () -> Optional.of(supplier.getAsInt())).get();
}
public static int showConfirmDialog(final Component parentComponent, final Object message, final String title,
final int optionType, final CountDownLatchHandler latchHandler) {
return invokeAndWait(
latchHandler,
() -> JOptionPane.showConfirmDialog(parentComponent, message, title, optionType));
}
}