/*
* Tiled Map Editor, (c) 2004-2006
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Adam Turk <aturk@biggeruniverse.com>
* Bjorn Lindeijer <bjorn@lindeijer.nl>
*/
package tiled.view;
import java.awt.AlphaComposite;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Point;
import java.awt.Polygon;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.util.Iterator;
import javax.swing.JPanel;
import javax.swing.Scrollable;
import tiled.core.Map;
import tiled.core.MapLayer;
import tiled.core.MultilayerPlane;
import tiled.core.ObjectGroup;
import tiled.core.TileLayer;
import tiled.mapeditor.Resources;
import tiled.mapeditor.brush.Brush;
import tiled.mapeditor.selection.SelectionLayer;
/**
* The base class for map views. This is meant to be extended for different
* tile map orientations, such as orthagonal and isometric.
*
* @version $Id$
*/
public abstract class MapView extends JPanel implements Scrollable
{
/**
*
*/
private static final long serialVersionUID = 4006490742854425648L;
public static final int PF_BOUNDARYMODE = 0x02;
public static final int PF_COORDINATES = 0x04;
public static final int PF_NOSPECIAL = 0x08;
public static int ZOOM_NORMALSIZE = 5;
protected Map map;
protected Brush currentBrush;
protected int modeFlags;
protected double zoom = 1.0;
protected int zoomLevel = ZOOM_NORMALSIZE;
// Grid properties
protected boolean showGrid;
protected boolean antialiasGrid;
protected Color gridColor;
protected int gridOpacity;
protected static double[] zoomLevels = {
0.0625, 0.125, 0.25, 0.5, 0.75, 1.0, 1.5, 2.0, 3.0, 4.0
};
private static final Color DEFAULT_BACKGROUND_COLOR = new Color(64, 64, 64);
/** The default grid color (black). */
public static final Color DEFAULT_GRID_COLOR = Color.black;
protected static Image propertyFlagImage;
/**
* Creates a new <code>MapView</code> that displays the specified map.
*
* @param map the map to be displayed by this map view
*/
protected MapView(Map map) {
// Setup static bits on first invocation
if (MapView.propertyFlagImage == null) {
try {
MapView.propertyFlagImage =
Resources.getImage("propertyflag-12.png");
}
catch (Exception e) {
}
}
this.map = map;
setOpaque(true);
}
public void toggleMode(int modeModifier) {
modeFlags ^= modeModifier;
revalidate();
repaint();
}
public void setMode(int modeModifier, boolean value) {
if (value) {
modeFlags |= modeModifier;
}
else {
modeFlags &= ~modeModifier;
}
revalidate();
repaint();
}
public boolean getMode(int modeModifier) {
return (modeFlags & modeModifier) != 0;
}
public void setGridColor(Color gridColor) {
this.gridColor = gridColor;
repaint();
}
public void setGridOpacity(int gridOpacity) {
this.gridOpacity = gridOpacity;
repaint();
}
public void setAntialiasGrid(boolean antialiasGrid) {
this.antialiasGrid = antialiasGrid;
repaint();
}
public boolean getShowGrid() {
return showGrid;
}
public void setShowGrid(boolean showGrid) {
this.showGrid = showGrid;
revalidate();
repaint();
}
/**
* Sets a new brush. The brush can draw a preview of the change while
* editing.
* @param brush the new brush
*/
public void setBrush(Brush brush) {
currentBrush = brush;
}
// Zooming
public boolean zoomIn() {
if (zoomLevel < zoomLevels.length - 1) {
setZoomLevel(zoomLevel + 1);
}
return zoomLevel < zoomLevels.length - 1;
}
public boolean zoomOut() {
if (zoomLevel > 0) {
setZoomLevel(zoomLevel - 1);
}
return zoomLevel > 0;
}
public void setZoom(double zoom) {
if (zoom > 0) {
this.zoom = zoom;
//revalidate();
setSize(getPreferredSize());
}
}
public void setZoomLevel(int zoomLevel) {
if (zoomLevel >= 0 && zoomLevel < zoomLevels.length) {
this.zoomLevel = zoomLevel;
setZoom(zoomLevels[zoomLevel]);
}
}
public double getZoom() {
return zoom;
}
public int getZoomLevel() {
return zoomLevel;
}
// Scrolling
@Override
public abstract Dimension getPreferredSize();
public Dimension getPreferredScrollableViewportSize() {
return getPreferredSize();
}
public boolean getScrollableTracksViewportHeight() {
return false;
}
public boolean getScrollableTracksViewportWidth() {
return false;
}
public abstract int getScrollableBlockIncrement(Rectangle visibleRect,
int orientation, int direction);
public abstract int getScrollableUnitIncrement(Rectangle visibleRect,
int orientation, int direction);
/**
* Creates a MapView instance that will render the map in the right
* orientation.
*
* @param p the Map to create a view for
* @return a suitable instance of a MapView for the given Map
* @see Map#getOrientation()
*/
public static MapView createViewforMap(Map p) {
MapView mapView = null;
int orientation = p.getOrientation();
if (orientation == Map.MDO_ISO) {
mapView = new IsoMapView(p);
}
else if (orientation == Map.MDO_ORTHO) {
mapView = new OrthoMapView(p);
}
else if (orientation == Map.MDO_HEX) {
mapView = new HexMapView(p);
}
else if (orientation == Map.MDO_SHIFTED) {
mapView = new ShiftedMapView(p);
}
return mapView;
}
// Painting
/**
* Draws all the visible layers of the map. Takes several flags into
* account when drawing, and will also draw the grid, and any 'special'
* layers.
*
* @param g the Graphics2D object to paint to
* @see javax.swing.JComponent#paintComponent(Graphics)
* @see MapLayer
* @see SelectionLayer
*/
@Override
public void paintComponent(Graphics g) {
Graphics2D g2d = (Graphics2D) g.create();
MapLayer layer;
Rectangle clip = g2d.getClipBounds();
g2d.setStroke(new BasicStroke(2.0f));
// Do an initial fill with the background color
// todo: make background color configurable
//try {
// String colorString = displayPrefs.get("backgroundColor", "");
// g2d.setColor(Color.decode(colorString));
//} catch (NumberFormatException e) {
//}
g2d.setColor(DEFAULT_BACKGROUND_COLOR);
g2d.fillRect(clip.x, clip.y, clip.width, clip.height);
paintSubMap(map, g2d, 1.0f);
if (!getMode(PF_NOSPECIAL)) {
Iterator<MapLayer> li = map.getLayersSpecial();
while (li.hasNext()) {
layer = li.next();
if (layer.isVisible()) {
if (layer instanceof SelectionLayer) {
g2d.setComposite(AlphaComposite.getInstance(
AlphaComposite.SRC_ATOP, 0.3f));
g2d.setColor(
((SelectionLayer) layer).getHighlightColor());
}
paintLayer(g2d, (TileLayer) layer);
}
}
// Paint Brush
if (currentBrush != null) {
currentBrush.drawPreview(g2d, this);
}
}
// Grid color (also used for coordinates)
g2d.setColor(gridColor);
if (showGrid) {
// Grid opacity
if (gridOpacity < 255) {
g2d.setComposite(AlphaComposite.getInstance(
AlphaComposite.SRC_ATOP,
gridOpacity / 255.0f));
}
else {
g2d.setComposite(AlphaComposite.SrcOver);
}
// Configure grid antialiasing
if (antialiasGrid) {
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
}
else {
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_OFF);
}
g2d.setStroke(new BasicStroke());
paintGrid(g2d);
}
if (getMode(PF_COORDINATES)) {
g2d.setComposite(AlphaComposite.SrcOver);
paintCoordinates(g2d);
}
//if (editor != null && editor.getCurrentLayer() instanceof TileLayer) {
// g2d.setComposite(AlphaComposite.SrcOver);
//
// TileLayer tl = (TileLayer) editor.getCurrentLayer();
// if (tl != null && tl.isVisible()) {
// paintPropertyFlags(g2d, tl);
// }
//}
}
public void paintSubMap(MultilayerPlane m, Graphics2D g2d,
float mapOpacity) {
Iterator<MapLayer> li = m.getLayers();
MapLayer layer;
while (li.hasNext()) {
layer = li.next();
if (layer != null) {
float opacity = layer.getOpacity() * mapOpacity;
if (layer.isVisible() && opacity > 0.0f) {
if (opacity < 1.0f) {
g2d.setComposite(AlphaComposite.getInstance(
AlphaComposite.SRC_ATOP, opacity));
}
else {
g2d.setComposite(AlphaComposite.SrcOver);
}
if (layer instanceof TileLayer) {
paintLayer(g2d, (TileLayer) layer);
}
else if (layer instanceof ObjectGroup) {
paintObjectGroup(g2d, (ObjectGroup) layer);
}
}
}
}
}
/**
* Draws a TileLayer. Implemented in a subclass.
*
* @param g2d the graphics context to draw the layer onto
* @param layer the TileLayer to be drawn
*/
protected abstract void paintLayer(Graphics2D g2d, TileLayer layer);
/**
* Draws an ObjectGroup. Implemented in a subclass.
*
* @param g2d the graphics context to draw the object group onto
* @param og the ObjectGroup to be drawn
*/
protected abstract void paintObjectGroup(Graphics2D g2d, ObjectGroup og);
protected void paintEdge(Graphics2D g2d, MapLayer layer, int x, int y) {
/*
Polygon grid = createGridPolygon(x, y, 0);
PathIterator itr = grid.getPathIterator(null);
double nextPoint[] = new double[6];
double prevPoint[], firstPoint[];
Point p = screenToTileCoords(x, y);
int tx = p.x;
int ty = p.y;
itr.currentSegment(nextPoint);
firstPoint = prevPoint = nextPoint;
// Top
itr.next();
nextPoint = new double[6];
itr.currentSegment(nextPoint);
if (layer.getTileAt(tx, ty - 1) == null) {
g.drawLine(
(int)prevPoint[0], (int)prevPoint[1],
(int)nextPoint[0], (int)nextPoint[1]);
}
// Right
itr.next();
prevPoint = nextPoint;
nextPoint = new double[6];
itr.currentSegment(nextPoint);
if (layer.getTileAt(tx + 1, ty) == null) {
g.drawLine(
(int)prevPoint[0], (int)prevPoint[1],
(int)nextPoint[0], (int)nextPoint[1]);
}
// Left
itr.next();
prevPoint = nextPoint;
nextPoint = new double[6];
itr.currentSegment(nextPoint);
if (layer.getTileAt(tx, ty + 1) == null) {
g.drawLine(
(int)prevPoint[0], (int)prevPoint[1],
(int)nextPoint[0], (int)nextPoint[1]);
}
// Bottom
if (layer.getTileAt(tx - 1, ty) == null) {
g.drawLine(
(int)nextPoint[0], (int)nextPoint[1],
(int)firstPoint[0], (int)firstPoint[1]);
}
*/
}
/**
* Tells this view a certain region of the map needs to be repainted.
* <p>
* Same as calling repaint() unless implemented more efficiently in a
* subclass.
*
* @param region the region that has changed in tile coordinates
*/
public void repaintRegion(Rectangle region) {
repaint();
}
/**
* Draws the map grid.
*
* @param g2d the graphics context to draw the grid onto
*/
protected abstract void paintGrid(Graphics2D g2d);
/**
* Draws the coordinates on each tile.
*
* @param g2d the graphics context to draw the coordinates onto
*/
protected abstract void paintCoordinates(Graphics2D g2d);
protected abstract void paintPropertyFlags(Graphics2D g2d, TileLayer layer);
/**
* Returns a Polygon that matches the grid around the specified <b>Map</b>.
*
* @param tx
* @param ty
* @param border
* @return the created polygon
*/
protected abstract Polygon createGridPolygon(int tx, int ty, int border);
// Conversion functions
public abstract Point screenToTileCoords(int x, int y);
/**
* Returns the pixel coordinates on the map based on the given screen
* coordinates. The map pixel coordinates may be different in more ways
* than the zoom level, depending on the projection the view implements.
*
* @param x x in screen coordinates
* @param y y in screen coordinates
* @return the position in map pixel coordinates
*/
public abstract Point screenToPixelCoords(int x, int y);
/**
* Returns the location on the screen of the top corner of a tile.
*
* @param x
* @param y
* @return Point
*/
public abstract Point tileToScreenCoords(int x, int y);
}