/* * $Id$ * * Copyright (c) 2000-2013 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. */ /* * Created by IntelliJ IDEA. * User: rkinney * Date: Oct 2, 2002 * Time: 6:30:35 AM * To change template for new class use * Code Style | Class Templates options (Tools | IDE Options). */ package VASSAL.counters; import java.awt.Component; import java.awt.Graphics; import java.awt.Rectangle; import java.awt.Shape; import java.awt.Window; import java.awt.event.InputEvent; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import javax.swing.BoxLayout; import javax.swing.JCheckBox; import javax.swing.JPanel; import javax.swing.KeyStroke; import javax.swing.SwingUtilities; import VASSAL.build.GameModule; import VASSAL.build.module.Chatter; import VASSAL.build.module.Map; import VASSAL.build.module.documentation.HelpFile; import VASSAL.build.module.properties.PropertySource; import VASSAL.command.ChangeTracker; import VASSAL.command.Command; import VASSAL.configure.NamedKeyStrokeArrayConfigurer; import VASSAL.configure.PlayerIdFormattedStringConfigurer; import VASSAL.configure.StringArrayConfigurer; import VASSAL.configure.StringConfigurer; import VASSAL.i18n.PieceI18nData; import VASSAL.i18n.Resources; import VASSAL.i18n.TranslatablePiece; import VASSAL.tools.ArrayUtils; import VASSAL.tools.FormattedString; import VASSAL.tools.NamedKeyStroke; import VASSAL.tools.SequenceEncoder; /** * A GamePiece with this trait will echo the piece's current name when any of a given key commands are pressed * (and after they take effect) */ public class ReportState extends Decorator implements TranslatablePiece { public static final String ID = "report;"; protected NamedKeyStroke[] keys; protected FormattedString format = new FormattedString(); protected String reportFormat; protected String[] cycleReportFormat; protected NamedKeyStroke[] cycleDownKeys; protected int cycleIndex = -1; protected String description; public ReportState() { this(ID, null); } public ReportState(String type, GamePiece inner) { mySetType(type); setInner(inner); } public Rectangle boundingBox() { return piece.boundingBox(); } public void draw(Graphics g, int x, int y, Component obs, double zoom) { piece.draw(g, x, y, obs, zoom); } public String getName() { return piece.getName(); } protected KeyCommand[] myGetKeyCommands() { return new KeyCommand[0]; } public String myGetState() { return cycleIndex + ""; } public String myGetType() { SequenceEncoder se = new SequenceEncoder(';'); se.append(NamedKeyStrokeArrayConfigurer.encode(keys)).append(reportFormat).append(NamedKeyStrokeArrayConfigurer.encode(cycleDownKeys)).append(StringArrayConfigurer.arrayToString(cycleReportFormat)) .append(description); return ID + se.getValue(); } // We perform the inner commands first so that their effects will be reported public Command keyEvent(KeyStroke stroke) { Command c = piece.keyEvent(stroke); return c == null ? myKeyEvent(stroke) : c.append(myKeyEvent(stroke)); } public Command myKeyEvent(KeyStroke stroke) { GamePiece outer = getOutermost(this); // Retrieve the name, location and visibilty of the unit prior to the // trait being executed if it is outside this one. format.setProperty(MAP_NAME, getMap() == null ? null : getMap().getConfigureName()); format.setProperty(LOCATION_NAME, getMap() == null ? null : getMap().locationName(getPosition())); format.setProperty(OLD_MAP_NAME, (String) getProperty(BasicPiece.OLD_MAP)); format.setProperty(OLD_LOCATION_NAME, (String) getProperty(BasicPiece.OLD_LOCATION_NAME)); Command c = null; GamePiece oldPiece = (GamePiece) getProperty(Properties.SNAPSHOT); boolean wasVisible = oldPiece != null && !Boolean.TRUE.equals(oldPiece.getProperty(Properties.INVISIBLE_TO_OTHERS)); boolean isVisible = !Boolean.TRUE.equals(outer.getProperty(Properties.INVISIBLE_TO_OTHERS)); PieceAccess.GlobalAccess.hideAll(); String oldUnitName = oldPiece == null ? null : oldPiece.getLocalizedName(); format.setProperty(OLD_UNIT_NAME, oldUnitName); String newUnitName = outer.getLocalizedName(); format.setProperty(NEW_UNIT_NAME, newUnitName); PieceAccess.GlobalAccess.revertAll(); // Only make a report if: // 1. It's not part of a global command with Single Reporting on // 2. The piece is visible to all players either before or after the trait // command was executed. if (isVisible || wasVisible) { final NamedKeyStroke[] allKeys = ArrayUtils.append(keys, cycleDownKeys); for (int i = 0; i < allKeys.length; ++i) { if (stroke != null && stroke.equals(allKeys[i].getKeyStroke())) { // // Find the Command Name // String commandName = ""; KeyCommand[] k = ((Decorator) outer).getKeyCommands(); for (int j = 0; j < k.length; j++) { KeyStroke commandKey = k[j].getKeyStroke(); if (stroke.equals(commandKey)) { commandName = k[j].getName(); } } ChangeTracker tracker = new ChangeTracker(this); format.setProperty(COMMAND_NAME, commandName); String theFormat = reportFormat; if (cycleIndex >= 0 && cycleReportFormat.length > 0) { if (i < keys.length) { theFormat = cycleReportFormat[cycleIndex]; cycleIndex = (cycleIndex + 1) % cycleReportFormat.length; } else { cycleIndex = (cycleIndex + cycleReportFormat.length - 1) % cycleReportFormat.length; theFormat = cycleReportFormat[(cycleIndex + cycleReportFormat.length - 1) % cycleReportFormat.length]; } } format.setFormat(getTranslation(theFormat)); OldAndNewPieceProperties properties = new OldAndNewPieceProperties(oldPiece,outer); String reportText = format.getLocalizedText(properties); if (getMap() != null) { format.setFormat(getMap().getChangeFormat()); } else if (!Map.isChangeReportingEnabled()) { format.setFormat(""); } else { format.setFormat("$"+Map.MESSAGE+"$"); } format.setProperty(Map.MESSAGE, reportText); reportText = format.getLocalizedText(properties); if (reportText.length() > 0) { Command display = new Chatter.DisplayText(GameModule.getGameModule().getChatter(), "* " + reportText); display.execute(); c = display; } c = tracker.getChangeCommand().append(c); break; } } } return c; } protected String getPieceName() { String name = ""; PieceAccess.GlobalAccess.hideAll(); name = getOutermost(this).getName(); PieceAccess.GlobalAccess.revertAll(); return name; } public void mySetState(String newState) { if (newState.length() > 0) { try { cycleIndex = Integer.parseInt(newState); } catch (NumberFormatException e) { cycleIndex = -1; reportDataError(this, Resources.getString("Error.non_number_error"), "Trying to init Message Index to "+newState); } } else { cycleIndex = -1; } } public Shape getShape() { return piece.getShape(); } public String getDescription() { String d = "Report Action"; if (description.length() > 0) { d += " - " + description; } return d; } public HelpFile getHelpFile() { return HelpFile.getReferenceManualPage("ReportChanges.htm"); } public void mySetType(String type) { SequenceEncoder.Decoder st = new SequenceEncoder.Decoder(type, ';'); st.nextToken(); String encodedKeys = st.nextToken(""); if (encodedKeys.indexOf(',') > 0) { keys = NamedKeyStrokeArrayConfigurer.decode(encodedKeys); } else { keys = new NamedKeyStroke[encodedKeys.length()]; for (int i = 0; i < keys.length; i++) { keys[i] = NamedKeyStroke.getNamedKeyStroke(encodedKeys.charAt(i),InputEvent.CTRL_MASK); } } reportFormat = st.nextToken("$" + LOCATION_NAME + "$: $" + NEW_UNIT_NAME + "$ *"); String encodedCycleDownKeys = st.nextToken(""); if (encodedCycleDownKeys.indexOf(',') > 0) { cycleDownKeys = NamedKeyStrokeArrayConfigurer.decode(encodedCycleDownKeys); } else { cycleDownKeys = new NamedKeyStroke[encodedCycleDownKeys.length()]; for (int i = 0; i < cycleDownKeys.length; i++) { cycleDownKeys[i] = NamedKeyStroke.getNamedKeyStroke(encodedCycleDownKeys.charAt(i),InputEvent.CTRL_MASK); } } cycleReportFormat = StringArrayConfigurer.stringToArray(st.nextToken("")); description = st.nextToken(""); } public PieceEditor getEditor() { return new Ed(this); } public PieceI18nData getI18nData() { int c = cycleReportFormat == null ? 0 : cycleReportFormat.length; String[] formats = new String[c+1]; String[] descriptions = new String[c+1]; formats[0] = reportFormat; descriptions[0] = getCommandDescription(description, "Report Format"); int j = 1; for (int i=0; i < c; i++) { formats[j] = cycleReportFormat[i]; descriptions[j] = getCommandDescription(description, "Report Format " + j); j++; } return getI18nData(formats, descriptions); } public static final String OLD_UNIT_NAME = "oldPieceName"; public static final String NEW_UNIT_NAME = "newPieceName"; public static final String MAP_NAME = "mapName"; public static final String OLD_MAP_NAME = "oldMapName"; public static final String LOCATION_NAME = "location"; public static final String OLD_LOCATION_NAME = "oldLocation"; public static final String COMMAND_NAME = "menuCommand"; public static class Ed implements PieceEditor { private NamedKeyStrokeArrayConfigurer keys; private StringConfigurer format; private JCheckBox cycle; private StringArrayConfigurer cycleFormat; private NamedKeyStrokeArrayConfigurer cycleDownKeys; protected StringConfigurer descInput; private JPanel box; public Ed(ReportState piece) { box = new JPanel(); box.setLayout(new BoxLayout(box, BoxLayout.Y_AXIS)); descInput = new StringConfigurer(null, "Description: ", piece.description); box.add(descInput.getControls()); keys = new NamedKeyStrokeArrayConfigurer(null, "Report on these keystrokes: ", piece.keys); box.add(keys.getControls()); cycle = new JCheckBox("Cycle through different messages?"); box.add(cycle); format = new PlayerIdFormattedStringConfigurer(null, "Report format: ", new String[]{COMMAND_NAME, OLD_UNIT_NAME, NEW_UNIT_NAME, MAP_NAME, OLD_MAP_NAME, LOCATION_NAME, OLD_LOCATION_NAME}); format.setValue(piece.reportFormat); box.add(format.getControls()); cycleFormat = new StringArrayConfigurer(null, "Message formats", piece.cycleReportFormat); box.add(cycleFormat.getControls()); cycleDownKeys = new NamedKeyStrokeArrayConfigurer(null, "Report previous message on these keystrokes: ", piece.cycleDownKeys); box.add(cycleDownKeys.getControls()); ItemListener l = new ItemListener() { public void itemStateChanged(ItemEvent e) { format.getControls().setVisible(!cycle.isSelected()); cycleFormat.getControls().setVisible(cycle.isSelected()); cycleDownKeys.getControls().setVisible(cycle.isSelected()); Window w = SwingUtilities.getWindowAncestor(box); if (w != null) { w.pack(); } } }; l.itemStateChanged(null); cycle.addItemListener(l); cycle.setSelected(piece.cycleReportFormat.length > 0); } public Component getControls() { return box; } public String getState() { return cycle.isSelected() ? "0" : "-1"; } public String getType() { SequenceEncoder se = new SequenceEncoder(';'); if (cycle.isSelected() && cycleFormat.getStringArray().length > 0) { se.append(keys.getValueString()).append("").append(cycleDownKeys.getValueString()).append(cycleFormat.getValueString()); } else { se.append(keys.getValueString()).append(format.getValueString()).append("").append(""); } se.append(descInput.getValueString()); return ID + se.getValue(); } } /** * Looks in both the new and old piece for property values. * Any properties with names of the format "oldXyz" are changed * to "xyz" and applied to the old piece. * @author rkinney * */ private static class OldAndNewPieceProperties implements PropertySource { private GamePiece oldPiece; private GamePiece newPiece; public OldAndNewPieceProperties(GamePiece oldPiece, GamePiece newPiece) { super(); this.oldPiece = oldPiece; this.newPiece = newPiece; } public Object getProperty(Object key) { Object value = null; if (key != null) { String name = key.toString(); if (name.startsWith("old") && name.length() >= 4) { name = name.substring(3); value = oldPiece.getProperty(name); } else { value = newPiece.getProperty(key); } } return value; } public Object getLocalizedProperty(Object key) { return getProperty(key); } } }