/* * $Id$ * * Copyright (c) 2004 by Rodney Kinney * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License (LGPL) as published by the Free Software Foundation. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, copies are available * at http://www.opensource.org. */ package VASSAL.counters; import java.awt.Component; import java.awt.Graphics; import java.awt.Rectangle; import java.awt.Shape; import javax.swing.KeyStroke; import VASSAL.build.module.PrototypeDefinition; import VASSAL.build.module.PrototypesContainer; import VASSAL.build.module.documentation.HelpFile; import VASSAL.build.module.properties.PropertySource; import VASSAL.command.Command; import VASSAL.configure.StringConfigurer; import VASSAL.tools.RecursionLimitException; import VASSAL.tools.RecursionLimiter; import VASSAL.tools.RecursionLimiter.Loopable; import VASSAL.tools.SequenceEncoder; /** * This trait is a placeholder for a pre-defined series of traits specified in a * {@link VASSAL.build.module.PrototypeDefinition} object. When a piece that uses a prototype is defined in a module, it * is simply assigned the name of a particular prototype definition. When that piece is during a game, the UsePrototype * trait is substituted for the list of traits in the prototype definition. From that point on, the piece has no record * that those traits were defined in a prototype instead of assigned to piece directly. This is necessary so that * subsequent changes to a prototype definition don't invalidate games that were saved using previous versions of the * module. * */ public class UsePrototype extends Decorator implements EditablePiece, Loopable { public static final String ID = "prototype;"; private String prototypeName; private String lastCachedPrototype; private GamePiece prototype; private PropertySource properties; private String type; public UsePrototype() { this(ID, null); } public UsePrototype(String type, GamePiece inner) { mySetType(type); setInner(inner); } public String getDescription() { return prototypeName != null && prototypeName.length() > 0 ? "Prototype - " + prototypeName : "Prototype"; } public HelpFile getHelpFile() { return HelpFile.getReferenceManualPage("UsePrototype.htm"); } public void mySetType(String type) { this.type = type; SequenceEncoder.Decoder st = new SequenceEncoder.Decoder(type.substring(ID.length()), ';'); prototypeName = st.nextToken(""); if (st.hasMoreTokens()) { final java.util.Properties p = new java.util.Properties(); SequenceEncoder.Decoder st2 = new SequenceEncoder.Decoder(st.nextToken(), ','); while (st2.hasMoreTokens()) { SequenceEncoder.Decoder st3 = new SequenceEncoder.Decoder(st2.nextToken(), '='); if (st3.hasMoreTokens()) { String key = st3.nextToken(); if (st3.hasMoreTokens()) { String value = st3.nextToken(); p.setProperty(key, value); } } } properties = new PropertySource() { public Object getProperty(Object key) { return p.getProperty(String.valueOf(key)); } public Object getLocalizedProperty(Object key) { return getProperty(key); } }; } lastCachedPrototype = null; } protected KeyCommand[] myGetKeyCommands() { return new KeyCommand[0]; } protected KeyCommand[] getKeyCommands() { return (KeyCommand[]) getExpandedInner().getProperty(Properties.KEY_COMMANDS); } public void setInner(GamePiece p) { super.setInner(p); lastCachedPrototype = null; } protected void buildPrototype() { final PrototypeDefinition def = PrototypesContainer.getPrototype(prototypeName); if (def != null) { final GamePiece expandedPrototype = def.getPiece(properties); // Check to see if prototype definition has changed final String type = expandedPrototype.getType(); if (!type.equals(lastCachedPrototype)) { lastCachedPrototype = type; try { RecursionLimiter.startExecution(this); prototype = PieceCloner.getInstance().clonePiece(expandedPrototype); final Decorator outer = (Decorator) Decorator.getInnermost(prototype).getProperty(Properties.OUTER); if (outer != null) { // Will be null for an empty prototype outer.setInner(piece); prototype.setProperty(Properties.OUTER, this); } else { prototype = null; } } catch (RecursionLimitException e) { RecursionLimiter.infiniteLoop(e); prototype = null; } finally { RecursionLimiter.endExecution(); } } } else { prototype = null; } } /** * Build a new GamePiece instance based on the traits in the referenced {@link PrototypeDefinition}. Substitute the * new instance for {@link #getInner} and return it. If the referenced definition does not exist, return the default * inner piece. * * @return the new instance */ public GamePiece getExpandedInner() { buildPrototype(); return prototype != null ? prototype : piece; } public String myGetState() { return ""; } public String myGetType() { return type; } public Command keyEvent(KeyStroke stroke) { return getExpandedInner().keyEvent(stroke); } public Command myKeyEvent(KeyStroke stroke) { return null; } public void mySetState(String newState) { } public Rectangle boundingBox() { return getExpandedInner().boundingBox(); } public void draw(Graphics g, int x, int y, Component obs, double zoom) { getExpandedInner().draw(g, x, y, obs, zoom); } public String getName() { return getExpandedInner().getName(); } public Shape getShape() { return getExpandedInner().getShape(); } public String getPrototypeName() { return prototypeName; } public PieceEditor getEditor() { return new Editor(this); } public static class Editor implements PieceEditor { private StringConfigurer nameConfig; public Editor(UsePrototype up) { nameConfig = new StringConfigurer(null, "Prototype name: ", up.type.substring(ID.length())); } public Component getControls() { return nameConfig.getControls(); } public String getState() { return ""; } public String getType() { return ID + nameConfig.getValueString(); } } // Implement Loopable public String getComponentName() { return piece.getName(); } public String getComponentTypeName() { return getDescription(); } }