/* * MapPanel.java * * Created on January 25, 2007, 6:39 PM */ package editor; import editor.mapmode.MapMode; import editor.mapmode.ProvinceMode; import eug.specific.eu3.EU3DataSource; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.geom.AffineTransform; import java.awt.image.AffineTransformOp; import java.awt.image.BufferedImage; import java.awt.image.BufferedImageOp; import java.io.File; import java.io.IOException; import java.util.List; import java.util.Locale; import javax.imageio.ImageIO; import javax.swing.JViewport; import javax.swing.Scrollable; import javax.swing.Timer; /** * * @author Michael Myers */ public class MapPanel extends javax.swing.JPanel implements Scrollable { private transient BufferedImage mapImage; private transient BufferedImage scaledMapImage; private double scaleFactor = DEFAULT_SCALE_FACTOR; public static final double DEFAULT_SCALE_FACTOR = 1.0; //0.8; //0.7; private static final double MIN_SCALE = 0.05; private static final double MAX_SCALE = 5.0; private static final double DEFAULT_ZOOM_AMOUNT = 0.2; //0.1; /** @since 0.4pre1 */ private MapPanelDataModel model; /** @since 0.4pre1 */ private transient MapMode mode; /** * Mapping which allows this class to override the color the * {@link #mode MapMode} paints a province. * @since 0.4pre4 */ public transient java.util.Map<Integer, Color> overrides; /** @since 0.5pre3 */ private boolean paintBorders; /** * Creates new form MapPanel. {@link #initialize()} must be called before the * panel is shown; this way, GUI editors can use this component without * having to load the data. */ public MapPanel() { // initialize(); } /** * Loads the necessary data for showing the map. This method must be called * before the panel is shown on screen; otherwise, it's just a blank JPanel. */ public void initialize() { createMapImage(); System.out.println("Reading map..."); final MapData data = new MapData(scaledMapImage, Integer.parseInt(Main.map.getString("max_provinces"))); System.out.println("Done."); model = new MapPanelDataModel(data); model.setDate("1453.1.1"); initComponents(); overrides = new java.util.HashMap<Integer, Color>(); mode = new ProvinceMode(this); paintBorders = true; } private void createMapImage() { try { String provFileName = Main.map.getString("provinces").replace('\\', '/'); if (!provFileName.contains("/")) provFileName = "map/" + provFileName; mapImage = ImageIO.read(new File(Main.filenameResolver.resolveFilename(provFileName))); rescaleMap(); } catch (IOException ex) { javax.swing.JOptionPane.showMessageDialog(null, "Error reading map: " + ex.getMessage(), "Error", javax.swing.JOptionPane.ERROR_MESSAGE); ex.printStackTrace(); } } public BufferedImage getMapImage() { return mapImage; } /** This method is called from within the constructor to * initialize the form. * WARNING: Do NOT modify this code. The content of this method is * always regenerated by the Form Editor. */ // <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:initComponents private void initComponents() { }// </editor-fold>//GEN-END:initComponents // <editor-fold defaultstate="collapsed" desc=" Generated Variables "> // Variables declaration - do not modify//GEN-BEGIN:variables // End of variables declaration//GEN-END:variables // </editor-fold> @Override protected void paintComponent(final Graphics g) { if (scaledMapImage == null) { super.paintComponent(g); } else { mode.paint(g); for (java.util.Map.Entry<Integer, Color> override : overrides.entrySet()) { paintProvince((Graphics2D) g, override.getKey(), override.getValue()); } if (paintBorders && mode.paintsBorders()) { paintBorders((Graphics2D) g); } } } /** * Paints the province map image. */ public final void paintProvinces(final Graphics2D g2D) { g2D.drawImage(scaledMapImage, 0, 0, null); } /** * Paint a province the given color. */ public final void paintProvince(final Graphics2D g, int provId, final Color c) { paintLines(g, model.getLinesInProv(provId), c); } /** * Paint a province the given paint. */ public final void paintProvince(final Graphics2D g, int provId, final Paint p) { paintLines(g, model.getLinesInProv(provId), p); } private final void paintLines(final Graphics2D g, final List<Integer[]> lines, final Color c) { paintLines(g, lines, (Paint) c); } /** * This is the actual method used to paint a province. */ private final void paintLines(final Graphics2D g, final List<Integer[]> lines, final Paint paint) { // Quick sanity check if (lines == null) return; if (g.getPaint() != paint) { g.setPaint(paint); } final double scale = scaleFactor/DEFAULT_SCALE_FACTOR; final int maxY = getHeight(); int x1, x2, y1, y2; double y; for(Integer[] line : lines) { x1 = (int) Math.round(((double) line[1]) * scale); x2 = (int) Math.round(((double) line[2]) * scale); // If there is an inaccuracy, y is usually the culprit. // This has been hacked to keep from overrunning the province bounds. y = ((double) (line[0]+1)) * scale; y1 = Math.max((int) Math.round(y - scale), 0); y = ((double) (line[0])) * scale; y2 = Math.min((int) Math.round(y + scale), maxY); g.fillRect(x1, y1, x2-x1, y2-y1); } } public final void paintBorders(final Graphics2D g) { g.setColor(Color.BLACK); final double scale = scaleFactor/DEFAULT_SCALE_FACTOR; final double halfScale = scale/2; final int maxX = getWidth(); final int maxY = getHeight(); int x1, x2, y1, y2; double x, y; for (Integer[] pt : model.getMapData().getBorderPixels()) { x = ((double) pt[0]) * scale; x1 = Math.max((int) Math.round(x - halfScale), 0); x2 = Math.min((int) Math.round(x + halfScale), maxX); y = ((double) pt[1]) * scale; y1 = Math.max((int) Math.round(y - halfScale), 0); y2 = Math.min((int) Math.round(y + halfScale), maxY); g.fillRect(x1, y1, x2-x1, y2-y1); } } @Override public Dimension getPreferredSize() { if (scaledMapImage == null) { // System.err.println("scaledMap == null!"); return super.getPreferredSize(); } return new Dimension(scaledMapImage.getWidth(), scaledMapImage.getHeight()); } // Locale.US is necessary because, in some locales, the comma ',' is used as // the decimal separator, which Double.parseDouble doesn't recognize. private static final java.text.NumberFormat rounder = java.text.NumberFormat.getNumberInstance(Locale.US); static { if (rounder instanceof java.text.DecimalFormat) { ((java.text.DecimalFormat)rounder).setDecimalSeparatorAlwaysShown(true); ((java.text.DecimalFormat)rounder).setMaximumFractionDigits(2); } } /** * @deprecated As of 0.3, use {@link zoomIn()}, {@link zoomIn(double)}, * {@link zoomOut}, or {@link zoomOut(double)} instead. */ @Deprecated public void setScaleFactor(double factor) { if (factor < MIN_SCALE) return; // System.out.println("New scale factor: " + factor); scaleFactor = factor; rescaleMap(); // repaint(); } public double getScaleFactor() { return scaleFactor; } /** @since 0.3pre1 */ public void zoomIn() { zoomIn(DEFAULT_ZOOM_AMOUNT); } /** @since 0.3pre1 */ public void zoomIn(double amount) { if (scaleFactor <= MAX_SCALE - amount) { // final Point oldPosition = ((JViewport) getParent()).getViewPosition(); // final int oldx = (int) (oldPosition.getX() / scaleFactor); // final int oldy = (int) (oldPosition.getY() / scaleFactor); scaleFactor += amount; scaleFactor = Double.parseDouble(rounder.format(scaleFactor)); rescaleMap(); // ((JViewport) getParent()).setViewPosition(new Point((int) (oldx * scaleFactor), (int) (oldy * scaleFactor))); } } /** @since 0.3pre1 */ public void zoomOut() { zoomOut(DEFAULT_ZOOM_AMOUNT); } /** @since 0.3pre1 */ public void zoomOut(double amount) { if (scaleFactor >= amount + MIN_SCALE) { // final Point oldPosition = ((JViewport) getParent()).getViewPosition(); // final int oldx = (int) (oldPosition.getX() / scaleFactor); // final int oldy = (int) (oldPosition.getY() / scaleFactor); scaleFactor -= amount; scaleFactor = Double.parseDouble(rounder.format(scaleFactor)); rescaleMap(); // ((JViewport) getParent()).setViewPosition(new Point((int) (oldx * scaleFactor), (int) (oldy * scaleFactor))); } } private static final RenderingHints scalingHints = new RenderingHints(null); static { scalingHints.put(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR); scalingHints.put(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_DISABLE); scalingHints.put(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY); } private void rescaleMap() { if (scaledMapImage != null) { scaledMapImage.flush(); scaledMapImage = null; } scaledMapImage = new BufferedImage( (int) Math.ceil(mapImage.getWidth() * scaleFactor), (int) Math.ceil(mapImage.getHeight() * scaleFactor), mapImage.getType() ); final BufferedImageOp transform; if (new File(Main.filenameResolver.resolveFilename("common/faction.txt")).exists()) { // DW transform = new AffineTransformOp( new AffineTransform(scaleFactor, 0.0, 0.0, scaleFactor, 0.0, -scaledMapImage.getHeight()), scalingHints ); } else { transform = new AffineTransformOp( new AffineTransform(scaleFactor, 0.0, 0.0, -scaleFactor, 0.0, 0.0), scalingHints ); } scaledMapImage.createGraphics().drawImage(mapImage, transform, 0, scaledMapImage.getHeight()); } public ProvinceData.Province getProvinceAt(final Point pt) { if (pt == null) return null; final ProvinceData.Province p = model.getProvinceData().getProv(scaledMapImage.getRGB(pt.x, pt.y)); if (p == null) { java.awt.Color c = new java.awt.Color(scaledMapImage.getRGB(pt.x, pt.y)); System.err.println("No province registered for " + c.getRed() + "," + c.getGreen() + "," + c.getBlue()); } return p; } public Dimension getPreferredScrollableViewportSize() { return getPreferredSize(); } public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) { return 5; } public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) { return 50; } public boolean getScrollableTracksViewportWidth() { return false; } public boolean getScrollableTracksViewportHeight() { return false; } /** @since 0.3pre1 */ public void goToProv(int provId) { java.awt.Rectangle r = calculateBounds(model.getMapData().getLinesInProv(model.getProvinceData().getProvByID(provId).getColor())); // We want the point x + width/2, y + height/2 to be in the // center of the viewport. int x = r.x + r.width/2; int y = r.y + r.height/2; // Since the bounds were calculated for the default scale factor, we // need to adjust them. x = (int) (((double) x) * (scaleFactor / DEFAULT_SCALE_FACTOR)); y = (int) (((double) y) * (scaleFactor / DEFAULT_SCALE_FACTOR)); centerMap(x, y); } /** @since 0.4pre1 */ public void centerMap() { centerMap(scaledMapImage.getWidth()/2, scaledMapImage.getHeight()/2); } private void centerMap(int x, int y) { java.awt.Dimension size = getParent().getSize(); x = Math.max(0, x - (size.width/2)); y = Math.max(0, y - (size.height/2)); ((JViewport)getParent()).setViewPosition(new Point(x, y)); } private static java.awt.Rectangle calculateBounds(final List<Integer[]> lines) { int xLeft = Integer.MAX_VALUE; int xRight = Integer.MIN_VALUE; int yTop = Integer.MAX_VALUE; int yBot = Integer.MIN_VALUE; for (Integer[] line : lines) { if (line[0] < yTop) yTop = line[0]; if (line[0] > yBot) yBot = line[0]; if (line[1] < xLeft) xLeft = line[1]; if (line[2] > xRight) xRight = line[2]; } return new java.awt.Rectangle(xLeft, yTop, xRight-xLeft, yBot-yTop); } /** @since 0.3pre1 */ public Color getColor(Point point) { if (point == null) return null; return new Color(scaledMapImage.getRGB(point.x, point.y)); } /** @since 0.3pre1 */ public MapMode getMode() { return mode; } /** @since 0.3pre1 */ public void setMode(MapMode mode) { this.mode = mode; } /** @since 0.4pre1 */ public MapPanelDataModel getModel() { return model; } public void setModel(MapPanelDataModel model) { this.model = model; } /** @since 0.3pre1 */ public void setToolTipTextForProv(final ProvinceData.Province p) { final StringBuilder tooltip = new StringBuilder("<html><b>"); tooltip.append(p.getName()).append("</b><br>ID: ").append(p.getId()); final String extra = mode.getTooltipExtraText(p); if (extra.length() != 0) tooltip.append("<br>").append(extra); setToolTipText(tooltip.append("</html>").toString()); } /** @since 0.4pre4 */ public void colorProvince(final int provId, final Color color) { overrides.put(provId, color); } /** @since 0.4pre4 */ public void uncolorProvince(final int provId) { overrides.remove(provId); } /** @since 0.4pre4 */ public void flashProvince(final int provId) { flashProvince(provId, 3); } /** @since 0.4pre4 */ public void flashProvince(final int provId, final int numFlashes) { final ActionListener listener = new ActionListener() { private boolean color = true; public void actionPerformed(final ActionEvent e) { if (color) { colorProvince(provId, Color.WHITE); } else { uncolorProvince(provId); } color = !color; repaint(); } }; for (int i = 1; i <= numFlashes*2; i++) { Timer t = new Timer(333*i, listener); t.setRepeats(false); t.start(); } } /** @since 0.6pre1 */ public EU3DataSource getDataSource() { return model.getDataSource(); } /** @since 0.5pre1 */ public void setDataSource(EU3DataSource dataSource) { model.setDataSource(dataSource); model.preloadProvs(); } /** @since 0.5pre3 */ public boolean isPaintBorders() { return paintBorders; } /** @since 0.5pre3 */ public void setPaintBorders(boolean paintBorders) { this.paintBorders = paintBorders; } private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); mode = new ProvinceMode(this); overrides = new java.util.HashMap<Integer, Color>(); createMapImage(); } // private static class BoundBox { // private List<Integer[]> lines; // private Integer[] bounds; // // public BoundBox(final List<Integer[]> lines) { // this.lines = lines; // bounds = new Integer[] {Integer.MAX_VALUE,Integer.MIN_VALUE,Integer.MAX_VALUE,Integer.MIN_VALUE}; // updateBounds(); // } // // private void updateBounds() { // int x1 = bounds[0]; // int x2 = bounds[1]; // int y1 = bounds[2]; // int y2 = bounds[3]; // for (Integer[] line : lines) { // // X // x1 = Math.min(line[1], x1); // x2 = Math.max(line[2], x2); // // Y // int y = line[0]; // y1 = Math.min(y, y1); // y2 = Math.max(y, y2); // } // bounds[0] = x1; // bounds[1] = x2; // bounds[2] = y1; // bounds[3] = y2; // } // } }