/*
* Copyright (c) 2014 tabletoptool.com team.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Public License v3.0
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/gpl.html
*
* Contributors:
* rptools.com team - initial implementation
* tabletoptool.com team - further development
*/
package com.t3.client.tool.drawing;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.awt.geom.AffineTransform;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.SwingUtilities;
import com.t3.client.ScreenPoint;
import com.t3.client.TabletopTool;
import com.t3.client.tool.ToolHelper;
import com.t3.client.ui.zone.ZoneRenderer;
import com.t3.model.CellPoint;
import com.t3.model.ZonePoint;
import com.t3.model.drawing.AbstractTemplate;
import com.t3.model.drawing.DrawableColorPaint;
import com.t3.model.drawing.Pen;
import com.t3.model.drawing.RadiusTemplate;
import com.t3.swing.SwingUtil;
import com.t3.util.guidreference.ZoneReference;
/**
* Draw a template for an effect with a radius. Make the template show the squares that are effected, not just draw a
* circle. Let the player choose the vertex with the mouse and use the wheel to set the radius. This allows the user to
* move the entire template where it is to be used before placing it which is very important when casting a spell.
*
* @author jgorrell
* @version $Revision: 5945 $ $Date: 2013-06-02 21:05:50 +0200 (Sun, 02 Jun 2013) $ $Author: azhrei_fje $
*/
public class RadiusTemplateTool extends AbstractDrawingTool implements MouseMotionListener {
/*---------------------------------------------------------------------------------------------
* Instance Variables
*-------------------------------------------------------------------------------------------*/
/**
* The vertex that the effect is drawn on. It is the upper left corner of a specific grid location.
*/
protected AbstractTemplate template = createBaseTemplate();
/**
* This flag controls the painting of the template.
*/
protected boolean painting;
/**
* Has the anchoring point been set? When false, the anchor point is being placed. When true, the area of effect is
* being drawn on the display.
*/
protected boolean anchorSet;
/**
* The offset used to move the vertex when the control key is pressed. If this value is <code>null</code> then this
* would be the first time that the control key had been reported in the mouse event.
*/
protected ZonePoint controlOffset;
/*---------------------------------------------------------------------------------------------
* Class Variables
*-------------------------------------------------------------------------------------------*/
/**
* The width of the cursor. Since the cursor is a cross, this is the width of the horizontal bar and the height of
* the vertical bar. Always make it an odd number to keep it aligned on the grid properly.
*/
public static final int CURSOR_WIDTH = 25;
/*---------------------------------------------------------------------------------------------
* Constructor
*-------------------------------------------------------------------------------------------*/
/**
* Add the icon to the toggle button.
*/
public RadiusTemplateTool() {
try {
setIcon(new ImageIcon(ImageIO.read(getClass().getClassLoader().getResourceAsStream("com/t3/client/image/tool/temp-blue.png"))));
} catch (IOException ioe) {
TabletopTool.showError("Can't find image resource 'temp-blue.png'", ioe);
} // endtry
}
/*---------------------------------------------------------------------------------------------
* Instance Methods
*-------------------------------------------------------------------------------------------*/
/**
* Create the base template for the tool.
*
* @return The radius template that is to be drawn.
*/
protected AbstractTemplate createBaseTemplate() {
return new RadiusTemplate();
}
/**
* Calculate the cell at the mouse point. If it is different from the current point, make it the current point and
* repaint.
*
* @param e
* The event to be checked.
* @param point
* The current point.
* @return Flag indicating that the value changed.
*/
protected boolean setCellAtMouse(MouseEvent e, ZonePoint point) {
ZonePoint working = getCellAtMouse(e);
if (!working.equals(point)) {
point.x = working.x;
point.y = working.y;
renderer.repaint();
return true;
} // endif
return false;
}
/**
* Calculate the cell closest to a mouse point. Cell coordinates are the upper left corner of the cell.
*
* @param e
* The event to be checked.
* @return The cell at the mouse point in screen coordinates.
*/
protected ZonePoint getCellAtMouse(MouseEvent e) {
// Find the cell that the mouse is in.
ZonePoint mouse = new ScreenPoint(e.getX(), e.getY()).convertToZone(renderer);
CellPoint cp = renderer.getZone().getGrid().convert(mouse);
ZonePoint working = renderer.getZone().getGrid().convert(cp);
// If the mouse is over half way to the next vertex, move it there (both X & Y)
int grid = (int) (renderer.getZone().getGrid().getSize() * renderer.getScale());
if (mouse.x - working.x >= grid / 2)
working.x += renderer.getZone().getGrid().getSize();
if (mouse.y - working.y >= grid / 2)
working.y += renderer.getZone().getGrid().getSize();
return working;
}
/**
* Calculate the radius between two cells based on a mouse event.
*
* @param e
* Mouse event being checked
* @return The radius between the current mouse location and the vertex location.
*/
protected int getRadiusAtMouse(MouseEvent e) {
CellPoint workingCell = renderer.getZone().getGrid().convert(getCellAtMouse(e));
CellPoint vertexCell = renderer.getZone().getGrid().convert(template.getVertex());
int x = Math.abs(workingCell.x - vertexCell.x);
int y = Math.abs(workingCell.y - vertexCell.y);
return template.getDistance(x, y);
}
/**
* Paint a cursor
*
* @param g
* Where to paint.
* @param paint
* Data to draw the cursor
* @param thickness
* The thickness of the cursor.
* @param vertex
* The vertex holding the cursor.
*/
protected void paintCursor(Graphics2D g, Paint paint, float thickness, ZonePoint vertex) {
int halfCursor = CURSOR_WIDTH / 2;
g.setPaint(paint);
g.setStroke(new BasicStroke(thickness));
g.drawLine(vertex.x - halfCursor, vertex.y, vertex.x + halfCursor, vertex.y);
g.drawLine(vertex.x, vertex.y - halfCursor, vertex.x, vertex.y + halfCursor);
}
/**
* Get the pen set up to paint the overlay.
*
* @return The pen used to paint the overlay.
*/
protected Pen getPenForOverlay() {
// Get the pen and modify to only show a cursor and the boundary
Pen pen = getPen(); // new copy of pen, OK to modify
pen.setBackgroundMode(Pen.MODE_SOLID);
pen.setForegroundMode(Pen.MODE_SOLID);
pen.setThickness(3);
if (pen.isEraser()) {
pen.setEraser(false);
pen.setPaint(new DrawableColorPaint(Color.WHITE));
} // endif
return pen;
}
/**
* Paint the radius value in feet.
*
* @param g
* Where to paint.
* @param p
* Vertex where radius is painted.
*/
protected void paintRadius(Graphics2D g, ZonePoint p) {
if (template.getRadius() > 0 && anchorSet) {
ScreenPoint centerText = ScreenPoint.fromZonePoint(renderer, p);
centerText.translate(CURSOR_WIDTH, -CURSOR_WIDTH);
ToolHelper.drawMeasurement(g, template.getRadius() * renderer.getZone().getUnitsPerCell(), (int) centerText.x, (int) centerText.y);
} // endif
}
/**
* New instance of the template, at the passed vertex
*
* @param vertex
* The starting vertex for the new template or <code>null</code> if we should use the current template's
* vertex.
*/
protected void resetTool(ZonePoint vertex) {
anchorSet = false;
if (vertex == null) {
vertex = template.getVertex();
vertex = new ZonePoint(vertex.x, vertex.y); // Must create copy!
} // endif
template = createBaseTemplate();
template.setVertex(vertex);
template.setZone(renderer.getZone());
controlOffset = null;
renderer.repaint();
}
/**
* Handles setting the vertex when the control key is pressed during mouse movement. A change in the passed vertex
* causes the template to repaint the zone.
*
* @param e
* The mouse movement event.
* @param vertex
* The vertex being modified.
*/
protected void handleControlOffset(MouseEvent e, ZonePoint vertex) {
ZonePoint working = getCellAtMouse(e);
if (controlOffset == null) {
controlOffset = working;
controlOffset.x = working.x - vertex.x;
controlOffset.y = working.y - vertex.y;
} else {
working.x = working.x - controlOffset.x;
working.y = working.y - controlOffset.y;
if (!working.equals(vertex)) {
if (vertex == template.getVertex()) {
template.setVertex(working);
} else {
vertex.x = working.x;
vertex.y = working.y;
} // endif
renderer.repaint();
} // endif
} // endif
}
/**
* Set the radius on a mouse move after the anchor has been set.
*
* @param e
* Current mouse locations
*/
protected void setRadiusFromAnchor(MouseEvent e) {
template.setRadius(getRadiusAtMouse(e));
}
/**
* Handle mouse movement. Done here so that subclasses can still benefit from the code in DefaultTool w/o rewriting
* it.
*
* @param e
* Current mouse location
*/
protected void handleMouseMovement(MouseEvent e) {
// Set the anchor
ZonePoint vertex = template.getVertex();
if (!anchorSet) {
setCellAtMouse(e, vertex);
controlOffset = null;
// Move the anchor if control pressed.
} else if (SwingUtil.isControlDown(e)) {
handleControlOffset(e, vertex);
// Set the radius and repaint
} else {
setRadiusFromAnchor(e);
renderer.repaint();
controlOffset = null;
} // endif
}
/*---------------------------------------------------------------------------------------------
* DefaultTool Interface Methods
*-------------------------------------------------------------------------------------------*/
/**
* @see com.t3.client.tool.DefaultTool#mouseMoved(java.awt.event.MouseEvent)
*/
@Override
public void mouseMoved(MouseEvent e) {
super.mouseMoved(e);
handleMouseMovement(e);
}
/*---------------------------------------------------------------------------------------------
* Overridden AbstractDrawingTool Methods
*-------------------------------------------------------------------------------------------*/
/**
* @see com.t3.client.ui.zone.ZoneOverlay#paintOverlay(com.t3.client.ui.zone.ZoneRenderer,
* java.awt.Graphics2D)
*/
@Override
public void paintOverlay(ZoneRenderer renderer, Graphics2D g) {
if (painting && renderer != null) {
Pen pen = getPenForOverlay();
AffineTransform old = g.getTransform();
g.setTransform(getPaintTransform(renderer));
template.draw(g, pen);
Paint paint = pen.getPaint() != null ? pen.getPaint().getPaint() : null;
paintCursor(g, paint, pen.getThickness(), template.getVertex());
g.setTransform(old);
paintRadius(g, template.getVertex());
} // endif
}
/**
* New instance of the template, at the current vertex
*
* @see com.t3.client.ui.Tool#resetTool()
*/
@Override
protected void resetTool() {
if (!anchorSet) {
super.resetTool();
return;
}
resetTool(null);
}
/**
* It is OK to modify the pen returned by this method
*
* @see com.t3.client.tool.drawing.AbstractDrawingTool#getPen()
*/
@Override
protected Pen getPen() {
// Just paint the foreground
Pen pen = super.getPen();
pen.setBackgroundMode(Pen.MODE_SOLID);
return pen;
}
/**
* @see com.t3.client.ui.Tool#detachFrom(com.t3.client.ui.zone.ZoneRenderer)
*/
@Override
protected void detachFrom(ZoneRenderer renderer) {
super.detachFrom(renderer);
template.setZone(null);
renderer.repaint();
}
/**
* @see com.t3.client.ui.Tool#attachTo(com.t3.client.ui.zone.ZoneRenderer)
*/
@Override
protected void attachTo(ZoneRenderer renderer) {
template.setZone(renderer.getZone());
renderer.repaint();
super.attachTo(renderer);
}
/**
* @see com.t3.client.ui.Tool#getTooltip()
*/
@Override
public String getTooltip() {
return "tool.radiustemplate.tooltip";
}
/**
* @see com.t3.client.ui.Tool#getInstructions()
*/
@Override
public String getInstructions() {
return "tool.radiustemplate.instructions";
}
/*---------------------------------------------------------------------------------------------
* MouseListener Interface Methods
*-------------------------------------------------------------------------------------------*/
/**
* @see java.awt.event.MouseListener#mousePressed(java.awt.event.MouseEvent)
*/
@Override
public void mousePressed(MouseEvent e) {
super.mousePressed(e);
if (!painting)
return;
if (SwingUtilities.isLeftMouseButton(e)) {
// Need to set the anchor?
controlOffset = null;
if (!anchorSet) {
anchorSet = true;
return;
} // endif
// Need to finish the radius?
if (!painting || template.getRadius() < AbstractTemplate.MIN_RADIUS)
return;
// Set the eraser, set the drawable, reset the tool.
setIsEraser(isEraser(e));
template.setRadius(getRadiusAtMouse(e));
ZonePoint vertex = template.getVertex();
ZonePoint newPoint = new ZonePoint(vertex.x, vertex.y);
completeDrawable(new ZoneReference(renderer.getZone()), getPen(), template);
setIsEraser(false);
resetTool(newPoint);
}
}
/**
* @see java.awt.event.MouseListener#mouseEntered(java.awt.event.MouseEvent)
*/
@Override
public void mouseEntered(MouseEvent e) {
super.mouseEntered(e);
painting = true;
renderer.repaint();
}
/**
* @see java.awt.event.MouseListener#mouseExited(java.awt.event.MouseEvent)
*/
@Override
public void mouseExited(MouseEvent e) {
super.mouseExited(e);
painting = false;
renderer.repaint();
}
}