/*
* $Id$
*
* Copyright (c) 2000-2003 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.configure;
import java.awt.Component;
import java.awt.Font;
import java.awt.Frame;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPopupMenu;
import javax.swing.JTree;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;
import VASSAL.build.Buildable;
import VASSAL.build.Builder;
import VASSAL.build.Configurable;
import VASSAL.build.GameModule;
import VASSAL.build.IllegalBuildException;
import VASSAL.build.module.Plugin;
import VASSAL.build.module.documentation.HelpFile;
import VASSAL.build.module.documentation.HelpWindow;
import VASSAL.build.module.map.boardPicker.board.mapgrid.Zone;
import VASSAL.build.module.properties.GlobalProperties;
import VASSAL.build.module.properties.GlobalProperty;
import VASSAL.build.module.properties.ZoneProperty;
import VASSAL.build.widget.CardSlot;
import VASSAL.build.widget.PieceSlot;
import VASSAL.counters.MassPieceLoader;
import VASSAL.i18n.Resources;
import VASSAL.i18n.TranslateAction;
import VASSAL.launch.EditorWindow;
import VASSAL.tools.ErrorDialog;
import VASSAL.tools.ReflectionUtils;
import VASSAL.tools.menu.MenuManager;
/**
* This is the Configuration Tree that appears in the Configuration window
* when editing a VASSAL module. Each node in the tree structure is a
* {@link VASSAL.build.Configurable} object, whose child nodes are obtained
* via {@link VASSAL.build.Configurable#getConfigureComponents}.
*/
public class ConfigureTree extends JTree implements PropertyChangeListener, MouseListener, MouseMotionListener, TreeSelectionListener {
private static final long serialVersionUID = 1L;
protected Map<Configurable, DefaultMutableTreeNode> nodes =
new HashMap<Configurable, DefaultMutableTreeNode>();
protected DefaultMutableTreeNode copyData;
protected DefaultMutableTreeNode cutData;
protected HelpWindow helpWindow;
protected EditorWindow editorWindow;
protected Configurable selected;
protected int selectedRow;
protected String moveCmd;
protected String deleteCmd;
protected String pasteCmd;
protected String copyCmd;
protected String cutCmd;
protected String helpCmd;
protected String propertiesCmd;
protected String translateCmd;
protected KeyStroke cutKey;
protected KeyStroke copyKey;
protected KeyStroke pasteKey;
protected KeyStroke deleteKey;
protected KeyStroke moveKey;
protected KeyStroke helpKey;
protected KeyStroke propertiesKey;
protected KeyStroke translateKey;
protected Action cutAction;
protected Action copyAction;
protected Action pasteAction;
protected Action deleteAction;
protected Action moveAction;
protected Action propertiesAction;
protected Action translateAction;
protected Action helpAction;
public static Font POPUP_MENU_FONT = new Font("Dialog", 0, 11);
protected static List<AdditionalComponent> additionalComponents =
new ArrayList<AdditionalComponent>();
/** Creates new ConfigureTree */
public ConfigureTree(Configurable root, HelpWindow helpWindow) {
this(root, helpWindow, null);
}
public ConfigureTree(Configurable root, HelpWindow helpWindow, EditorWindow editorWindow) {
toggleClickCount = 3;
this.helpWindow = helpWindow;
this.editorWindow = editorWindow;
setShowsRootHandles(true);
setModel(new DefaultTreeModel(buildTreeNode(root)));
setCellRenderer(buildRenderer());
addMouseListener(this);
addMouseMotionListener(this);
addTreeSelectionListener(this);
moveCmd = Resources.getString("Editor.move"); //$NON-NLS-1$
deleteCmd = Resources.getString("Editor.delete"); //$NON-NLS-1$
pasteCmd = Resources.getString("Editor.paste"); //$NON-NLS-1$
copyCmd = Resources.getString("Editor.copy"); //$NON-NLS-1$
cutCmd = Resources.getString("Editor.cut"); //$NON-NLS-1$
propertiesCmd = Resources.getString("Editor.ModuleEditor.properties"); //$NON-NLS-1$
translateCmd = Resources.getString("Editor.ModuleEditor.translate"); //$NON-NLS-1$
helpCmd = Resources.getString("Editor.ModuleEditor.component_help"); //$NON-NLS-1$
int mask = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask();
cutKey = KeyStroke.getKeyStroke(KeyEvent.VK_X, mask);
copyKey = KeyStroke.getKeyStroke(KeyEvent.VK_C, mask);
pasteKey = KeyStroke.getKeyStroke(KeyEvent.VK_V, mask);
deleteKey = KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0);
moveKey = KeyStroke.getKeyStroke(KeyEvent.VK_M, mask);
propertiesKey = KeyStroke.getKeyStroke(KeyEvent.VK_P, mask);
translateKey = KeyStroke.getKeyStroke(KeyEvent.VK_T, mask);
helpKey = KeyStroke.getKeyStroke(KeyEvent.VK_F1, 0);
copyAction = new KeyAction(copyCmd, copyKey);
pasteAction = new KeyAction(pasteCmd, pasteKey);
cutAction = new KeyAction(cutCmd, cutKey);
deleteAction = new KeyAction(deleteCmd, deleteKey);
moveAction = new KeyAction(moveCmd, moveKey);
propertiesAction = new KeyAction(propertiesCmd, propertiesKey);
translateAction = new KeyAction(translateCmd, translateKey);
helpAction = new KeyAction(helpCmd, helpKey);
/*
* Cut, Copy and Paste will not work unless I add them to the JTree input and action maps. Why??? All the others
* work fine.
*/
getInputMap().put(cutKey, cutCmd);
getInputMap().put(copyKey, copyCmd);
getInputMap().put(pasteKey, pasteCmd);
getInputMap().put(deleteKey, deleteCmd);
getActionMap().put(cutCmd, cutAction);
getActionMap().put(copyCmd, copyAction);
getActionMap().put(pasteCmd, pasteAction);
getActionMap().put(deleteCmd, deleteAction);
this.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
}
public JFrame getFrame() {
return editorWindow;
}
class KeyAction extends AbstractAction {
private static final long serialVersionUID = 1L;
protected String actionName;
public KeyAction(String name, KeyStroke key) {
super(name);
actionName = name;
putValue(Action.ACCELERATOR_KEY, key);
}
public void actionPerformed(ActionEvent e) {
doKeyAction(actionName);
}
}
protected Renderer buildRenderer() {
return new Renderer();
}
/**
* Tell our enclosing EditorWindow that we are now clean
* or dirty.
*
* @param changed true = state is not dirty
*/
protected void notifyStateChanged(boolean changed) {
if (editorWindow != null) {
editorWindow.treeStateChanged(changed);
}
}
protected Configurable getTarget(int x, int y) {
TreePath path = getPathForLocation(x, y);
Configurable target = null;
if (path != null) {
target = (Configurable) ((DefaultMutableTreeNode) path.getLastPathComponent()).getUserObject();
}
return target;
}
protected DefaultMutableTreeNode buildTreeNode(Configurable c) {
c.addPropertyChangeListener(this);
final DefaultMutableTreeNode node = new DefaultMutableTreeNode(c);
final Configurable[] children = c.getConfigureComponents();
for (Configurable child : children) {
if (! (child instanceof Plugin)) { // Hide Plug-ins
node.add(buildTreeNode(child));
}
}
nodes.put(c, node);
return node;
}
protected void addAction(JPopupMenu menu, Action a) {
if (a != null) {
menu.add(a).setFont(POPUP_MENU_FONT);
}
}
private void addActionGroup(JPopupMenu menu, ArrayList<Action> l) {
boolean empty = true;
for (Action a : l) {
if (a != null) {
menu.add(a).setFont(POPUP_MENU_FONT);
empty = false;
}
}
if (!empty) {
menu.addSeparator();
}
l.clear();
}
protected JPopupMenu buildPopupMenu(final Configurable target) {
final JPopupMenu popup = new JPopupMenu();
final ArrayList<Action> l = new ArrayList<Action>();
l.add(buildEditAction(target));
l.add(buildEditPiecesAction(target));
addActionGroup(popup, l);
l.add(buildTranslateAction(target));
addActionGroup(popup, l);
l.add(buildHelpAction(target));
addActionGroup(popup, l);
l.add(buildDeleteAction(target));
l.add(buildCutAction(target));
l.add(buildCopyAction(target));
l.add(buildPasteAction(target));
l.add(buildMoveAction(target));
addActionGroup(popup, l);
for (Action a : buildAddActionsFor(target)) {
addAction(popup, a);
}
if (hasChild(target, PieceSlot.class) || hasChild(target, CardSlot.class)) {
addAction(popup, buildMassPieceLoaderAction(target));
}
addAction(popup, buildImportAction(target));
return popup;
}
protected Action buildMoveAction(final Configurable target) {
Action a = null;
if (getTreeNode(target).getParent() != null) {
a = new AbstractAction(moveCmd) {
private static final long serialVersionUID = 1L;
public void actionPerformed(ActionEvent e) {
final JDialog d = new JDialog((Frame) SwingUtilities.getAncestorOfClass(Frame.class, ConfigureTree.this), true);
d.setTitle(target.getConfigureName() == null ? moveCmd : moveCmd + " " + target.getConfigureName());
d.setLayout(new BoxLayout(d.getContentPane(), BoxLayout.Y_AXIS));
Box box = Box.createHorizontalBox();
box.add(new JLabel("Move to position"));
box.add(Box.createHorizontalStrut(10));
final JComboBox select = new JComboBox();
TreeNode parentNode = getTreeNode(target).getParent();
for (int i = 0; i < parentNode.getChildCount(); ++i) {
Configurable c = (Configurable) ((DefaultMutableTreeNode) parentNode.getChildAt(i)).getUserObject();
String name = (c.getConfigureName() != null ? c.getConfigureName() : "") + " [" + getConfigureName(c.getClass()) + "]";
select.addItem((i + 1) + ": " + name);
}
final DefaultMutableTreeNode targetNode = getTreeNode(target);
final int currentIndex = targetNode.getParent().getIndex(targetNode);
select.setSelectedIndex(currentIndex);
box.add(select);
JButton ok = new JButton(Resources.getString(Resources.OK));
ok.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
int index = select.getSelectedIndex();
if (currentIndex != index) {
Configurable parent = getParent(targetNode);
if (remove(parent, target)) {
insert(parent, target, index);
}
}
d.dispose();
}
});
d.add(box);
d.add(ok);
d.pack();
d.setLocationRelativeTo(d.getParent());
d.setVisible(true);
}
};
}
return a;
}
protected Action buildCutAction(final Configurable target) {
Action a = null;
if (getTreeNode(target).getParent() != null) {
a = new AbstractAction(cutCmd) {
private static final long serialVersionUID = 1L;
public void actionPerformed(ActionEvent e) {
cutData = getTreeNode(target);
copyData = null;
updateEditMenu();
}
};
}
return a;
}
protected Action buildCopyAction(final Configurable target) {
Action a = null;
if (getTreeNode(target).getParent() != null) {
a = new AbstractAction(copyCmd) {
private static final long serialVersionUID = 1L;
public void actionPerformed(ActionEvent e) {
copyData = getTreeNode(target);
cutData = null;
updateEditMenu();
}
};
}
return a;
}
protected Action buildPasteAction(final Configurable target) {
final Action a = new AbstractAction(pasteCmd) {
private static final long serialVersionUID = 1L;
public void actionPerformed(ActionEvent e) {
if (cutData != null) {
final DefaultMutableTreeNode targetNode = getTreeNode(target);
final Configurable cutObj = (Configurable) cutData.getUserObject();
final Configurable convertedCutObj = convertChild(target, cutObj);
if (remove(getParent(cutData), cutObj)) {
insert(target, convertedCutObj, targetNode.getChildCount());
}
copyData = getTreeNode(convertedCutObj);
}
else if (copyData != null) {
final Configurable copyBase = (Configurable) copyData.getUserObject();
Configurable clone = null;
try {
clone = convertChild(target, copyBase.getClass().getConstructor().newInstance());
}
catch (Throwable t) {
ReflectionUtils.handleNewInstanceFailure(t, copyBase.getClass());
}
if (clone != null) {
clone.build(copyBase.getBuildElement(Builder.createNewDocument()));
insert(target, clone, getTreeNode(target).getChildCount());
updateGpIds(clone);
}
}
cutData = null;
updateEditMenu();
}
};
a.setEnabled(isValidPasteTarget(target));
return a;
}
protected boolean isValidPasteTarget(Configurable target) {
return (cutData != null &&
isValidParent(target, (Configurable) cutData.getUserObject())) ||
(copyData != null &&
isValidParent(target, (Configurable) copyData.getUserObject()));
}
/**
* Some components need to be converted to a new type before insertion.
*
* Currently this is used to allow cut and paste of CardSlots and PieceSlots
* between Decks and GamePiece Palette components.
*
* @param parent
* @param child
* @return new Child
*/
protected Configurable convertChild(Configurable parent, Configurable child) {
if (child.getClass() == PieceSlot.class && isAllowedChildClass(parent, CardSlot.class)) {
return new CardSlot((PieceSlot) child);
}
else if (child.getClass() == CardSlot.class && isAllowedChildClass(parent, PieceSlot.class)) {
return new PieceSlot((CardSlot) child);
}
else {
return child;
}
}
protected boolean isAllowedChildClass(Configurable parent, Class<?> childClass) {
final Class<?>[] allowableClasses = parent.getAllowableConfigureComponents();
for (int i = 0; i < allowableClasses.length; i++) {
if (allowableClasses[i] == childClass) {
return true;
}
}
return false;
}
/**
* Allocate new PieceSlot Id's to any PieceSlot sub-components
*
* @param c Configurable to update
*/
public void updateGpIds(Configurable c) {
if (c instanceof PieceSlot) {
((PieceSlot) c).updateGpId(GameModule.getGameModule());
}
else {
for (Configurable comp : c.getConfigureComponents()) updateGpIds(comp);
}
}
protected Action buildImportAction(final Configurable target) {
Action a = new AbstractAction("Add Imported Class") {
private static final long serialVersionUID = 1L;
public void actionPerformed(ActionEvent evt) {
final Configurable child = importConfigurable();
if (child != null) {
try {
child.build(null);
final Configurable c = target;
if (child.getConfigurer() != null) {
PropertiesWindow w = new PropertiesWindow((Frame) SwingUtilities.getAncestorOfClass(Frame.class, ConfigureTree.this), false, child, helpWindow) {
private static final long serialVersionUID = 1L;
public void save() {
super.save();
insert(c, child, getTreeNode(c).getChildCount());
}
public void cancel() {
dispose();
}
};
w.setVisible(true);
}
else {
insert(c, child, getTreeNode(c).getChildCount());
}
}
// FIXME: review error message
catch (Exception ex) {
JOptionPane.showMessageDialog(getTopLevelAncestor(), "Error adding " + getConfigureName(child) + " to " + getConfigureName(target) + "\n"
+ ex.getMessage(), "Illegal configuration", JOptionPane.ERROR_MESSAGE);
}
}
}
};
return a;
}
protected Action buildMassPieceLoaderAction(final Configurable target) {
Action a = null;
final ConfigureTree tree = this;
if (getTreeNode(target).getParent() != null) {
String desc = "Add Multiple " + (hasChild(target, CardSlot.class) ? "Cards" : "Pieces");
a = new AbstractAction(desc) {
private static final long serialVersionUID = 1L;
public void actionPerformed(ActionEvent e) {
new MassPieceLoader(tree, target).load();
}
};
}
return a;
}
protected boolean hasChild(Configurable parent, Class<?> childClass) {
for (Class<?> c : parent.getAllowableConfigureComponents()) {
if (c.equals(childClass)) {
return true;
}
}
return false;
}
protected List<Action> buildAddActionsFor(final Configurable target) {
final ArrayList<Action> l = new ArrayList<Action>();
for (Class<? extends Buildable> newConfig :
target.getAllowableConfigureComponents()) {
l.add(buildAddAction(target, newConfig));
}
for (AdditionalComponent add : additionalComponents) {
if (target.getClass().equals(add.getParent())) {
final Class<? extends Buildable> newConfig = add.getChild();
l.add(buildAddAction(target, newConfig));
}
}
return l;
}
/**
* @deprecated Use {@link #buildAddActionsFor(final Configurable)} instead.
*/
@Deprecated
protected Enumeration<Action> buildAddActions(final Configurable target) {
return Collections.enumeration(buildAddActionsFor(target));
}
protected Action buildAddAction(final Configurable target, final Class<? extends Buildable> newConfig) {
AbstractAction action = new AbstractAction("Add " + getConfigureName(newConfig)) {
private static final long serialVersionUID = 1L;
public void actionPerformed(ActionEvent evt) {
Configurable ch = null;
try {
ch = (Configurable) newConfig.getConstructor().newInstance();
}
catch (Throwable t) {
ReflectionUtils.handleNewInstanceFailure(t, newConfig);
}
if (ch != null) {
final Configurable child = ch;
child.build(null);
if (child instanceof PieceSlot) {
((PieceSlot) child).updateGpId(GameModule.getGameModule());
}
final Configurable c = target;
if (child.getConfigurer() != null) {
if (insert(target, child, getTreeNode(target).getChildCount())) {
PropertiesWindow w = new PropertiesWindow((Frame) SwingUtilities.getAncestorOfClass(Frame.class, ConfigureTree.this), false, child, helpWindow) {
private static final long serialVersionUID = 1L;
public void save() {
super.save();
}
public void cancel() {
ConfigureTree.this.remove(c, child);
dispose();
}
};
w.setVisible(true);
}
}
else {
insert(c, child, getTreeNode(c).getChildCount());
}
}
}
};
return action;
}
protected Action buildHelpAction(final Configurable target) {
Action showHelp;
HelpFile helpFile = target.getHelpFile();
if (helpFile == null) {
showHelp = new ShowHelpAction(null,null);
showHelp.setEnabled(false);
}
else {
showHelp = new ShowHelpAction(helpFile.getContents(), null);
}
return showHelp;
}
protected Action buildCloneAction(final Configurable target) {
final DefaultMutableTreeNode targetNode = getTreeNode(target);
if (targetNode.getParent() != null) {
return new AbstractAction("Clone") {
private static final long serialVersionUID = 1L;
public void actionPerformed(ActionEvent evt) {
Configurable clone = null;
try {
clone = target.getClass().getConstructor().newInstance();
}
catch (Throwable t) {
ReflectionUtils.handleNewInstanceFailure(t, target.getClass());
}
if (clone != null) {
clone.build(target.getBuildElement(Builder.createNewDocument()));
insert(getParent(targetNode), clone,
targetNode.getParent().getIndex(targetNode) + 1);
}
}
};
}
else {
return null;
}
}
protected Configurable getParent(final DefaultMutableTreeNode targetNode) {
DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode) targetNode.getParent();
return parentNode == null ? null : (Configurable) parentNode.getUserObject();
}
protected Action buildDeleteAction(final Configurable target) {
final DefaultMutableTreeNode targetNode = getTreeNode(target);
final Configurable parent = getParent(targetNode);
if (targetNode.getParent() != null) {
return new AbstractAction(deleteCmd) {
private static final long serialVersionUID = 1L;
public void actionPerformed(ActionEvent evt) {
int row = selectedRow;
remove(parent, target);
if (row < getRowCount()) {
setSelectionRow(row);
}
else {
setSelectionRow(row - 1);
}
}
};
}
else {
return null;
}
}
protected Action buildEditPiecesAction(final Configurable target) {
if (canContainGamePiece(target)) {
return new EditContainedPiecesAction(target);
}
else {
return null;
}
}
protected Action buildEditAction(final Configurable target) {
return new EditPropertiesAction(target, helpWindow, (Frame) SwingUtilities.getAncestorOfClass(Frame.class, this), this);
}
protected Action buildTranslateAction(final Configurable target) {
Action a = new TranslateAction(target, helpWindow, this);
a.setEnabled(target.getI18nData().isTranslatable());
return a;
}
public boolean canContainGamePiece(final Configurable target) {
boolean canContainPiece = false;
for (Class<?> c : target.getAllowableConfigureComponents()) {
if (VASSAL.build.widget.PieceSlot.class.isAssignableFrom(c)) {
canContainPiece = true;
break;
}
}
return canContainPiece;
}
protected boolean remove(Configurable parent, Configurable child) {
try {
child.removeFrom(parent);
parent.remove(child);
((DefaultTreeModel) getModel()).removeNodeFromParent(getTreeNode(child));
notifyStateChanged(true);
return true;
}
// FIXME: review error message
catch (IllegalBuildException err) {
JOptionPane.showMessageDialog(getTopLevelAncestor(), "Cannot delete " + getConfigureName(child) + " from " + getConfigureName(parent) + "\n"
+ err.getMessage(), "Illegal configuration", JOptionPane.ERROR_MESSAGE);
return false;
}
}
protected boolean insert(Configurable parent, Configurable child, int index) {
Configurable theChild = child;
// Convert subclasses of GlobalProperty to an actual GlobalProperty before inserting into the GlobalProperties container
if (parent.getClass() == GlobalProperties.class && child.getClass() == ZoneProperty.class) {
theChild = new GlobalProperty((GlobalProperty) child);
}
if (parent.getClass() == Zone.class && child.getClass() == GlobalProperty.class) {
theChild = new ZoneProperty((GlobalProperty) child);
}
DefaultMutableTreeNode childNode = buildTreeNode(theChild);
DefaultMutableTreeNode parentNode = getTreeNode(parent);
Configurable[] oldContents = parent.getConfigureComponents();
boolean succeeded = true;
ArrayList<Configurable> moveToBack = new ArrayList<Configurable>();
for (int i = index; i < oldContents.length; ++i) {
try {
oldContents[i].removeFrom(parent);
parent.remove(oldContents[i]);
}
// FIXME: review error message
catch (IllegalBuildException err) {
JOptionPane.showMessageDialog(getTopLevelAncestor(), "Can't insert " + getConfigureName(theChild) + " before " + getConfigureName(oldContents[i]),
"Illegal configuration", JOptionPane.ERROR_MESSAGE);
for (int j = index; j < i; ++j) {
parent.add(oldContents[j]);
oldContents[j].addTo(parent);
}
return false;
}
moveToBack.add(oldContents[i]);
}
try {
theChild.addTo(parent);
parent.add(theChild);
parentNode.insert(childNode, index);
int[] childI = new int[1];
childI[0] = index;
((DefaultTreeModel) getModel()).nodesWereInserted(parentNode, childI);
}
// FIXME: review error message
catch (IllegalBuildException err) {
JOptionPane.showMessageDialog(getTopLevelAncestor(), "Can't add " + getConfigureName(child) + "\n" + err.getMessage(), "Illegal configuration",
JOptionPane.ERROR_MESSAGE);
succeeded = false;
}
for (Configurable c : moveToBack) {
parent.add(c);
c.addTo(parent);
}
notifyStateChanged(true);
return succeeded;
}
public void propertyChange(PropertyChangeEvent evt) {
DefaultMutableTreeNode newValue = getTreeNode((Configurable) evt.getSource());
((DefaultTreeModel) getModel()).nodeChanged(newValue);
}
static class Renderer extends javax.swing.tree.DefaultTreeCellRenderer {
private static final long serialVersionUID = 1L;
@Override
public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus) {
if (value instanceof DefaultMutableTreeNode) {
final Configurable c =
(Configurable) ((DefaultMutableTreeNode) value).getUserObject();
if (c != null) {
leaf = c.getAllowableConfigureComponents().length == 0;
value = (c.getConfigureName() != null ? c.getConfigureName() : "") +
" [" + getConfigureName(c.getClass()) + "]";
}
}
return super.getTreeCellRendererComponent(
tree, value, sel, expanded, leaf, row, hasFocus
);
}
}
/**
* Returns the name of the class for display purposes. Reflection is
* used to call <code>getConfigureTypeName()</code>, which should be
* a static method if it exists in the given class. (This is necessary
* because static methods are not permitted in interfaces.)
*
* @param the class whose configure name will be returned
* @return the configure name of the class
*/
public static String getConfigureName(Class<?> c) {
try {
return (String) c.getMethod("getConfigureTypeName").invoke(null);
}
catch (NoSuchMethodException e) {
// Ignore. This is normal, since some classes won't have this method.
}
catch (IllegalAccessException e) {
ErrorDialog.bug(e);
}
catch (IllegalArgumentException e) {
ErrorDialog.bug(e);
}
catch (InvocationTargetException e) {
ErrorDialog.bug(e);
}
catch (NullPointerException e) {
ErrorDialog.bug(e);
}
catch (ExceptionInInitializerError e) {
ErrorDialog.bug(e);
}
return c.getName().substring(c.getName().lastIndexOf(".") + 1);
}
public static String getConfigureName(Configurable c) {
if (c.getConfigureName() != null && c.getConfigureName().length() > 0) {
return c.getConfigureName();
}
else {
return getConfigureName(c.getClass());
}
}
protected Configurable importConfigurable() {
final String className = JOptionPane.showInputDialog(
getTopLevelAncestor(),
"Enter fully-qualified name of Java class to import");
if (className == null) return null;
Object o = null;
try {
o = GameModule.getGameModule().getDataArchive()
.loadClass(className).getConstructor().newInstance();
}
catch (Throwable t) {
ReflectionUtils.handleImportClassFailure(t, className);
}
if (o == null) return null;
if (o instanceof Configurable) return (Configurable) o;
ErrorDialog.show("Error.not_a_configurable", className);
return null;
}
public void mousePressed(MouseEvent e) {
}
public void mouseReleased(MouseEvent e) {
Configurable target = getTarget(e.getX(), e.getY());
if (target != null) {
if (e.getClickCount() == 2 && !e.isMetaDown()) {
if (target.getConfigurer() != null) {
Action a = buildEditAction(target);
if (a != null) {
a.actionPerformed(new ActionEvent(e.getSource(), ActionEvent.ACTION_PERFORMED, "Edit"));
}
}
}
else if (e.isMetaDown()) {
setSelectionRow(getClosestRowForLocation(e.getX(), e.getY()));
JPopupMenu popup = buildPopupMenu(target);
popup.show(ConfigureTree.this, e.getX(), e.getY());
popup.addPopupMenuListener(new javax.swing.event.PopupMenuListener() {
public void popupMenuCanceled(javax.swing.event.PopupMenuEvent evt) {
repaint();
}
public void popupMenuWillBecomeInvisible(javax.swing.event.PopupMenuEvent evt) {
repaint();
}
public void popupMenuWillBecomeVisible(javax.swing.event.PopupMenuEvent evt) {
}
});
}
}
}
/*
* protected void performDrop(Configurable target) { DefaultMutableTreeNode dragNode = getTreeNode(dragging);
* DefaultMutableTreeNode targetNode = getTreeNode(target); Configurable parent = null; int index = 0; if
* (isValidParent(target, dragging)) { parent = target; index = targetNode.getChildCount(); if (dragNode.getParent() ==
* targetNode) { index--; } } else if (targetNode.getParent() != null && isValidParent(getParent(targetNode),
* dragging)) { parent = (Configurable) ((DefaultMutableTreeNode) targetNode.getParent()).getUserObject(); index =
* targetNode.getParent().getIndex(targetNode); } if (parent != null) { remove(getParent(dragNode), dragging);
* insert(parent, dragging, index); } dragging = null; }
*/
public DefaultMutableTreeNode getTreeNode(Configurable target) {
return nodes.get(target);
}
public void mouseDragged(MouseEvent evt) {
}
protected boolean isValidParent(Configurable parent, Configurable child) {
if (parent != null && child != null) {
final Class<?> c[] = parent.getAllowableConfigureComponents();
for (int i = 0; i < c.length; ++i) {
if (c[i].isAssignableFrom(child.getClass()) ||
((c[i] == CardSlot.class) && (child.getClass() == PieceSlot.class)) || // Allow PieceSlots to be pasted to Decks
((c[i] == ZoneProperty.class) && (child.getClass() == GlobalProperty.class)) // Allow Global Properties to be saved as Zone Properties
) {
return true;
}
}
}
return false;
}
public void mouseClicked(MouseEvent e) {
}
public void mouseEntered(MouseEvent e) {
}
public void mouseExited(MouseEvent e) {
}
public void mouseMoved(MouseEvent e) {
}
/*
* Refresh the display of a node
*/
public void nodeUpdated(Configurable target) {
DefaultMutableTreeNode node = getTreeNode(target);
Configurable parent = getParent(node);
if (remove(parent, target)) {
insert(parent, target, 0);
}
}
/**
* Configurers that add or remove their own children directly should implement the Mutable interface so that
* ConfigureTree can refresh the changed node.
*/
public interface Mutable {
}
/**
* Build an AddAction and execute it to request a new component from the user
*
* @param parent
* Target Parent
* @param type
* Type to add
*/
public void externalInsert(Configurable parent, Configurable child) {
insert(parent, child, getTreeNode(parent).getChildCount());
}
public Action getHelpAction() {
return helpAction;
}
public void populateEditMenu(EditorWindow ew) {
final MenuManager mm = MenuManager.getInstance();
mm.addAction("Editor.delete", deleteAction);
mm.addAction("Editor.cut", cutAction);
mm.addAction("Editor.copy", copyAction);
mm.addAction("Editor.paste", pasteAction);
mm.addAction("Editor.move", moveAction);
mm.addAction("Editor.ModuleEditor.properties", propertiesAction);
mm.addAction("Editor.ModuleEditor.translate", translateAction);
updateEditMenu();
}
/**
* Handle main Edit menu selections/accelerators
*
* @param action
* Edit command name
*/
protected void doKeyAction(String action) {
DefaultMutableTreeNode targetNode = (DefaultMutableTreeNode) this.getLastSelectedPathComponent();
if (targetNode != null) {
Configurable target = (Configurable) targetNode.getUserObject();
Action a = null;
if (cutCmd.equals(action)) {
a = buildCutAction(target);
}
else if (copyCmd.equals(action)) {
a = buildCopyAction(target);
}
else if (pasteCmd.equals(action) || action.equals(pasteKey.getKeyChar())) {
a = buildPasteAction(target);
}
else if (deleteCmd.equals(action)) {
a = buildDeleteAction(target);
}
else if (moveCmd.equals(action)) {
a = buildMoveAction(target);
}
else if (propertiesCmd.equals(action)) {
a = buildEditAction(target);
}
else if (translateCmd.equals(action)) {
a = buildTranslateAction(target);
}
else if (helpCmd.equals(action)) {
a = buildHelpAction(target);
}
if (a != null) {
a.actionPerformed(null);
}
}
}
/**
* Tree selection changed, record info about the currently selected component
*/
public void valueChanged(TreeSelectionEvent e) {
selected = null;
TreePath path = e.getPath();
if (path != null) {
selected = (Configurable) ((DefaultMutableTreeNode) path.getLastPathComponent()).getUserObject();
selectedRow = getRowForPath(path);
updateEditMenu();
}
}
protected void updateEditMenu() {
deleteAction.setEnabled(selected != null);
cutAction.setEnabled(selected != null);
copyAction.setEnabled(selected != null);
pasteAction.setEnabled(selected != null && isValidPasteTarget(selected));
moveAction.setEnabled(selected != null);
propertiesAction.setEnabled(selected != null &&
selected.getConfigurer() != null);
translateAction.setEnabled(selected != null);
}
/**
* Find the parent Configurable of a specified Configurable
*
* @param target
* @return parent
*/
protected Configurable getParent(Configurable target) {
DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode) getTreeNode(target).getParent();
return (Configurable) parentNode.getUserObject();
}
/**
* Record additional available components to add to the popup menu.
*
* @param parent
* @param child
*/
public static void addAdditionalComponent(Class<? extends Buildable> parent, Class<? extends Buildable> child) {
additionalComponents.add(new AdditionalComponent(parent, child));
}
protected static class AdditionalComponent {
Class<? extends Buildable> parent;
Class<? extends Buildable> child;
public AdditionalComponent(Class<? extends Buildable> p, Class<? extends Buildable> c) {
parent = p;
child = c;
}
public Class<? extends Buildable> getParent() {
return parent;
}
public Class<? extends Buildable> getChild() {
return child;
}
}
}