/* * $Id$ * * Copyright (c) 2000-2009 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.Dialog; import java.awt.Frame; import java.awt.Window; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import javax.swing.BoxLayout; import javax.swing.DefaultListCellRenderer; import javax.swing.DefaultListModel; import javax.swing.JButton; import javax.swing.JDialog; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.ListCellRenderer; import javax.swing.ListSelectionModel; import javax.swing.SwingUtilities; import javax.swing.border.TitledBorder; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import net.miginfocom.swing.MigLayout; import VASSAL.build.GameModule; import VASSAL.build.GpIdSupport; import VASSAL.build.module.documentation.HelpFile; import VASSAL.build.module.documentation.HelpWindow; import VASSAL.build.module.documentation.HelpWindowExtension; import VASSAL.build.widget.PieceSlot; import VASSAL.tools.BrowserSupport; import VASSAL.tools.ErrorDialog; import VASSAL.tools.ReflectionUtils; /** * This is the GamePiece designer dialog. It appears when you edit * the properties of a "Single Piece" in the Configuration window. */ public class PieceDefiner extends JPanel implements HelpWindowExtension { private static final long serialVersionUID = 1L; protected static DefaultListModel availableModel; protected DefaultListModel inUseModel; protected ListCellRenderer r; protected PieceSlot slot; private GamePiece piece; protected static TraitClipboard clipBoard; protected String pieceId = ""; protected JLabel pieceIdLabel = new JLabel(""); protected GpIdSupport gpidSupport; protected boolean changed; /** Creates new form test */ public PieceDefiner() { initDefinitions(); inUseModel = new DefaultListModel(); r = new Renderer(); slot = new PieceSlot(); initComponents(); availableList.setSelectedIndex(0); setChanged(false); gpidSupport = GameModule.getGameModule().getGpIdSupport(); } public PieceDefiner(String id, GpIdSupport s) { this(); pieceId = id; pieceIdLabel.setText("Id: "+id); gpidSupport = s; } public PieceDefiner(GpIdSupport s) { this(); gpidSupport = s; } protected static void initDefinitions() { if (availableModel == null) { availableModel = new DefaultListModel(); availableModel.addElement(new BasicPiece()); availableModel.addElement(new Delete()); availableModel.addElement(new Clone()); availableModel.addElement(new Embellishment()); availableModel.addElement(new UsePrototype()); availableModel.addElement(new Labeler()); availableModel.addElement(new ReportState()); availableModel.addElement(new TriggerAction()); availableModel.addElement(new GlobalHotKey()); availableModel.addElement(new ActionButton()); availableModel.addElement(new FreeRotator()); availableModel.addElement(new Pivot()); availableModel.addElement(new Hideable()); availableModel.addElement(new Obscurable()); availableModel.addElement(new SendToLocation()); availableModel.addElement(new CounterGlobalKeyCommand()); availableModel.addElement(new Translate()); availableModel.addElement(new ReturnToDeck()); availableModel.addElement(new Immobilized()); availableModel.addElement(new PropertySheet()); availableModel.addElement(new TableInfo()); availableModel.addElement(new PlaceMarker()); availableModel.addElement(new Replace()); availableModel.addElement(new NonRectangular()); availableModel.addElement(new PlaySound()); availableModel.addElement(new MovementMarkable()); availableModel.addElement(new Footprint()); availableModel.addElement(new AreaOfEffect()); availableModel.addElement(new SubMenu()); availableModel.addElement(new RestrictCommands()); availableModel.addElement(new Restricted()); availableModel.addElement(new Marker()); availableModel.addElement(new DynamicProperty()); availableModel.addElement(new CalculatedProperty()); availableModel.addElement(new SetGlobalProperty()); } } /** * Plugins can add additional GamePiece definitions * @param definition */ public static void addDefinition(GamePiece definition) { initDefinitions(); availableModel.addElement(definition); } public void setPiece(GamePiece piece) { inUseModel.clear(); while (piece instanceof Decorator) { final Class<?> pieceClass = piece.getClass(); inUseModel.insertElementAt(piece, 0); boolean contains = false; for (int i = 0,j = availableModel.size(); i < j; ++i) { if (pieceClass.isInstance(availableModel.elementAt(i))) { contains = true; break; } } if (!contains) { try { availableModel.addElement(pieceClass.getConstructor().newInstance()); } catch (Throwable t) { ReflectionUtils.handleNewInstanceFailure(t, pieceClass); } } piece = ((Decorator) piece).piece; } if (piece == null) { inUseModel.insertElementAt(new BasicPiece(), 0); } else { inUseModel.insertElementAt(piece, 0); } inUseList.setSelectedIndex(0); refresh(); } @Deprecated public void setBaseWindow(HelpWindow w) { } private void refresh() { if (inUseModel.getSize() > 0) { piece = (GamePiece) inUseModel.lastElement(); } else { piece = null; } slot.setPiece(piece); slot.getComponent().repaint(); } public GamePiece getPiece() { return piece; } public void setChanged(boolean b) { changed = b; } public boolean isChanged() { return changed; } /** * This method is called from within the constructor to initialize the form. */ private void initComponents() { setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); add(slot.getComponent()); JPanel controls = new JPanel(); controls.setLayout(new BoxLayout(controls, BoxLayout.X_AXIS)); availablePanel = new JPanel(); availableScroll = new JScrollPane(); availableList = new JList(); helpButton = new JButton(); importButton = new JButton(); addRemovePanel = new JPanel(); addButton = new JButton(); removeButton = new JButton(); inUsePanel = new JPanel(); inUseScroll = new JScrollPane(); inUseList = new JList(); propsButton = new JButton(); moveUpDownPanel = new JPanel(); moveUpButton = new JButton(); moveDownButton = new JButton(); copyButton = new JButton(); pasteButton = new JButton(); // setLayout(new BoxLayout(this, 0)); availablePanel.setLayout(new BoxLayout(availablePanel, 1)); availableList.setModel(availableModel); availableList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); availableList.setCellRenderer(r); availableList.addListSelectionListener(new ListSelectionListener() { public void valueChanged(ListSelectionEvent evt) { Object o = availableList.getSelectedValue(); helpButton.setEnabled(o instanceof EditablePiece && ((EditablePiece) o).getHelpFile() != null); addButton.setEnabled(o instanceof Decorator); } }); availableScroll.setViewportView(availableList); availableScroll.setBorder(new TitledBorder("Available Traits")); availablePanel.add(availableScroll); helpButton.setText("Help"); helpButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { showHelpForPiece(); } } ); availablePanel.add(helpButton); importButton.setText("Import"); importButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { String className = JOptionPane.showInputDialog(PieceDefiner.this, "Enter fully-qualified name of Java class to import"); importPiece(className); } }); availablePanel.add(importButton); controls.add(availablePanel); addRemovePanel.setLayout(new BoxLayout(addRemovePanel, 1)); addButton.setText("Add ->"); addButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { Object selected = availableList.getSelectedValue(); if (selected instanceof Decorator) { if (inUseModel.getSize() > 0) { Decorator c = (Decorator) selected; addTrait(c); if (inUseModel.lastElement().getClass() == c.getClass()) { if (edit(inUseModel.size() - 1)) { // Add was successful } else { // Add was cancelled if (!inUseModel.isEmpty()) removeTrait(inUseModel.size() - 1); } } } } else if (selected instanceof GamePiece && inUseModel.getSize() == 0) { GamePiece p = null; try { p = (GamePiece) selected.getClass().getConstructor().newInstance(); } catch (Throwable t) { ReflectionUtils.handleNewInstanceFailure(t, selected.getClass()); } if (p != null) { setPiece(p); if (inUseModel.getSize() > 0) { if (edit(0)) { // Add was successful } else { // Add was cancelled removeTrait(0); } } } } } }); addButton.setAlignmentX(Component.CENTER_ALIGNMENT); addRemovePanel.add(addButton); removeButton.setText("<- Remove"); removeButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { int index = inUseList.getSelectedIndex(); if (index >= 0) { removeTrait(index); if (inUseModel.getSize() > 0) { inUseList.setSelectedIndex( Math.min(inUseModel.getSize() - 1, Math.max(index, 0))); } } } } ); removeButton.setAlignmentX(Component.CENTER_ALIGNMENT); addRemovePanel.add(removeButton); pieceIdLabel.setAlignmentX(Component.CENTER_ALIGNMENT); addRemovePanel.add(pieceIdLabel); controls.add(addRemovePanel); inUsePanel.setLayout(new BoxLayout(inUsePanel, 1)); inUseList.setModel(inUseModel); inUseList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); inUseList.setCellRenderer(r); inUseList.addListSelectionListener(new ListSelectionListener() { public void valueChanged(ListSelectionEvent evt) { final Object o = inUseList.getSelectedValue(); propsButton.setEnabled(o instanceof EditablePiece); final int index = inUseList.getSelectedIndex(); final boolean copyAndRemove = inUseModel.size() > 0 && (index > 0 || !(inUseModel.getElementAt(0) instanceof BasicPiece)); copyButton.setEnabled(copyAndRemove); removeButton.setEnabled(copyAndRemove); pasteButton.setEnabled(clipBoard != null); moveUpButton.setEnabled(index > 1); moveDownButton.setEnabled(index > 0 && index < inUseModel.size() - 1); } }); inUseList.addMouseListener(new MouseAdapter() { public void mouseReleased(MouseEvent evt) { if (evt.getClickCount() == 2) { int index = inUseList.locationToIndex(evt.getPoint()); if (index >= 0) { edit(index); } } } }); inUseScroll.setViewportView(inUseList); inUseScroll.setBorder(new TitledBorder("Current Traits")); inUsePanel.add(inUseScroll); propsButton.setText("Properties"); propsButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { int index = inUseList.getSelectedIndex(); if (index >= 0) { edit(index); } } }); inUsePanel.add(propsButton); controls.add(inUsePanel); moveUpDownPanel.setLayout(new BoxLayout(moveUpDownPanel, BoxLayout.Y_AXIS)); moveUpButton.setText("Move Up"); moveUpButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { int index = inUseList.getSelectedIndex(); if (index > 1 && index < inUseModel.size()) { moveDecoratorUp(index); } } } ); moveUpDownPanel.add(moveUpButton); moveDownButton.setText("Move Down"); moveDownButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { int index = inUseList.getSelectedIndex(); if (index > 0 && index < inUseModel.size() - 1) { moveDecoratorDown(index); } } } ); moveUpDownPanel.add(moveDownButton); copyButton.setText("Copy"); copyButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { pasteButton.setEnabled(true); int index = inUseList.getSelectedIndex(); clipBoard = new TraitClipboard((Decorator) inUseModel.get(index)); }}); moveUpDownPanel.add(copyButton); pasteButton.setText("Paste"); pasteButton.setEnabled(clipBoard != null); pasteButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { if (clipBoard != null) { paste(); } }}); moveUpDownPanel.add(pasteButton); controls.add(moveUpDownPanel); add(controls); } protected void paste() { final Decorator c = (Decorator) GameModule.getGameModule().createPiece(clipBoard.getType(), null); if (c instanceof PlaceMarker) { ((PlaceMarker) c).updateGpId(GameModule.getGameModule().getGpIdSupport()); } c.setInner((GamePiece) inUseModel.lastElement()); inUseModel.addElement(c); c.mySetState(clipBoard.getState()); refresh(); } protected void moveDecoratorDown(int index) { GamePiece selm1 = (GamePiece) inUseModel.elementAt(index - 1); Decorator sel = (Decorator) inUseModel.elementAt(index); Decorator selp1 = (Decorator) inUseModel.elementAt(index + 1); Decorator selp2 = index < inUseModel.size() - 2 ? (Decorator) inUseModel.elementAt(index + 2) : null; selp1.setInner(selm1); sel.setInner(selp1); if (selp2 != null) { selp2.setInner(sel); } inUseModel.setElementAt(selp1, index); inUseModel.setElementAt(sel, index + 1); ((GamePiece) inUseModel.lastElement()).setProperty(Properties.OUTER, null); inUseList.setSelectedIndex(index + 1); refresh(); setChanged(true); } protected void moveDecoratorUp(int index) { final GamePiece selm2 = (GamePiece) inUseModel.elementAt(index - 2); final Decorator sel = (Decorator) inUseModel.elementAt(index); final Decorator selm1 = (Decorator) inUseModel.elementAt(index - 1); final Decorator selp1 = index < inUseModel.size() - 1 ? (Decorator) inUseModel.elementAt(index + 1) : null; sel.setInner(selm2); selm1.setInner(sel); if (selp1 != null) { selp1.setInner(selm1); } inUseModel.setElementAt(selm1, index); inUseModel.setElementAt(sel, index - 1); ((GamePiece) inUseModel.lastElement()).setProperty(Properties.OUTER, null); inUseList.setSelectedIndex(index - 1); refresh(); setChanged(true); } protected void importPiece(String className) { if (className == null) return; Object o = null; try { o = GameModule.getGameModule().getDataArchive() .loadClass(className).getConstructor().newInstance(); } catch (Throwable t) { ReflectionUtils.handleImportClassFailure(t, className); } if (o == null) return; if (o instanceof GamePiece) { availableModel.addElement((GamePiece) o); } else { ErrorDialog.show("Error.not_a_gamepiece", className); } } private void showHelpForPiece() { Object o = availableList.getSelectedValue(); if (o instanceof EditablePiece) { HelpFile h = ((EditablePiece) o).getHelpFile(); BrowserSupport.openURL(h.getContents().toString()); } } protected boolean edit(int index) { Object o = inUseModel.elementAt(index); if (!(o instanceof EditablePiece)) { return false; } EditablePiece p = (EditablePiece) o; if (p.getEditor() != null) { Ed ed = null; Window w = SwingUtilities.getWindowAncestor(this); if (w instanceof Frame) { ed = new Ed((Frame) w, p); } else if (w instanceof Dialog) { ed = new Ed((Dialog) w, p); } else { ed = new Ed((Frame) null, p); } final String oldState = p.getState(); final String oldType = p.getType(); ed.setVisible(true); PieceEditor c = ed.getEditor(); if (c != null) { p.mySetType(c.getType()); if (p instanceof Decorator) { ((Decorator) p).mySetState(c.getState()); } else { p.setState(c.getState()); } if ((! p.getType().equals(oldType)) || (! p.getState().equals(oldState))) { setChanged(true); } refresh(); return true; } } return false; } /** A Dialog for editing an EditablePiece's properties */ protected static class Ed extends JDialog { private static final long serialVersionUID = 1L; PieceEditor ed; private Ed(Frame owner, final EditablePiece p) { super(owner, p.getDescription() + " properties", true); initialize(p); } private Ed(Dialog owner, final EditablePiece p) { super(owner, p.getDescription() + " properties", true); initialize(p); } private void initialize(final EditablePiece p) { ed = p.getEditor(); setLayout(new MigLayout("ins dialog,fill", "[]unrel[]", "")); add(ed.getControls(), "spanx 3,grow,push,wrap"); JButton b = new JButton("Ok"); b.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { dispose(); } }); add(b, "tag ok"); b = new JButton("Cancel"); b.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { ed = null; dispose(); } }); add(b, "tag cancel"); if (p.getHelpFile() != null) { b = new JButton("Help"); b.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { p.getHelpFile().showWindow(Ed.this); } }); add(b, "tag help"); } pack(); setLocationRelativeTo(getOwner()); } public PieceEditor getEditor() { return ed; } } protected void removeTrait(int index) { inUseModel.removeElementAt(index); if (index < inUseModel.size()) { ((Decorator) inUseModel.elementAt(index)).setInner((GamePiece) inUseModel.elementAt(index - 1)); } refresh(); setChanged(true); } protected void addTrait(Decorator c) { final Class<? extends Decorator> cClass = c.getClass(); Decorator d = null; try { d = cClass.getConstructor().newInstance(); } catch (Throwable t) { ReflectionUtils.handleNewInstanceFailure(t, cClass); } if (d != null) { if (d instanceof PlaceMarker) { ((PlaceMarker) d).updateGpId(gpidSupport); } d.setInner((GamePiece) inUseModel.lastElement()); inUseModel.addElement(d); setChanged(true); } refresh(); } private JPanel availablePanel; private JScrollPane availableScroll; protected JList availableList; private JButton helpButton; private JButton importButton; private JPanel addRemovePanel; private JButton addButton; private JButton removeButton; private JPanel inUsePanel; private JScrollPane inUseScroll; private JList inUseList; private JButton propsButton; private JPanel moveUpDownPanel; private JButton moveUpButton; private JButton moveDownButton; protected JButton copyButton; protected JButton pasteButton; private static class Renderer extends DefaultListCellRenderer { private static final long serialVersionUID = 1L; public java.awt.Component getListCellRendererComponent (JList list, Object value, int index, boolean selected, boolean hasFocus) { super.getListCellRendererComponent(list, value, index, selected, hasFocus); if (value instanceof EditablePiece) { setText(((EditablePiece) value).getDescription()); } else { String s = value.getClass().getName(); setText(s.substring(s.lastIndexOf('.') + 1)); } return this; } } /** * Contents of the Copy/Paste buffer for traits in the editor * @author rkinney * */ private static class TraitClipboard { private String type; private String state; public TraitClipboard(Decorator copy) { type = copy.myGetType(); state = copy.myGetState(); } public String getType() { return type; } public String getState() { return state; } } }