/* * $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; } }