/*
* $Id$
*
* Copyright (c) 2000-2012 by Brent Easton, 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.Font;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.InputEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.geom.Area;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.Box;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import net.miginfocom.swing.MigLayout;
import VASSAL.build.GameModule;
import VASSAL.build.module.documentation.HelpFile;
import VASSAL.command.ChangeTracker;
import VASSAL.command.Command;
import VASSAL.configure.BooleanConfigurer;
import VASSAL.configure.FormattedExpressionConfigurer;
import VASSAL.configure.IntConfigurer;
import VASSAL.configure.NamedHotKeyConfigurer;
import VASSAL.configure.PropertyNameExpressionConfigurer;
import VASSAL.configure.StringConfigurer;
import VASSAL.i18n.PieceI18nData;
import VASSAL.i18n.Resources;
import VASSAL.i18n.TranslatablePiece;
import VASSAL.script.expression.Expression;
import VASSAL.script.expression.ExpressionException;
import VASSAL.tools.FormattedString;
import VASSAL.tools.NamedKeyStroke;
import VASSAL.tools.SequenceEncoder;
import VASSAL.tools.icon.IconFactory;
import VASSAL.tools.icon.IconFamily;
import VASSAL.tools.image.ImageUtils;
import VASSAL.tools.imageop.ImageOp;
import VASSAL.tools.imageop.ScaledImagePainter;
/**
* The "Layer" trait. Contains a list of images that the user may cycle through.
* The current image is superimposed over the inner piece. The entire layer may
* be activated or deactivated.
*
* Changes to support NamedKeyStrokes:
* - Random and reset command changed directly to Name Key Strokes.
* - Disentangle alwaysActive flag from length of activateKey field. Make a
* separate field and save in type
* - Add a Version field to type to enable conversion of Activate/Increase/Decrease
* commands. Note commands with more than 1 target keycode cannot be converted
* - Simplify code. Removed Version 0 (3.1) code to a separate class Embellishment0. The BasicCommandEncoder
* replaces this class with an Embellishment0 if Embellishment(type, inner) returns
* a version 0 Embellishment trait.
*/
public class Embellishment extends Decorator implements TranslatablePiece {
public static final String OLD_ID = "emb;";
public static final String ID = "emb2;"; // New type encoding
public static final String IMAGE = "_Image";
public static final String NAME = "_Name";
public static final String LEVEL = "_Level";
public static final String ACTIVE = "_Active";
protected String activateKey = "";
protected String upKey, downKey;
protected int activateModifiers, upModifiers, downModifiers;
protected String upCommand, downCommand, activateCommand;
protected String resetCommand;
protected FormattedString resetLevel = new FormattedString("1");
protected boolean loopLevels;
protected NamedKeyStroke resetKey;
protected boolean followProperty;
protected String propertyName = "";
protected Expression followPropertyExpression;
protected int firstLevelValue;
// random layers
// protected KeyCommand rndCommand;
protected NamedKeyStroke rndKey;
private String rndText = "";
// end random layers
// Index of the image to draw. Negative if inactive. 0 is not a valid value.
protected int value = -1;
protected int nValues;
protected int xOff, yOff;
protected String imageName[];
protected String commonName[];
protected Rectangle size[];
protected ScaledImagePainter imagePainter[];
protected boolean drawUnderneathWhenSelected = false;
protected String name = "";
protected KeyCommand[] commands;
protected KeyCommand up = null;
protected KeyCommand down = null;
// Shape cache
protected Rectangle lastBounds = null;
protected Area lastShape = null;
// Version control
// Version 0 = Original multi-keystroke support for Activate/Increase/Decrease
// Version 1 = NamedKeyStrokes for Activate/Increase/Decrease
public static final int BASE_VERSION = 0;
public static final int CURRENT_VERSION = 1;
protected int version;
// NamedKeyStroke support
protected boolean alwaysActive;
protected NamedKeyStroke activateKeyStroke;
protected NamedKeyStroke increaseKeyStroke;
protected NamedKeyStroke decreaseKeyStroke;
public Embellishment() {
this(ID + "Activate", null);
}
public Embellishment(String type, GamePiece d) {
mySetType(type);
setInner(d);
}
public boolean isActive() {
return value > 0;
}
public void setActive(boolean val) {
value = val ? Math.abs(value) : -Math.abs(value);
}
public int getValue() {
return Math.abs(value) - 1;
}
/**
* Set the current level - First level = 0 Does not change the active status
*
* @param val
*/
public void setValue(int val) {
int theVal = val;
if (val >= nValues) {
reportDataError(this, Resources.getString("Error.bad_layer"), "Layer="+val);
theVal = nValues;
}
value = value > 0 ? theVal + 1 : -theVal - 1;
}
public void mySetType(String s) {
if (!s.startsWith(ID)) {
originalSetType(s);
}
else {
s = s.substring(ID.length());
SequenceEncoder.Decoder st = new SequenceEncoder.Decoder(s, ';');
activateCommand = st.nextToken("");
activateModifiers = st.nextInt(InputEvent.CTRL_MASK);
activateKey = st.nextToken("A");
upCommand = st.nextToken("");
upModifiers = st.nextInt(InputEvent.CTRL_MASK);
upKey = st.nextToken("");
downCommand = st.nextToken("");
downModifiers = st.nextInt(InputEvent.CTRL_MASK);
downKey = st.nextToken("");
resetCommand = st.nextToken("");
resetKey = st.nextNamedKeyStroke();
resetLevel = new FormattedString(st.nextToken("1"));
drawUnderneathWhenSelected = st.nextBoolean(false);
xOff = st.nextInt(0);
yOff = st.nextInt(0);
imageName = st.nextStringArray(0);
commonName = st.nextStringArray(imageName.length);
loopLevels = st.nextBoolean(true);
name = st.nextToken("");
// random layers
rndKey = st.nextNamedKeyStroke(null);
rndText = st.nextToken("");
// end random layers
// Follow property value
followProperty = st.nextBoolean(false);
propertyName = st.nextToken("");
firstLevelValue = st.nextInt(1);
version = st.nextInt(0);
alwaysActive = st.nextBoolean(false);
activateKeyStroke = st.nextNamedKeyStroke();
increaseKeyStroke = st.nextNamedKeyStroke();
decreaseKeyStroke = st.nextNamedKeyStroke();
// Conversion?
if (version == BASE_VERSION) {
alwaysActive = activateKey.length() == 0;
// Cannot convert if activate, up or down has more than 1 char specified
if (activateKey.length() <= 1 && upKey.length() <= 1 && downKey.length() <= 1) {
if (activateKey.length() == 0) {
activateKeyStroke = NamedKeyStroke.NULL_KEYSTROKE;
}
else {
activateKeyStroke = new NamedKeyStroke(activateKey.charAt(0), activateModifiers);
}
if (upKey.length() == 0) {
increaseKeyStroke = NamedKeyStroke.NULL_KEYSTROKE;
}
else {
increaseKeyStroke = new NamedKeyStroke(upKey.charAt(0), upModifiers);
}
if (downKey.length() == 0) {
decreaseKeyStroke = NamedKeyStroke.NULL_KEYSTROKE;
}
else {
decreaseKeyStroke = new NamedKeyStroke(downKey.charAt(0), downModifiers);
}
version = CURRENT_VERSION;
}
}
value = activateKey.length() > 0 ? -1 : 1;
nValues = imageName.length;
size = new Rectangle[imageName.length];
imagePainter = new ScaledImagePainter[imageName.length];
for (int i = 0; i < imageName.length; ++i) {
imagePainter[i] = new ScaledImagePainter();
imagePainter[i].setImageName(imageName[i]);
}
}
commands = null;
}
/**
* This original way of representing the type causes problems because it's not
* extensible
*
* @param s
*/
private void originalSetType(String s) {
SequenceEncoder.Decoder st = new SequenceEncoder.Decoder(s, ';');
st.nextToken();
final SequenceEncoder.Decoder st2 =
new SequenceEncoder.Decoder(st.nextToken(), ';');
activateKey = st2.nextToken().toUpperCase();
if (activateKey != null && activateKey.length() > 0) {
activateKeyStroke = new NamedKeyStroke(KeyStroke.getKeyStroke(activateKey));
}
activateModifiers = InputEvent.CTRL_MASK;
if (st2.hasMoreTokens()) {
resetCommand = st2.nextToken();
resetKey = st2.nextNamedKeyStroke(null);
resetLevel.setFormat(st2.nextToken("0"));
}
else {
resetKey = null;
resetCommand = "";
resetLevel.setFormat("0");
}
activateCommand = st.nextToken();
drawUnderneathWhenSelected = activateCommand.startsWith("_");
if (drawUnderneathWhenSelected) {
activateCommand = activateCommand.substring(1);
}
value = activateKey.length() > 0 ? -1 : 1;
upKey = st.nextToken().toUpperCase();
upCommand = st.nextToken();
upModifiers = InputEvent.CTRL_MASK;
downKey = st.nextToken().toUpperCase();
downCommand = st.nextToken();
downModifiers = InputEvent.CTRL_MASK;
xOff = st.nextInt(0);
yOff = st.nextInt(0);
final ArrayList<String> l = new ArrayList<String>();
while (st.hasMoreTokens()) {
l.add(st.nextToken());
}
nValues = l.size();
imageName = new String[l.size()];
commonName = new String[l.size()];
size = new Rectangle[imageName.length];
imagePainter = new ScaledImagePainter[imageName.length];
for (int i = 0; i < imageName.length; ++i) {
final String sub = l.get(i);
final SequenceEncoder.Decoder subSt =
new SequenceEncoder.Decoder(sub, ',');
imageName[i] = subSt.nextToken();
imagePainter[i] = new ScaledImagePainter();
imagePainter[i].setImageName(imageName[i]);
if (subSt.hasMoreTokens()) {
commonName[i] = subSt.nextToken();
}
}
loopLevels = true;
alwaysActive = activateKey.length() == 0;
if (activateKey.length() == 0) {
activateKeyStroke = NamedKeyStroke.NULL_KEYSTROKE;
}
else {
activateKeyStroke = new NamedKeyStroke(activateKey.charAt(0), activateModifiers);
}
if (upKey.length() == 0) {
increaseKeyStroke = NamedKeyStroke.NULL_KEYSTROKE;
}
else {
increaseKeyStroke = new NamedKeyStroke(upKey.charAt(0), upModifiers);
}
if (downKey.length() == 0) {
decreaseKeyStroke = NamedKeyStroke.NULL_KEYSTROKE;
}
else {
decreaseKeyStroke = new NamedKeyStroke(downKey.charAt(0), downModifiers);
}
version = CURRENT_VERSION;
}
public String getLocalizedName() {
return getName(true);
}
public String getName() {
return getName(false);
}
public String getName(boolean localized) {
checkPropertyLevel(); // Name Change?
String name = null;
final String cname = 0 < value && value - 1 < commonName.length ?
getCommonName(localized, value - 1) : null;
if (cname != null && cname.length() > 0) {
final SequenceEncoder.Decoder st =
new SequenceEncoder.Decoder(cname, '+');
final String first = st.nextToken();
if (st.hasMoreTokens()) {
final String second = st.nextToken();
if (first.length() == 0) {
name = (localized ? piece.getLocalizedName() : piece.getName()) + second;
}
else {
name = first + (localized ? piece.getLocalizedName() : piece.getName());
}
}
else {
name = first;
}
}
else {
name = (localized ? piece.getLocalizedName() : piece.getName());
}
return name;
}
/**
* Return raw Embellishment name
* @return Embellishment name
*/
public String getLayerName() {
return name == null ? "" : name;
}
public void mySetState(String s) {
final SequenceEncoder.Decoder st = new SequenceEncoder.Decoder(s, ';');
value = st.nextInt(-1);
}
public String myGetType() {
final SequenceEncoder se = new SequenceEncoder(';');
se.append(activateCommand)
.append(activateModifiers)
.append(activateKey)
.append(upCommand)
.append(upModifiers)
.append(upKey)
.append(downCommand)
.append(downModifiers)
.append(downKey)
.append(resetCommand)
.append(resetKey)
.append(resetLevel.getFormat())
.append(drawUnderneathWhenSelected)
.append(xOff)
.append(yOff)
.append(imageName)
.append(commonName)
.append(loopLevels)
.append(name)
.append(rndKey) // random layers
.append(rndText) // random layers
.append(followProperty)
.append(propertyName)
.append(firstLevelValue)
.append(version)
.append(alwaysActive)
.append(activateKeyStroke)
.append(increaseKeyStroke)
.append(decreaseKeyStroke);
return ID + se.getValue();
}
@Deprecated
public String oldGetType() {
final SequenceEncoder se = new SequenceEncoder(';');
final SequenceEncoder se2 = new SequenceEncoder(activateKey, ';');
se2.append(resetCommand)
.append(resetKey)
.append(String.valueOf(resetLevel));
se.append(se2.getValue())
.append(drawUnderneathWhenSelected ?
"_" + activateCommand : activateCommand)
.append(upKey)
.append(upCommand)
.append(downKey)
.append(downCommand)
.append(xOff)
.append(yOff);
for (int i = 0; i < nValues; ++i) {
if (commonName[i] != null) {
SequenceEncoder sub = new SequenceEncoder(imageName[i], ',');
se.append(sub.append(commonName[i]).getValue());
}
else {
se.append(imageName[i]);
}
}
return ID + se.getValue();
}
public String myGetState() {
final SequenceEncoder se = new SequenceEncoder(';');
/*
* Fix for Bug 9700 is to strip back the encoding of State to the simplest case.
* Both Activation status and level is determined by the value parameter.
*/
return se.append(String.valueOf(value)).getValue();
}
public void draw(Graphics g, int x, int y, Component obs, double zoom) {
final boolean drawUnder = drawUnderneathWhenSelected && Boolean.TRUE.equals(getProperty(Properties.SELECTED));
if (!drawUnder) {
piece.draw(g, x, y, obs, zoom);
}
checkPropertyLevel();
if (!isActive()) {
if (drawUnder) {
piece.draw(g, x, y, obs, zoom);
}
return;
}
final int i = value - 1;
if (i < imagePainter.length && imagePainter[i] != null) {
final Rectangle r = getCurrentImageBounds();
imagePainter[i].draw(g, x + (int)(zoom*r.x), y + (int)(zoom*r.y), zoom, obs);
}
if (drawUnder) {
piece.draw(g, x, y, obs, zoom);
}
}
/*
* Calculate the new level to display based on a property?
*/
protected void checkPropertyLevel() {
if (!followProperty || propertyName.length() == 0) return;
if (followPropertyExpression == null) {
followPropertyExpression = Expression.createSimplePropertyExpression(propertyName);
}
String val = "";
try {
val = followPropertyExpression.evaluate(Decorator.getOutermost(this));
if (val == null || val.length() == 0) val = String.valueOf(firstLevelValue);
int v = Integer.parseInt(val) - firstLevelValue + 1;
if (v <= 0) v = 1;
if (v > nValues) v = nValues;
value = isActive() ? v : -v;
}
catch (NumberFormatException e) {
reportDataError(this, Resources.getString("Error.non_number_error"), "followProperty["+propertyName+"]="+val, e);
}
catch (ExpressionException e) {
reportDataError(this, Resources.getString("Error.expression_error"), "followProperty["+propertyName+"]", e);
}
return;
}
public KeyCommand[] myGetKeyCommands() {
if (commands == null) {
final ArrayList<KeyCommand> l = new ArrayList<KeyCommand>();
final GamePiece outer = Decorator.getOutermost(this);
if (activateCommand != null && activateCommand.length() > 0 &&
!alwaysActive)
{
KeyCommand k;
k = new KeyCommand(activateCommand, activateKeyStroke, outer, this);
k.setEnabled(nValues > 0);
l.add(k);
}
if (!followProperty) {
if (nValues > 1) {
if (upCommand != null && upCommand.length() > 0 &&
increaseKeyStroke != null && !increaseKeyStroke.isNull())
{
up = new KeyCommand(upCommand, increaseKeyStroke, outer, this);
l.add(up);
}
if (downCommand != null && downCommand.length() > 0 &&
decreaseKeyStroke != null && !decreaseKeyStroke.isNull())
{
down = new KeyCommand(downCommand, decreaseKeyStroke, outer, this);
l.add(down);
}
}
if (resetKey != null && !resetKey.isNull() &&
resetCommand.length() > 0)
{
l.add(new KeyCommand(resetCommand, resetKey, outer, this));
}
// random layers
if (rndKey != null && !rndKey.isNull() && rndText.length() > 0) {
l.add(new KeyCommand(rndText, rndKey, outer, this));
}
// end random layers
}
commands = l.toArray(new KeyCommand[l.size()]);
}
if (up != null) {
up.setEnabled(loopLevels || Math.abs(value) < imageName.length);
}
if (down != null) {
down.setEnabled(loopLevels || Math.abs(value) > 1);
}
return commands;
}
public Command myKeyEvent(KeyStroke stroke) {
final ChangeTracker tracker = new ChangeTracker(this);
if (activateKeyStroke.equals(stroke) && nValues > 0 && !alwaysActive) {
value = - value;
// activated = ! activated;
// if (activated) {
// value = Math.abs(value);
// }
// else {
// value = -Math.abs(value);
// }
}
if (!followProperty) {
if (increaseKeyStroke.equals(stroke)) {
doIncrease();
}
if (decreaseKeyStroke.equals(stroke)) {
doDecrease();
}
if (resetKey != null && resetKey.equals(stroke)) {
final GamePiece outer = Decorator.getOutermost(this);
final String levelText = resetLevel.getText(outer);
try {
final int level = Integer.parseInt(levelText);
setValue(Math.abs(level) - 1);
setActive(level > 0);
}
catch (NumberFormatException e) {
reportDataError(this, Resources.getString("Error.non_number_error"), resetLevel.debugInfo(levelText, "resetLevel"), e);
}
}
// random layers
if (rndKey != null && rndKey.equals(stroke)) {
int val = 0;
val = GameModule.getGameModule().getRNG().nextInt(nValues) + 1;
value = value > 0 ? val : -val;
}
}
// end random layers
return tracker.isChanged() ? tracker.getChangeCommand() : null;
}
protected void doIncrease() {
int val = Math.abs(value);
if (++val > nValues) {
val = loopLevels ? 1 : nValues;
}
value = value > 0 ? val : -val;
return;
}
protected void doDecrease() {
int val = Math.abs(value);
if (--val < 1) {
val = loopLevels ? nValues : 1;
}
value = value > 0 ? val : -val;
return;
}
/** @deprecated Use {@link ImageOp.getImage} instead. */
@Deprecated
protected Image getCurrentImage() throws java.io.IOException {
// nonpositive value means that layer is inactive
// null or empty imageName[value-1] means that this layer has no image
if (value <= 0 ||
imageName[value-1] == null ||
imageName[value-1].length() == 0 ||
imagePainter[value-1] == null ||
imagePainter[value-1].getSource() == null) return null;
return imagePainter[value-1].getSource().getImage();
}
public Rectangle boundingBox() {
final Rectangle r = piece.boundingBox();
if (value > 0) r.add(getCurrentImageBounds());
return r;
}
public Rectangle getCurrentImageBounds() {
if (value > 0) {
final int i = value - 1;
if (i >= size.length) {
// Occurs when adding a layer with a name, but no image
return new Rectangle();
}
if (size[i] == null) {
if (imagePainter[i] != null) {
size[i] = ImageUtils.getBounds(imagePainter[i].getImageSize());
size[i].translate(xOff, yOff);
}
else {
size[i] = new Rectangle();
}
}
return size[i];
}
else {
return new Rectangle();
}
}
/**
* Return the Shape of the counter by adding the shape of this layer to the shape of all inner traits.
* Minimize generation of new Area objects.
*/
public Shape getShape() {
final Shape innerShape = piece.getShape();
if (value > 0 && !drawUnderneathWhenSelected) {
final Rectangle r = getCurrentImageBounds();
// If the label is completely enclosed in the current counter shape, then we can just return
// the current shape
if (innerShape.contains(r.x, r.y, r.width, r.height)) {
return innerShape;
}
else {
final Area a = new Area(innerShape);
// Cache the Area object generated. Only recreate if the layer position or size has changed
if (!r.equals(lastBounds)) {
lastShape = new Area(r);
lastBounds = new Rectangle(r);
}
a.add(lastShape);
return a;
}
}
else {
return innerShape;
}
}
public String getDescription() {
String displayName = name;
if (name == null || name.length() == 0) {
if (imageName.length > 0 &&
imageName[0] != null &&
imageName[0].length() > 0) {
displayName = imageName[0];
}
}
if (displayName == null || displayName.length() == 0) {
return "Layer";
}
else {
return "Layer - " + displayName;
}
}
public Object getProperty(Object key) {
if (key.equals(name + IMAGE)) {
checkPropertyLevel();
if (value > 0) {
return imageName[Math.abs(value) - 1];
}
else
return "";
}
else if (key.equals(name + NAME)) {
checkPropertyLevel();
if (value > 0) {
return strip(commonName[Math.abs(value) - 1]);
}
else
return "";
}
else if (key.equals(name + LEVEL)) {
checkPropertyLevel();
return String.valueOf(value);
}
else if (key.equals(name + ACTIVE)) {
return String.valueOf(isActive());
}
else if (key.equals(Properties.VISIBLE_STATE)) {
String s = String.valueOf(super.getProperty(key));
if (drawUnderneathWhenSelected) {
s += getProperty(Properties.SELECTED);
}
return s;
}
return super.getProperty(key);
}
public Object getLocalizedProperty(Object key) {
if (key.equals(name + IMAGE) ||
key.equals(name + LEVEL) ||
key.equals(name + ACTIVE) ||
key.equals(Properties.VISIBLE_STATE)) {
return getProperty(key);
}
else if (key.equals(name + NAME)) {
checkPropertyLevel();
if (value > 0) {
return strip(getLocalizedCommonName(Math.abs(value) - 1));
}
else
return "";
}
return super.getLocalizedProperty(key);
}
protected String strip (String s) {
if (s == null) {
return null;
}
if (s.startsWith("+")) {
return s.substring(1);
}
if (s.endsWith("+")) {
return s.substring(0, s.length() - 1);
}
return s;
}
/** Get the name of this level (alone) */
protected String getCommonName(boolean localized, int i) {
return localized ? getLocalizedCommonName(i) : commonName[i];
}
/** Get the localized name of this level (alone) */
protected String getLocalizedCommonName(int i) {
final String name = commonName[i];
if (name == null) return null;
final String translation = getTranslation(strip(name));
if (name.startsWith("+")) {
return "+" + translation;
}
if (name.endsWith("+")) {
return translation + "+";
}
return translation;
}
public HelpFile getHelpFile() {
return HelpFile.getReferenceManualPage("Layer.htm");
}
public PieceEditor getEditor() {
return new Ed(this);
}
public int getVersion() {
return version;
}
/**
* If the argument GamePiece contains a Layer whose "activate" command matches
* the given keystroke, and whose active status matches the boolean argument,
* return that Layer
*/
public static Embellishment getLayerWithMatchingActivateCommand(GamePiece piece, KeyStroke stroke, boolean active) {
for (Embellishment layer = (Embellishment) Decorator.getDecorator(piece, Embellishment.class); layer != null; layer = (Embellishment) Decorator
.getDecorator(layer.piece, Embellishment.class)) {
for (int i = 0; i < layer.activateKey.length(); ++i) {
if (stroke.equals(KeyStroke.getKeyStroke(layer.activateKey.charAt(i), layer.activateModifiers))) {
if (active && layer.isActive()) {
return layer;
}
else if (!active && !layer.isActive()) {
return layer;
}
break;
}
}
}
return null;
}
public static Embellishment getLayerWithMatchingActivateCommand(GamePiece piece, NamedKeyStroke stroke, boolean active) {
return getLayerWithMatchingActivateCommand(piece, stroke.getKeyStroke(), active);
}
public List<String> getPropertyNames() {
ArrayList<String> l = new ArrayList<String>();
l.add(name + IMAGE);
l.add(name + LEVEL);
l.add(name + ACTIVE);
l.add(name + NAME);
return l;
}
/**
* Return Property names exposed by this trait
*/
protected static class Ed implements PieceEditor {
private MultiImagePicker images;
private StringConfigurer activateCommand;
private StringConfigurer upCommand;
private StringConfigurer downCommand;
private StringConfigurer rndCommand;
private JTextField xOffInput = new JTextField(2);
private JTextField yOffInput = new JTextField(2);
private JTextField levelNameInput = new JTextField(10);
private JRadioButton prefix = new JRadioButton("is prefix");
private JRadioButton suffix = new JRadioButton("is suffix");
private JCheckBox drawUnderneath = new JCheckBox("Underneath when highlighted?");
private FormattedExpressionConfigurer resetLevel = new FormattedExpressionConfigurer(null, "Reset to level: ");
private StringConfigurer resetCommand;
private JCheckBox loop = new JCheckBox("Loop through levels?");
private JPanel controls;
private List<String> names;
private List<Integer> isPrefix;
private static final Integer NEITHER = 0;
private static final Integer PREFIX = 1;
private static final Integer SUFFIX = 2;
private BooleanConfigurer followConfig;
private PropertyNameExpressionConfigurer propertyConfig;
private IntConfigurer firstLevelConfig;
private StringConfigurer nameConfig;
private JButton up, down;
private int version;
private BooleanConfigurer alwaysActiveConfig;
private NamedHotKeyConfigurer activateConfig;
private NamedHotKeyConfigurer increaseConfig;
private NamedHotKeyConfigurer decreaseConfig;
private NamedHotKeyConfigurer resetConfig;
private NamedHotKeyConfigurer rndKeyConfig;
private JLabel activateLabel;
private JLabel increaseLabel;
private JLabel decreaseLabel;
private JLabel resetLabel;
private JLabel rndLabel;
private JLabel actionLabel;
private JLabel menuLabel;
private JLabel keyLabel;
private JLabel optionLabel;
public Ed(Embellishment e) {
Box box;
version = e.version;
controls = new JPanel();
controls.setLayout(new MigLayout("hidemode 2,fillx","[]rel[]rel[]rel[]"));
nameConfig = new StringConfigurer(null, "Name: ", e.getName());
controls.add(nameConfig.getControls(), "span 4,wrap,growx");
alwaysActiveConfig = new BooleanConfigurer(null, "Always active?", e.alwaysActive);
alwaysActiveConfig.addPropertyChangeListener(new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent evt) {
showHideFields();
}
});
controls.add(alwaysActiveConfig.getControls(), "span 2");
controls.add(drawUnderneath, "span 2,wrap");
controls.add(loop, "span 2");
final Box offsetControls = Box.createHorizontalBox();
xOffInput.setMaximumSize(xOffInput.getPreferredSize());
xOffInput.setText("0");
yOffInput.setMaximumSize(xOffInput.getPreferredSize());
yOffInput.setText("0");
offsetControls.add(new JLabel("Offset: "));
offsetControls.add(xOffInput);
offsetControls.add(new JLabel(","));
offsetControls.add(yOffInput);
controls.add(offsetControls, "span 2,wrap");
followConfig = new BooleanConfigurer(null, "Levels follow expression value?");
controls.add(followConfig.getControls(), "span 2");
final Box levelBox = Box.createHorizontalBox();
propertyConfig = new PropertyNameExpressionConfigurer(null, "Follow Expression: ");
levelBox.add(propertyConfig.getControls());
firstLevelConfig = new IntConfigurer(null, " Level 1 = ", e.firstLevelValue);
levelBox.add(firstLevelConfig.getControls());
controls.add(levelBox, "span 2,wrap");
followConfig.addPropertyChangeListener(new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent e) {
showHideFields();
}
});
actionLabel = new JLabel("Action");
final Font defaultFont = actionLabel.getFont();
final Font boldFont = new Font (defaultFont.getFamily(), Font.BOLD, defaultFont.getSize());
actionLabel.setFont(boldFont);
controls.add(actionLabel);
menuLabel = new JLabel("Menu Command");
menuLabel.setFont(boldFont);
controls.add(menuLabel, "align center");
keyLabel = new JLabel("Key");
keyLabel.setFont(boldFont);
controls.add(keyLabel, "align center");
optionLabel = new JLabel("Option");
optionLabel.setFont(boldFont);
controls.add(optionLabel, "align center,wrap");
activateConfig = new NamedHotKeyConfigurer(null, "", e.activateKeyStroke);
increaseConfig = new NamedHotKeyConfigurer(null, "", e.increaseKeyStroke);
decreaseConfig = new NamedHotKeyConfigurer(null, "", e.decreaseKeyStroke);
resetConfig = new NamedHotKeyConfigurer(null, "", e.resetKey);
rndKeyConfig = new NamedHotKeyConfigurer(null, "", e.rndKey);
activateLabel = new JLabel("Activate Layer");
controls.add(activateLabel);
activateCommand = new StringConfigurer(null, "", e.activateCommand);
controls.add(activateCommand.getControls(), "align center");
controls.add(activateConfig.getControls(), "wrap");
increaseLabel = new JLabel("Increase Level");
controls.add(increaseLabel);
upCommand = new StringConfigurer(null, "", e.upCommand);
controls.add(upCommand.getControls(), "align center");
controls.add(increaseConfig.getControls(), "wrap");
decreaseLabel = new JLabel("Decrease Level");
controls.add(decreaseLabel);
downCommand = new StringConfigurer(null, "", e.downCommand);
controls.add(downCommand.getControls(), "align center");
controls.add(decreaseConfig.getControls(), "wrap");
resetLabel = new JLabel("Reset to Level");
controls.add(resetLabel);
resetCommand = new StringConfigurer(null, "", e.resetCommand);
controls.add(resetCommand.getControls(), "align center");
controls.add(resetConfig.getControls());
controls.add(resetLevel.getControls(), "wrap");
rndLabel = new JLabel("Randomize");
controls.add(rndLabel);
rndCommand = new StringConfigurer(null, "", e.rndText);
controls.add(rndCommand.getControls(), "align center");
controls.add(rndKeyConfig.getControls(), "wrap");
images = getImagePicker();
images.addListSelectionListener(new ListSelectionListener() {
public void valueChanged(ListSelectionEvent e) {
setUpDownEnabled();
}});
controls.add(images, "span 4,split,grow");
up = new JButton(IconFactory.getIcon("go-up", IconFamily.XSMALL));
up.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
moveSelectedUp();
}});
down = new JButton(IconFactory.getIcon("go-down", IconFamily.XSMALL));
down.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
moveSelectedDown();
}});
final Box upDownPanel = Box.createVerticalBox();
upDownPanel.add(Box.createVerticalGlue());
upDownPanel.add(up);
upDownPanel.add(down);
upDownPanel.add(Box.createVerticalGlue());
controls.add(upDownPanel, "wrap");
box = Box.createHorizontalBox();
box.add(new JLabel("Level Name: "));
levelNameInput.setMaximumSize(levelNameInput.getPreferredSize());
levelNameInput.addKeyListener(new KeyAdapter() {
public void keyReleased(KeyEvent evt) {
changeLevelName();
}
});
box.add(levelNameInput);
controls.add(box, "span 2,growx");
box = Box.createHorizontalBox();
prefix.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
if (prefix.isSelected()) {
suffix.setSelected(false);
}
changeLevelName();
}
});
suffix.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
if (suffix.isSelected()) {
prefix.setSelected(false);
}
changeLevelName();
}
});
box.add(prefix);
box.add(suffix);
controls.add(box, "span 2,center,wrap");
final JPanel buttonPanel = new JPanel(new MigLayout("ins 0","[grow 1]rel[grow 1]"));
JButton b = new JButton("Add Level");
b.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
names.add(null);
isPrefix.add(null);
images.addEntry();
}
});
buttonPanel.add(b, "growx");
b = new JButton("Remove Level");
b.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
final int index = images.getList().getSelectedIndex();
if (index >= 0) {
names.remove(index);
isPrefix.remove(index);
images.removeEntryAt(index);
}
}
});
buttonPanel.add(b, "growx");
controls.add(buttonPanel, "span 4,center,growx,wrap");
images.getList().addListSelectionListener(new ListSelectionListener() {
public void valueChanged(javax.swing.event.ListSelectionEvent evt) {
updateLevelName();
}
});
showHideFields();
reset(e);
}
protected void moveSelectedUp() {
final int selected = images.getList().getSelectedIndex();
final int count = images.getList().getModel().getSize();
if (count > 1 && selected > 0) {
swap(selected, selected-1);
}
}
protected void moveSelectedDown() {
final int selected = images.getList().getSelectedIndex();
final int count = images.getList().getModel().getSize();
if (count > 1 && selected < (count-1)) {
swap(selected, selected+1);
}
}
protected void swap(int index1, int index2) {
final String name = names.get(index1);
names.set(index1, names.get(index2));
names.set(index2, name);
final Integer prefix = isPrefix.get(index1);
isPrefix.set(index1, isPrefix.get(index2));
isPrefix.set(index2, prefix);
images.swap (index1, index2);
}
protected void setUpDownEnabled() {
final int selected = images.getList().getSelectedIndex();
final int count = images.getList().getModel().getSize();
up.setEnabled(count > 1 && selected > 0);
down.setEnabled(count > 1 && selected < (count-1));
}
/*
* Change visibility of fields depending on the Follow Property and Always Active settings
*/
protected void showHideFields() {
final boolean alwaysActive = alwaysActiveConfig.getValueBoolean();
if (alwaysActive) {
activateLabel.setVisible(false);
activateCommand.getControls().setVisible(false);
activateConfig.getControls().setVisible(false);
}
else {
activateLabel.setVisible(true);
activateCommand.getControls().setVisible(true);
activateConfig.getControls().setVisible(true);
}
final boolean controlled = !followConfig.booleanValue().booleanValue();
loop.setEnabled(controlled);
propertyConfig.getControls().setVisible(!controlled);
firstLevelConfig.getControls().setVisible(!controlled);
increaseLabel.setVisible(controlled);
upCommand.getControls().setVisible(controlled);
increaseConfig.getControls().setVisible(controlled);
decreaseLabel.setVisible(controlled);
downCommand.getControls().setVisible(controlled);
decreaseConfig.getControls().setVisible(controlled);
resetLabel.setVisible(controlled);
resetCommand.getControls().setVisible(controlled);
resetConfig.getControls().setVisible(controlled);
resetLevel.getControls().setVisible(controlled);
rndLabel.setVisible(controlled);
rndCommand.getControls().setVisible(controlled);
rndKeyConfig.getControls().setVisible(controlled);
final boolean labelsVisible = ((!alwaysActive) || controlled);
actionLabel.setVisible(labelsVisible);
menuLabel.setVisible(labelsVisible);
keyLabel.setVisible(labelsVisible);
optionLabel.setVisible(labelsVisible);
Decorator.repack(controls);
}
private void updateLevelName() {
int index = images.getList().getSelectedIndex();
if (index < 0) {
levelNameInput.setText(null);
}
else {
levelNameInput.setText(names.get(index));
prefix.setSelected(PREFIX.equals(isPrefix.get(index)));
suffix.setSelected(SUFFIX.equals(isPrefix.get(index)));
}
}
private void changeLevelName() {
int index = images.getList().getSelectedIndex();
if (index >= 0) {
String s = levelNameInput.getText();
names.set(index, s);
if (prefix.isSelected()) {
isPrefix.set(index, PREFIX);
}
else if (suffix.isSelected()) {
isPrefix.set(index, SUFFIX);
}
else {
isPrefix.set(index, NEITHER);
}
}
else if (index == 0) {
names.set(index, null);
isPrefix.set(index, NEITHER);
}
}
protected MultiImagePicker getImagePicker() {
return new MultiImagePicker();
}
public String getState() {
return alwaysActiveConfig.getValueBoolean() ? "1" : "-1";
}
public String getType() {
final SequenceEncoder se = new SequenceEncoder(';');
final ArrayList<String> imageNames = new ArrayList<String>();
final ArrayList<String> commonNames = new ArrayList<String>();
int i = 0;
for (String n : images.getImageNameList()) {
imageNames.add(n);
String commonName = names.get(i);
if (commonName != null && commonName.length() > 0) {
if (PREFIX.equals(isPrefix.get(i))) {
commonName =
new SequenceEncoder(commonName, '+').append("").getValue();
}
else if (SUFFIX.equals(isPrefix.get(i))) {
commonName =
new SequenceEncoder("", '+').append(commonName).getValue();
}
else {
commonName = new SequenceEncoder(commonName, '+').getValue();
}
}
commonNames.add(commonName);
i++;
}
try {
Integer.parseInt(xOffInput.getText());
}
catch (NumberFormatException xNAN) {
// TODO use IntConfigurer NB Deprecated code - don't worry
xOffInput.setText("0");
}
try {
Integer.parseInt(yOffInput.getText());
}
catch (NumberFormatException yNAN) {
// TODO use IntConfigurer NB Deprecated code - don't worry
yOffInput.setText("0");
}
se.append(activateCommand.getValueString())
.append("")
.append("")
.append(upCommand.getValueString())
.append("")
.append("")
.append(downCommand.getValueString())
.append("")
.append("")
.append(resetCommand.getValueString())
.append(resetConfig.getValueString())
.append(resetLevel.getValueString())
.append(drawUnderneath.isSelected())
.append(xOffInput.getText())
.append(yOffInput.getText())
.append(imageNames.toArray(new String[imageNames.size()]))
.append(commonNames.toArray(new String[commonNames.size()]))
.append(loop.isSelected())
.append(nameConfig.getValueString())
.append(rndKeyConfig.getValueString())
.append(rndCommand.getValueString() == null ? "" :
rndCommand.getValueString().trim())
.append(followConfig.getValueString())
.append(propertyConfig.getValueString())
.append(firstLevelConfig.getValueString())
.append(version)
.append(alwaysActiveConfig.getValueString())
.append(activateConfig.getValueString())
.append(increaseConfig.getValueString())
.append(decreaseConfig.getValueString());
return ID + se.getValue();
}
public Component getControls() {
return controls;
}
public void reset(Embellishment e) {
nameConfig.setValue(e.name);
names = new ArrayList<String>();
isPrefix = new ArrayList<Integer>();
for (int i = 0; i < e.commonName.length; ++i) {
String s = e.commonName[i];
Integer is = NEITHER;
if (s != null && s.length() > 0) {
SequenceEncoder.Decoder st = new SequenceEncoder.Decoder(s, '+');
String first = st.nextToken();
if (st.hasMoreTokens()) {
String second = st.nextToken();
if (first.length() == 0) {
s = second;
is = SUFFIX;
}
else {
s = first;
is = PREFIX;
}
}
else {
s = first;
}
}
names.add(s);
isPrefix.add(is);
}
alwaysActiveConfig.setValue(Boolean.valueOf(e.alwaysActive));
drawUnderneath.setSelected(e.drawUnderneathWhenSelected);
loop.setSelected(e.loopLevels);
images.clear();
activateCommand.setValue(e.activateCommand);
upCommand.setValue(e.upCommand);
downCommand.setValue(e.downCommand);
resetConfig.setValue(e.resetKey);
resetCommand.setValue(e.resetCommand);
resetLevel.setValue(e.resetLevel.getFormat());
xOffInput.setText(String.valueOf(e.xOff));
yOffInput.setText(String.valueOf(e.yOff));
images.setImageList(e.imageName);
followConfig.setValue(Boolean.valueOf(e.followProperty));
propertyConfig.setValue(e.propertyName);
// Add at least one level if none defined
if (images.getImageNameList().isEmpty()) {
names.add(null);
isPrefix.add(null);
images.addEntry();
}
updateLevelName();
showHideFields();
}
}
public PieceI18nData getI18nData() {
final PieceI18nData data = new PieceI18nData(this);
final String prefix = name.length() > 0 ? name+": " : "";
if (activateKey.length() > 0) {
data.add(activateCommand, prefix + "Activate command");
}
if (!followProperty) {
data.add(upCommand, prefix + "Increase command");
data.add(downCommand, prefix + "Decrease command");
data.add(resetCommand, prefix + "Reset command");
data.add(rndText, prefix + "Random command");
}
// Strip off prefix/suffix marker
for (int i = 0; i < commonName.length; i++) {
data.add(strip(commonName[i]), prefix + "Level " + (i+1) + " name");
}
return data;
}
}