/*
* $Id$
*
* Copyright (c) 2000-2012 by Rodney Kinney, Brent Easton
*
* 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.Point;
import java.awt.Window;
import java.util.ArrayList;
import java.util.List;
import java.util.NoSuchElementException;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import VASSAL.build.BadDataReport;
import VASSAL.build.module.Map;
import VASSAL.build.module.map.boardPicker.Board;
import VASSAL.build.module.map.boardPicker.board.mapgrid.Zone;
import VASSAL.build.module.properties.PropertyNameSource;
import VASSAL.command.Command;
import VASSAL.i18n.Localization;
import VASSAL.i18n.PieceI18nData;
import VASSAL.i18n.TranslatablePiece;
import VASSAL.tools.ArrayUtils;
import VASSAL.tools.ErrorDialog;
import VASSAL.tools.SequenceEncoder;
/**
* The abstract class describing a generic 'trait' of a GamePiece. Follows the Decorator design pattern
* of wrapping around another instance of GamePiece (the 'inner' piece) and delegating some of the GamePiece methods to it
*/
public abstract class Decorator implements GamePiece, StateMergeable, PropertyNameSource {
protected GamePiece piece;
private Decorator dec;
private boolean selected = false;
public Decorator() {
}
/** Set the inner GamePiece */
public void setInner(GamePiece p) {
piece = p;
if (p != null) {
p.setProperty(Properties.OUTER, this);
}
}
public void setMap(Map m) {
piece.setMap(m);
}
/**
* @return the piece decorated by this Decorator
*/
public GamePiece getInner() {
return piece;
}
public Map getMap() {
return piece.getMap();
}
public void setParent(Stack s) {
piece.setParent(s);
}
public Stack getParent() {
return piece.getParent();
}
public Object getProperty(Object key) {
if (Properties.KEY_COMMANDS.equals(key)) {
return getKeyCommands();
}
else if (Properties.INNER.equals(key)) {
return piece;
}
else if (Properties.OUTER.equals(key)) {
return dec;
}
else if (Properties.VISIBLE_STATE.equals(key)) {
return myGetState()+piece.getProperty(key);
}
else if (Properties.SELECTED.equals(key)) {
return selected;
}
else {
return piece.getProperty(key);
}
}
public Object getLocalizedProperty(Object key) {
if (Properties.KEY_COMMANDS.equals(key)) {
return getProperty(key);
}
else if (Properties.INNER.equals(key)) {
return getProperty(key);
}
else if (Properties.OUTER.equals(key)) {
return getProperty(key);
}
else if (Properties.VISIBLE_STATE.equals(key)) {
return getProperty(key);
}
/**
* Return local cached copy of Selection Status
*/
else if (Properties.SELECTED.equals(key)) {
return isSelected();
}
else {
return piece.getLocalizedProperty(key);
}
}
public void setProperty(Object key, Object val) {
if (Properties.INNER.equals(key)) {
setInner((GamePiece) val);
}
else if (Properties.OUTER.equals(key)) {
dec = (Decorator) val;
}
/**
* Cache Selection status and pass it on to all inner traits.
*/
else if (Properties.SELECTED.equals(key)) {
if (val instanceof Boolean) {
setSelected(((Boolean) val).booleanValue());
}
else {
setSelected(false);
}
piece.setProperty(key, val);
}
else {
piece.setProperty(key, val);
}
}
/*
* getOuter() required by Obscurable to handle masking of getProperty calls
*/
public Decorator getOuter() {
return dec;
}
public void setPosition(Point p) {
piece.setPosition(p);
}
public Point getPosition() {
return piece.getPosition();
}
/** Set just the state of this trait
* @see #myGetState
*/
public abstract void mySetState(String newState);
/**
* Extract the string describing this trait's state and forward the remaining string to the inner piece
* @param newState the new state of this trait and all inner pieces
*/
public void setState(String newState) {
SequenceEncoder.Decoder st = new SequenceEncoder.Decoder(newState, '\t');
mySetState(st.nextToken());
try {
piece.setState(st.nextToken());
}
catch (NoSuchElementException e) {
throw new IllegalStateException("No state for Decorator=" + myGetType());
}
}
/**
* Compute the difference between <code>newState</code> and <code>oldState</code>
* and appy that difference to the current state
* @param newState
* @param oldState
*/
public void mergeState(String newState, String oldState) {
SequenceEncoder.Decoder stNew = new SequenceEncoder.Decoder(newState, '\t');
String myNewState = stNew.nextToken();
String innerNewState = stNew.nextToken();
SequenceEncoder.Decoder stOld = new SequenceEncoder.Decoder(oldState, '\t');
String myOldState = stOld.nextToken();
String innerOldState = stOld.nextToken();
if (!myOldState.equals(myNewState)) {
mySetState(myNewState);
}
if (piece instanceof StateMergeable) {
((StateMergeable)piece).mergeState(innerNewState,innerOldState);
}
else {
piece.setState(innerNewState);
}
}
/**
*
* @return the state of this trait alone
* @see #getState
*/
public abstract String myGetState();
/** The state of a Decorator is a composition of {@link #myGetState} and the inner piece's state */
public String getState() {
SequenceEncoder se = new SequenceEncoder(myGetState(), '\t');
se.append(piece.getState());
return se.getValue();
}
/**
*
* @return the type of this trait alone
* @see #getType
*/
public abstract String myGetType();
/**
* The type of a Decorator is a composition of {@link #myGetType} and the type of its inner piece
* @return the combined type of this trait and its inner piece
*/
public String getType() {
SequenceEncoder se = new SequenceEncoder(myGetType(), '\t');
se.append(piece.getType());
return se.getValue();
}
/**
*
* @return the commands for this trait alone
* @see #getKeyCommands
*/
protected abstract KeyCommand[] myGetKeyCommands();
/**
* The set of key commands that will populate the piece's right-click menu.
* The key commands are accessible through the {@link Properties#KEY_COMMANDS} property.
* The commands for a Decorator are a composite of {@link #myGetKeyCommands} and the
* commands of its inner piece.
* @return the commands for this piece and its inner piece
*/
protected KeyCommand[] getKeyCommands() {
final KeyCommand myC[] = myGetKeyCommands();
final KeyCommand c[] =
(KeyCommand[]) piece.getProperty(Properties.KEY_COMMANDS);
if (c == null) return myC;
else if (myC == null) return c;
else return ArrayUtils.append(KeyCommand[].class, myC, c);
}
/**
* The response of this trait alone to the given KeyStroke
* @param stroke
* @return null if no effect
* @see #keyEvent
*/
public abstract Command myKeyEvent(KeyStroke stroke);
/**
* Append the command returned by {@link #myKeyEvent} with the command returned
* by the inner piece's {@link GamePiece#keyEvent} method.
* @param stroke
* @return
*/
public Command keyEvent(KeyStroke stroke) {
Command c = myKeyEvent(stroke);
return c == null ? piece.keyEvent(stroke)
: c.append(piece.keyEvent(stroke));
}
public String getId() {
return piece.getId();
}
public void setId(String id) {
piece.setId(id);
}
/**
* @param p
* @return the outermost Decorator instance of this piece, i.e. the entire piece with all traits
*/
public static GamePiece getOutermost(GamePiece p) {
while (p.getProperty(Properties.OUTER) != null) {
p = (GamePiece) p.getProperty(Properties.OUTER);
}
return p;
}
/**
*
* @param p
* @return the innermost GamePiece of this piece. In most cases, an instance of {@link BasicPiece}
*/
public static GamePiece getInnermost(GamePiece p) {
while (p instanceof Decorator) {
p = ((Decorator) p).piece;
}
return p;
}
/**
* @return the first Decorator within the given GamePiece
* that is an instance of the given Class
*/
public static GamePiece getDecorator(GamePiece p, Class<?> type) {
while (p instanceof Decorator) {
if (type.isInstance(p)) {
return p;
}
p = ((Decorator) p).piece;
}
return null;
}
public PieceEditor getEditor() {
return new SimplePieceEditor(this);
}
public String toString() {
if (piece == null) {
return super.toString();
}
else {
return super.toString()+"[name="+getName()+",type="+getType()+",state="+getState()+"]";
}
}
/**
* Return the translated name for this piece. Most pieces do not have
* translatable elements, so just return the standard name
*/
public String getLocalizedName() {
return piece.getLocalizedName();
}
/**
* Return I18n data for this piece
* @return
*/
public PieceI18nData getI18nData() {
return new PieceI18nData(this);
}
protected PieceI18nData getI18nData(String command, String description) {
PieceI18nData data = new PieceI18nData(this);
data.add(command, description);
return data;
}
protected PieceI18nData getI18nData(String[] commands, String[] descriptions) {
PieceI18nData data = new PieceI18nData(this);
for (int i = 0; i < commands.length; i++) {
data.add(commands[i], descriptions[i]);
}
return data;
}
protected String getCommandDescription(String description, String command) {
String s = "";
if (description != null && description.length() > 0) {
s += description + ": ";
}
return s + command;
}
protected String getTranslation(String key) {
String fullKey = TranslatablePiece.PREFIX + key;
return Localization.getInstance().translate(fullKey, key);
}
/**
* Report a Data Error detected by a trait
*/
protected static void reportDataError(EditablePiece piece, String message, String data, Throwable e) {
ErrorDialog.dataError(new BadDataReport(piece, message, data, e));
}
protected static void reportDataError(EditablePiece piece, String message, String data) {
ErrorDialog.dataError(new BadDataReport(piece, message, data));
}
protected static void reportDataError(EditablePiece piece, String message) {
ErrorDialog.dataError(new BadDataReport(piece, message));
}
/**
* Default Property Name Source
*/
public List<String> getPropertyNames() {
return new ArrayList<String>(0);
}
/**
* Set the Oldxxxx properties related to movement
* @param p
*/
public static void setOldProperties(GamePiece p) {
String mapName = ""; //$NON-NLS-1$
String boardName = ""; //$NON-NLS-1$
String zoneName = ""; //$NON-NLS-1$
String locationName = ""; //$NON-NLS-1$
final Map m = p.getMap();
final Point pos = p.getPosition();
if (m != null) {
mapName = m.getConfigureName();
final Board b = m.findBoard(pos);
if (b != null) {
boardName = b.getName();
}
final Zone z = m.findZone(pos);
if (z != null) {
zoneName = z.getName();
}
locationName = m.locationName(pos);
}
p.setProperty(BasicPiece.OLD_X, String.valueOf(pos.x));
p.setProperty(BasicPiece.OLD_Y, String.valueOf(pos.y));
p.setProperty(BasicPiece.OLD_MAP, mapName);
p.setProperty(BasicPiece.OLD_BOARD, boardName);
p.setProperty(BasicPiece.OLD_ZONE, zoneName);
p.setProperty(BasicPiece.OLD_LOCATION_NAME, locationName);
}
public void setOldProperties() {
setOldProperties(this);
}
/**
*
* Utility method to allow Decorator Editors to repack themselves. c must be one of the
* components that make up the Decorator's controls.
*/
public static void repack(Component c) {
final Window w = SwingUtilities.getWindowAncestor(c);
if (w != null) {
w.pack();
}
}
/**
* Support Selection status locally
*/
protected void setSelected(boolean b) {
selected = b;
}
protected boolean isSelected() {
return selected;
}
}