package games.strategy.ui;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GradientPaint;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.MediaTracker;
import java.awt.Point;
import java.awt.Polygon;
import java.awt.RenderingHints;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.geom.GeneralPath;
import java.awt.image.BufferedImage;
import java.awt.image.ImageObserver;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicReference;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
import games.strategy.util.CountDownLatchHandler;
import games.strategy.util.EventThreadJOptionPane;
public final class Util {
public static final String TERRITORY_SEA_ZONE_INFIX = "Sea Zone";
// all we have is static methods
private Util() {}
public interface Task<T> {
T run();
}
public static <T> T runInSwingEventThread(final Task<T> task) {
if (SwingUtilities.isEventDispatchThread()) {
return task.run();
}
final AtomicReference<T> results = new AtomicReference<>();
SwingAction.invokeAndWait(() -> results.set(task.run()));
return results.get();
}
private static final Component c = new Component() {
private static final long serialVersionUID = 1800075529163275600L;
};
public static void ensureImageLoaded(final Image anImage) {
final MediaTracker tracker = new MediaTracker(c);
tracker.addImage(anImage, 1);
try {
tracker.waitForAll();
tracker.removeImage(anImage);
} catch (final InterruptedException ignored) {
// ignore interrupted
}
}
public static Image copyImage(final BufferedImage img) {
final BufferedImage copy = createImage(img.getWidth(), img.getHeight(), false);
final Graphics2D g = (Graphics2D) copy.getGraphics();
g.drawImage(img, 0, 0, null);
g.dispose();
return copy;
}
public static void notifyError(final Component parent, final String message) {
EventThreadJOptionPane.showMessageDialog(JOptionPane.getFrameForComponent(parent), message, "Error",
JOptionPane.ERROR_MESSAGE, new CountDownLatchHandler(true));
}
/**
* Previously used to use TYPE_INT_BGR and TYPE_INT_ABGR but caused memory
* problems. Fix is to use 3Byte rather than INT.
*/
public static BufferedImage createImage(final int width, final int height, final boolean needAlpha) {
if (needAlpha) {
return new BufferedImage(width, height, BufferedImage.TYPE_4BYTE_ABGR);
} else {
return new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR);
}
}
public static Dimension getDimension(final Image anImage, final ImageObserver obs) {
return new Dimension(anImage.getWidth(obs), anImage.getHeight(obs));
}
public static void center(final Window w) {
final int screenWidth = Toolkit.getDefaultToolkit().getScreenSize().width;
final int screenHeight = Toolkit.getDefaultToolkit().getScreenSize().height;
final int windowWidth = w.getWidth();
final int windowHeight = w.getHeight();
if (windowHeight > screenHeight) {
return;
}
if (windowWidth > screenWidth) {
return;
}
final int x = (screenWidth - windowWidth) / 2;
final int y = (screenHeight - windowHeight) / 2;
w.setLocation(x, y);
}
// code stolen from swingx
// swingx is lgpl, so no problems with copyright
public static Image getBanner(final String text) {
final int w = 400;
final int h = 60;
final float loginStringX = w * .05f;
final float loginStringY = h * .75f;
final BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
final Graphics2D g2 = img.createGraphics();
final Font font = new Font("Arial Bold", Font.PLAIN, 36);
g2.setFont(font);
final Graphics2D originalGraphics = g2;
g2.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
g2.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
// draw a big square
g2.setColor(Color.GRAY);
g2.fillRect(0, 0, w, h);
// create the curve shape
final GeneralPath curveShape = new GeneralPath(GeneralPath.WIND_NON_ZERO);
curveShape.moveTo(0, h * .6f);
curveShape.curveTo(w * .167f, h * 1.2f, w * .667f, h * -.5f, w, h * .75f);
curveShape.lineTo(w, h);
curveShape.lineTo(0, h);
curveShape.lineTo(0, h * .8f);
curveShape.closePath();
// draw into the buffer a gradient (bottom to top), and the text "Login"
final GradientPaint gp = new GradientPaint(0, h, Color.GRAY, 0, 0, Color.LIGHT_GRAY);
g2.setPaint(gp);
g2.fill(curveShape);
// g2.setPaint(Color.white);
originalGraphics.setColor(Color.WHITE);
originalGraphics.drawString(text, loginStringX, loginStringY);
return img;
}
/**
* java.lang.String findTerritoryName(java.awt.Point)
* Finds a land territory name or some sea zone name where the point is contained in according to the territory name
* -> polygons map.
*
* @param p A point on the map.
* @param terrPolygons a map territory name -> polygons
* @return Optional<String>
*/
public static Optional<String> findTerritoryName(final Point p, final Map<String, List<Polygon>> terrPolygons) {
return Optional.ofNullable(findTerritoryName(p, terrPolygons, null));
}
/**
* java.lang.String findTerritoryName(java.awt.Point)
* Finds a land territory name or some sea zone name where the point is contained in according to the territory name
* -> polygons map. If no land or sea territory has been found a default name is returned.
*
* @param p A point on the map.
* @param terrPolygons a map territory name -> polygons
* @param defaultTerrName Default territory name that gets returns if nothing was found.
* @return found territory name of defaultTerrName
*/
public static String findTerritoryName(final Point p, final Map<String, List<Polygon>> terrPolygons,
final String defaultTerrName) {
String lastWaterTerrName = defaultTerrName;
// try to find a land territory.
// sea zones often surround a land territory
for (final String terrName : terrPolygons.keySet()) {
final Collection<Polygon> polygons = terrPolygons.get(terrName);
for (final Polygon poly : polygons) {
if (poly.contains(p)) {
if (Util.isTerritoryNameIndicatingWater(terrName)) {
lastWaterTerrName = terrName;
} else {
return terrName;
}
} // if p is contained
} // polygons collection loop
} // terrPolygons map loop
return lastWaterTerrName;
}
/**
* Checks whether name indicates water or not (meaning name starts or ends with default text).
*
* @param territoryName - territory name
* @return true if yes, false otherwise
*/
public static boolean isTerritoryNameIndicatingWater(final String territoryName) {
return territoryName.endsWith(TERRITORY_SEA_ZONE_INFIX) || territoryName.startsWith(TERRITORY_SEA_ZONE_INFIX);
}
}