/*
* $Id$
*
* Copyright (c) 2004 by Rodney Kinney
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License (LGPL) as published by the Free Software Foundation.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, copies are available
* at http://www.opensource.org.
*/
package VASSAL.counters;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import VASSAL.build.module.documentation.HelpFile;
import VASSAL.build.module.map.MovementReporter;
import VASSAL.command.ChangeTracker;
import VASSAL.command.Command;
import VASSAL.command.MoveTracker;
import VASSAL.configure.BooleanConfigurer;
import VASSAL.configure.DoubleConfigurer;
import VASSAL.configure.IntConfigurer;
import VASSAL.configure.NamedHotKeyConfigurer;
import VASSAL.configure.StringConfigurer;
import VASSAL.i18n.PieceI18nData;
import VASSAL.i18n.TranslatablePiece;
import VASSAL.tools.NamedKeyStroke;
import VASSAL.tools.SequenceEncoder;
/**
* Provides commands to pivot a Game Piece around a given point
*/
public class Pivot extends Decorator implements TranslatablePiece {
public static final String ID = "pivot;";
public static final String DEGREES = "_Degrees";
protected int pivotX;
protected int pivotY;
protected double angle;
protected String command;
protected NamedKeyStroke key;
protected boolean fixedAngle;
protected KeyCommand[] commands;
protected KeyCommand pivotCommand;
protected FreeRotator rotator;
public Pivot() {
this(ID, null);
}
public Pivot(String type, GamePiece inner) {
mySetType(type);
setInner(inner);
}
public String getDescription() {
return "Can Pivot";
}
public HelpFile getHelpFile() {
return HelpFile.getReferenceManualPage("Pivot.htm");
}
public void mySetType(String type) {
type = type.substring(ID.length());
SequenceEncoder.Decoder st = new SequenceEncoder.Decoder(type, ';');
command = st.nextToken("Pivot");
key = st.nextNamedKeyStroke(null);
pivotX = st.nextInt(0);
pivotY = st.nextInt(0);
fixedAngle = st.nextBoolean(true);
angle = st.nextDouble(90.0);
commands = null;
}
protected KeyCommand[] myGetKeyCommands() {
if (commands == null) {
pivotCommand = new KeyCommand(command, key, Decorator.getOutermost(this), this);
if (command.length() > 0 && key != null && !key.isNull()) {
commands = new KeyCommand[]{pivotCommand};
}
else {
commands = new KeyCommand[0];
}
rotator = (FreeRotator) Decorator.getDecorator(this, FreeRotator.class);
pivotCommand.setEnabled(rotator != null);
}
return commands;
}
public String myGetState() {
return "";
}
public String myGetType() {
SequenceEncoder se = new SequenceEncoder(';');
se.append(command)
.append(key)
.append(pivotX)
.append(pivotY)
.append(fixedAngle)
.append(angle);
return ID + se.getValue();
}
public Command myKeyEvent(KeyStroke stroke) {
myGetKeyCommands();
Command c = null;
if (pivotCommand.matches(stroke)) {
if (fixedAngle) {
ChangeTracker t = new ChangeTracker(this);
double oldAngle = rotator.getAngle();
rotator.setAngle(oldAngle - angle);
double newAngle = rotator.getAngle();
if (getMap() != null) {
setOldProperties();
Point pos = getPosition();
pivotPoint(pos, -Math.PI * oldAngle / 180.0, -Math.PI * newAngle / 180.0);
GamePiece outer = Decorator.getOutermost(this);
if (!Boolean.TRUE.equals(outer.getProperty(Properties.IGNORE_GRID))) {
pos = getMap().snapTo(pos);
}
outer.setProperty(Properties.MOVED, Boolean.TRUE);
c = t.getChangeCommand();
MoveTracker moveTracker = new MoveTracker(outer);
getMap().placeOrMerge(outer, pos);
c = c.append(moveTracker.getMoveCommand());
MovementReporter r = new MovementReporter(c);
Command reportCommand = r.getReportCommand();
if (reportCommand != null) {
reportCommand.execute();
}
c = c.append(reportCommand);
c = c.append(r.markMovedPieces());
getMap().ensureVisible(getMap().selectionBoundsOf(outer));
}
else {
c = t.getChangeCommand();
}
}
else if (getMap() != null) {
setOldProperties();
final double oldAngle = rotator.getAngleInRadians();
Point2D pivot2D = new Point2D.Double(pivotX, pivotY);
AffineTransform t = AffineTransform.getRotateInstance(oldAngle);
t.transform(pivot2D, pivot2D);
rotator.beginInteractiveRotate();
rotator.setPivot(getPosition().x + (int) Math.round(pivot2D.getX()),
getPosition().y + (int) Math.round(pivot2D.getY()));
}
}
// Apply map auto-move key
if (c != null && getMap() != null && getMap().getMoveKey()!= null) {
c.append(Decorator.getOutermost(this).keyEvent(getMap().getMoveKey()));
}
return c;
}
/**
* Pivot the given point around the pivot point from oldAngle to newAngle
* @param oldAngle
* @param newAngle
*/
private void pivotPoint(Point p, double oldAngle, double newAngle) {
Point2D pivot2D = new Point2D.Double(pivotX, pivotY);
AffineTransform t = AffineTransform.getRotateInstance(oldAngle);
t.transform(pivot2D, pivot2D);
t = AffineTransform.getRotateInstance(newAngle - oldAngle, pivot2D.getX(), pivot2D.getY());
Point2D newPos2D = new Point2D.Float(0, 0);
t.transform(newPos2D, newPos2D);
p.x += (int) Math.round(newPos2D.getX());
p.y += (int) Math.round(newPos2D.getY());
}
public void mySetState(String newState) {
}
public Rectangle boundingBox() {
return getInner().boundingBox();
}
public void draw(Graphics g, int x, int y, Component obs, double zoom) {
getInner().draw(g, x, y, obs, zoom);
}
public String getName() {
return getInner().getName();
}
public Shape getShape() {
return getInner().getShape();
}
public PieceEditor getEditor() {
return new Ed(this);
}
public PieceI18nData getI18nData() {
return getI18nData(command, "Pivot command");
}
public static class Ed implements PieceEditor {
private StringConfigurer command;
private NamedHotKeyConfigurer key;
private IntConfigurer xOff, yOff;
private DoubleConfigurer angle;
private BooleanConfigurer fixedAngle;
private JPanel controls;
public Ed(Pivot p) {
controls = new JPanel();
controls.setLayout(new BoxLayout(controls, BoxLayout.Y_AXIS));
command = new StringConfigurer(null, "Command: ", p.command);
controls.add(command.getControls());
key = new NamedHotKeyConfigurer(null, "Keyboard command: ", p.key);
controls.add(key.getControls());
Box b = Box.createHorizontalBox();
xOff = new IntConfigurer(null, "Pivot point: ", p.pivotX);
b.add(xOff.getControls());
yOff = new IntConfigurer(null, ", ", p.pivotY);
b.add(yOff.getControls());
controls.add(b);
fixedAngle = new BooleanConfigurer(null, "Pivot through fixed angle?",
Boolean.valueOf(p.fixedAngle));
controls.add(fixedAngle.getControls());
angle = new DoubleConfigurer(null, "Angle: ", p.angle);
controls.add(angle.getControls());
angle.getControls().setVisible(p.fixedAngle);
fixedAngle.addPropertyChangeListener(new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent evt) {
angle.getControls().setVisible(Boolean.TRUE.equals(fixedAngle.getValue()));
}
});
}
public Component getControls() {
return controls;
}
public String getState() {
return "";
}
public String getType() {
SequenceEncoder se = new SequenceEncoder(';');
se.append(command.getValueString())
.append(key.getValueString())
.append(xOff.getValueString())
.append(yOff.getValueString())
.append(Boolean.TRUE.equals(fixedAngle.getValue()))
.append(angle.getValueString());
return ID + se.getValue();
}
}
}