/* * $Id$ * * Copyright (c) 2000-2006 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.Shape; import java.awt.event.InputEvent; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import javax.swing.Box; import javax.swing.KeyStroke; import VASSAL.build.GameModule; import VASSAL.build.module.Map; import VASSAL.build.module.documentation.HelpFile; import VASSAL.build.module.properties.PropertyChanger; import VASSAL.build.module.properties.PropertyChangerConfigurer; import VASSAL.build.module.properties.PropertyPrompt; import VASSAL.build.module.properties.PropertySource; import VASSAL.command.ChangeTracker; import VASSAL.command.Command; import VASSAL.configure.BooleanConfigurer; import VASSAL.configure.Configurer; import VASSAL.configure.IntConfigurer; import VASSAL.configure.ListConfigurer; import VASSAL.configure.NamedHotKeyConfigurer; import VASSAL.configure.StringConfigurer; import VASSAL.i18n.PieceI18nData; import VASSAL.i18n.TranslatablePiece; import VASSAL.tools.FormattedString; import VASSAL.tools.NamedKeyStroke; import VASSAL.tools.SequenceEncoder; /** * Trait that contains a property accessible via getProperty() and updateable * dynamically via key commands * * @author rkinney * */ public class DynamicProperty extends Decorator implements TranslatablePiece, PropertyPrompt.DialogParent, PropertyChangerConfigurer.Constraints { public static final String ID = "PROP;"; protected String value; protected String key; protected boolean numeric; protected int minValue; protected int maxValue; protected boolean wrap; protected FormattedString format = new FormattedString(); protected DynamicKeyCommand[] keyCommands; protected KeyCommand[] menuCommands; protected ListConfigurer keyCommandListConfig; public DynamicProperty() { this(ID, null); } public DynamicProperty(String type, GamePiece p) { setInner(p); keyCommandListConfig = new ListConfigurer(null, "Commands") { protected Configurer buildChildConfigurer() { return new DynamicKeyCommandConfigurer(DynamicProperty.this); } }; mySetType(type); } public void mySetType(String s) { final SequenceEncoder.Decoder sd = new SequenceEncoder.Decoder(s, ';'); sd.nextToken(); // Skip over command prefix key = sd.nextToken("name"); decodeConstraints(sd.nextToken("")); keyCommandListConfig.setValue(sd.nextToken("")); keyCommands = keyCommandListConfig.getListValue().toArray( new DynamicKeyCommand[keyCommandListConfig.getListValue().size()]); final ArrayList<DynamicKeyCommand> l = new ArrayList<DynamicKeyCommand>(); for (DynamicKeyCommand dkc : keyCommands) { if (dkc.getName() != null && dkc.getName().length() > 0) { l.add(dkc); } } menuCommands = l.toArray(new KeyCommand[l.size()]); } protected void decodeConstraints(String s) { SequenceEncoder.Decoder sd = new SequenceEncoder.Decoder(s, ','); numeric = sd.nextBoolean(false); minValue = sd.nextInt(0); maxValue = sd.nextInt(100); wrap = sd.nextBoolean(false); } protected String encodeConstraints() { return new SequenceEncoder(',').append(numeric).append(minValue).append(maxValue).append(wrap).getValue(); } public void draw(java.awt.Graphics g, int x, int y, java.awt.Component obs, double zoom) { piece.draw(g, x, y, obs, zoom); } public String getName() { return piece.getName(); } public java.awt.Rectangle boundingBox() { return piece.boundingBox(); } public Shape getShape() { return piece.getShape(); } public Object getProperty(Object key) { if (key.equals(getKey())) { return getValue(); } return super.getProperty(key); } public Object getLocalizedProperty(Object key) { if (key.equals(getKey())) { return getValue(); } else { return super.getLocalizedProperty(key); } } public void setProperty(Object key, Object value) { if (key.equals(getKey())) { setValue(null == value ? null : value.toString()); } else { super.setProperty(key, value); } } public String myGetState() { return getValue(); } public Component getComponent() { return getMap() != null ? getMap().getView().getTopLevelAncestor() : GameModule.getGameModule().getFrame(); } public void mySetState(String state) { setValue(state); } public String getKey() { return key; } public String getValue() { return value; } public void setValue(String value) { Stack parent = getParent(); Map map = getMap(); value = formatValue(value); // If the property has changed the layer to which this piece belongs, // re-insert it into the map. // No need to re-insert pieces in Decks, it causes problems if they are NO_STACK if (map != null && ! (getParent() instanceof Deck)) { GamePiece outer = Decorator.getOutermost(this); if (parent == null) { Point pos = getPosition(); map.removePiece(outer); this.value = value; map.placeOrMerge(outer, pos); } else { GamePiece other = parent.getPieceBeneath(outer); if (other == null) { other = parent.getPieceAbove(outer); } if (other == null) { Point pos = parent.getPosition(); map.removePiece(parent); this.value = value; map.placeOrMerge(parent,pos); } else { this.value = value; if (!map.getPieceCollection().canMerge(other, outer)) { map.placeOrMerge(outer, parent.getPosition()); } } } } else { this.value = value; } } private String formatValue(String value) { format.setFormat(value); return format.getText(Decorator.getOutermost(this)); } public String myGetType() { final SequenceEncoder se = new SequenceEncoder(';'); se.append(key); se.append(encodeConstraints()); se.append(keyCommandListConfig.getValueString()); return ID + se.getValue(); } protected KeyCommand[] myGetKeyCommands() { return menuCommands; } public Command myKeyEvent(KeyStroke stroke) { final ChangeTracker tracker = new ChangeTracker(this); for (DynamicKeyCommand dkc : keyCommands) { if (dkc.matches(stroke)) { setValue(dkc.propChanger.getNewValue(value)); } } return tracker.getChangeCommand(); } public String getDescription() { String s = "Dynamic Property"; if (getKey() != null && getKey().length() > 0) { s += " - " + getKey(); } return s; } public VASSAL.build.module.documentation.HelpFile getHelpFile() { return HelpFile.getReferenceManualPage("DynamicProperty.htm"); } public int getMaximumValue() { return maxValue; } public int getMinimumValue() { return minValue; } public boolean isNumeric() { return numeric; } public boolean isWrap() { return wrap; } /** * Return Property names exposed by this trait */ public List<String> getPropertyNames() { ArrayList<String> l = new ArrayList<String>(); l.add(key); return l; } public PropertySource getPropertySource() { return Decorator.getOutermost(this); } public PieceEditor getEditor() { return new Ed(this); } public PieceI18nData getI18nData() { final String[] commandNames = new String[menuCommands.length]; final String[] commandDescs = new String[menuCommands.length]; for (int i = 0; i < menuCommands.length; i++) { commandNames[i] = menuCommands[i].getName(); commandDescs[i] = "Property " + key + ": Menu Command " + i; } return getI18nData(commandNames, commandDescs); } protected static class Ed implements PieceEditor { protected StringConfigurer nameConfig; protected StringConfigurer initialValueConfig; protected BooleanConfigurer numericConfig; protected IntConfigurer minConfig; protected IntConfigurer maxConfig; protected BooleanConfigurer wrapConfig; protected ListConfigurer keyCommandListConfig; protected Box controls; public Ed(final DynamicProperty m) { keyCommandListConfig = new ListConfigurer(null, "Key Commands") { protected Configurer buildChildConfigurer() { return new DynamicKeyCommandConfigurer(m); } }; keyCommandListConfig.setValue( new ArrayList<DynamicKeyCommand>(Arrays.asList(m.keyCommands))); PropertyChangeListener l = new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent evt) { boolean isNumeric = numericConfig.booleanValue().booleanValue(); minConfig.getControls().setVisible(isNumeric); maxConfig.getControls().setVisible(isNumeric); wrapConfig.getControls().setVisible(isNumeric); keyCommandListConfig.repack(); } }; controls = Box.createVerticalBox(); nameConfig = new StringConfigurer(null, "Name: ", m.getKey()); controls.add(nameConfig.getControls()); initialValueConfig = new StringConfigurer(null, "Value: ", m.getValue()); controls.add(initialValueConfig.getControls()); numericConfig = new BooleanConfigurer(null, "Is numeric: ", m.isNumeric()); controls.add(numericConfig.getControls()); minConfig = new IntConfigurer(null, "Minimum value: ", m.getMinimumValue()); controls.add(minConfig.getControls()); maxConfig = new IntConfigurer(null, "Maximum value: ", m.getMaximumValue()); controls.add(maxConfig.getControls()); wrapConfig = new BooleanConfigurer(null, "Wrap?", m.isWrap()); controls.add(wrapConfig.getControls()); controls.add(keyCommandListConfig.getControls()); numericConfig.addPropertyChangeListener(l); numericConfig.fireUpdate(); } public Component getControls() { return controls; } protected String encodeConstraints() { return new SequenceEncoder(',') .append(numericConfig.getValueString()) .append(minConfig.getValueString()) .append(maxConfig.getValueString()) .append(wrapConfig.getValueString()).getValue(); } public String getType() { final SequenceEncoder se = new SequenceEncoder(';'); se.append(nameConfig.getValueString()); se.append(encodeConstraints()); se.append(keyCommandListConfig.getValueString()); return ID + se.getValue(); } public String getState() { return initialValueConfig.getValueString(); } } /** * DynamicKeyCommand A class that represents an action to be performed on a * Dynamic property */ protected static class DynamicKeyCommand extends KeyCommand { private static final long serialVersionUID = 1L; protected PropertyChanger propChanger = null; public DynamicKeyCommand(String name, NamedKeyStroke key, GamePiece target, TranslatablePiece i18nPiece, PropertyChanger propChanger) { super(name, key, target, i18nPiece); this.propChanger = propChanger; } } /** * * Configure a single Dynamic Key Command line */ protected static class DynamicKeyCommandConfigurer extends Configurer { protected NamedHotKeyConfigurer keyConfig; protected PropertyChangerConfigurer propChangeConfig; protected StringConfigurer commandConfig; protected Box controls = null; protected DynamicProperty target; public DynamicKeyCommandConfigurer(DynamicProperty target) { super(target.getKey(), target.getKey(), new DynamicKeyCommand("Change value", NamedKeyStroke.getNamedKeyStroke('V', InputEvent.CTRL_MASK), Decorator.getOutermost(target), target, new PropertyPrompt(target, "Change value of " + target.getKey()))); commandConfig = new StringConfigurer(null, " Menu Command: ", "Change value"); keyConfig = new NamedHotKeyConfigurer(null, " Key Command: ", NamedKeyStroke.getNamedKeyStroke('V', InputEvent.CTRL_MASK)); propChangeConfig = new PropertyChangerConfigurer(null, target.getKey(), target); propChangeConfig.setValue(new PropertyPrompt(target, " Change value of " + target.getKey())); PropertyChangeListener pl = new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent e) { updateValue(); } }; commandConfig.addPropertyChangeListener(pl); keyConfig.addPropertyChangeListener(pl); propChangeConfig.addPropertyChangeListener(pl); this.target = target; } public String getValueString() { SequenceEncoder se = new SequenceEncoder(':'); se.append(commandConfig.getValueString()).append(keyConfig.getValueString()).append(propChangeConfig.getValueString()); return se.getValue(); } public void setValue(Object value) { if (!noUpdate && value instanceof DynamicKeyCommand && commandConfig != null) { DynamicKeyCommand dkc = (DynamicKeyCommand) value; commandConfig.setValue(dkc.getName()); keyConfig.setValue(dkc.getNamedKeyStroke()); propChangeConfig.setValue(dkc.propChanger); } super.setValue(value); } public DynamicKeyCommand getKeyCommand() { return (DynamicKeyCommand) getValue(); } public void setValue(String s) { SequenceEncoder.Decoder sd = new SequenceEncoder.Decoder(s == null ? "" : s, ':'); commandConfig.setValue(sd.nextToken("")); keyConfig.setValue(sd.nextNamedKeyStroke(null)); propChangeConfig.setValue(sd.nextToken("")); updateValue(); } public Component getControls() { if (controls == null) { buildControls(); } return controls; } protected void updateValue() { noUpdate = true; setValue(new DynamicKeyCommand(commandConfig.getValueString(), keyConfig.getValueNamedKeyStroke(), target, target, propChangeConfig.getPropertyChanger())); noUpdate = false; } protected void buildControls() { controls = Box.createHorizontalBox(); controls.add(commandConfig.getControls()); controls.add(keyConfig.getControls()); controls.add(propChangeConfig.getControls()); } } }