/*
* Copyright (C) 2007, 2010 IsmAvatar <IsmAvatar@gmail.com>
* Copyright (C) 2007, 2008 Clam <clamisgood@gmail.com>
* Copyright (C) 2007, 2008, 2009 Quadduc <quadduc@gmail.com>
* Copyright (C) 2013, Robert B. Colton
*
* This file is part of LateralGM.
* LateralGM is free software and comes with ABSOLUTELY NO WARRANTY.
* See LICENSE for details.
*/
package org.lateralgm.subframes;
import static java.lang.Integer.MAX_VALUE;
import static javax.swing.GroupLayout.DEFAULT_SIZE;
import static javax.swing.GroupLayout.PREFERRED_SIZE;
import static org.lateralgm.main.Util.deRef;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.Point;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.event.ActionEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import javax.swing.BorderFactory;
import javax.swing.DropMode;
import javax.swing.GroupLayout;
import javax.swing.GroupLayout.Alignment;
import javax.swing.GroupLayout.ParallelGroup;
import javax.swing.GroupLayout.SequentialGroup;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.TransferHandler;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;
import org.lateralgm.components.ActionList;
import org.lateralgm.components.ActionListEditor;
import org.lateralgm.components.CodeTextArea;
import org.lateralgm.components.NumberField;
import org.lateralgm.components.ResourceMenu;
import org.lateralgm.components.ActionList.ActionListModel;
import org.lateralgm.components.impl.EventNode;
import org.lateralgm.components.impl.ResNode;
import org.lateralgm.components.mdi.MDIFrame;
import org.lateralgm.main.LGM;
import org.lateralgm.main.Listener;
import org.lateralgm.main.Prefs;
import org.lateralgm.main.Util;
import org.lateralgm.messages.Messages;
import org.lateralgm.resources.GmObject;
import org.lateralgm.resources.GmObject.PGmObject;
import org.lateralgm.resources.GmObject.ParentLoopException;
import org.lateralgm.resources.ResourceReference;
import org.lateralgm.resources.Sprite;
import org.lateralgm.resources.library.LibAction;
import org.lateralgm.resources.library.LibManager;
import org.lateralgm.resources.sub.Action;
import org.lateralgm.resources.sub.Event;
import org.lateralgm.resources.sub.MainEvent;
import org.lateralgm.util.PropertyMap.PropertyUpdateEvent;
import org.lateralgm.util.PropertyMap.PropertyUpdateListener;
public class GmObjectFrame extends InstantiableResourceFrame<GmObject,PGmObject> implements
TreeSelectionListener
{
private static final long serialVersionUID = 1L;
private static final ImageIcon INFO_ICON = LGM.getIconForKey("GmObjectFrame.INFO"); //$NON-NLS-1$
public ResourceMenu<Sprite> sprite;
public JButton newSprite;
public JButton editSprite;
public JCheckBox visible;
public JCheckBox solid;
public NumberField depth;
public JCheckBox persistent;
public ResourceMenu<GmObject> parent;
public ResourceMenu<Sprite> mask;
public JButton information;
public EventTree events;
public JButton eventModify;
public JButton eventEdit;
public JButton eventDelete;
public JMenuItem eventModifyItem;
public JMenuItem eventAddItem;
public JMenuItem eventReplaceItem;
public JMenuItem eventDuplicateItem;
public JMenuItem eventEditItem;
public JMenuItem eventDeleteItem;
public EventGroupNode rootEvent;
private MListener mListener = new MListener();
public ActionList actions;
public CodeTextArea code;
private JComponent editor;
private ResourceInfoFrame infoFrame;
private DefaultMutableTreeNode lastValidEventSelection;
private JCheckBox physics;
private JPanel phyPane;
private PropertyUpdateListener<PGmObject> propUpdateListener;
// if drag and drop is not enabled the frame will not create or show
// the action list editor but still create the action list, and add
// an extra edit button for events, which when clicked checks the first
// action of the action list to be a code window and if it is opens it
// otherwise it creates one and opens it
// this was the safest way I could think to do it, because it could get
// tricky opening a DND game when you have it turned off, so I abstracted it
// so that they are still there and you can see them if you just go to
// the prefs panel and turn em back on, it will ensure the system doesnt
// mess anything up or screw up somebodies game without giving them
// a chance to fix it, very safe - Robert B. Colton
public GmObjectFrame(GmObject res, ResNode node)
{
super(res,node);
GroupLayout layout = new GroupLayout(getContentPane());
setLayout(layout);
JPanel propPane = makePropertiesPane();
phyPane = makePhysicsPane();
JPanel evtPane = new JPanel(new BorderLayout());
JLabel lab = new JLabel(Messages.getString("GmObjectFrame.EVENTS")); //$NON-NLS-1$
evtPane.add(lab,BorderLayout.NORTH);
makeEventTree(res);
JScrollPane scroll = new JScrollPane(events);
//if (Prefs.enableDragAndDrop) {
scroll.setPreferredSize(new Dimension(140,260));
//} else {
//scroll.setPreferredSize(new Dimension(300,260));
//}
evtPane.add(scroll,BorderLayout.CENTER);
JPanel eventButtonPane = new JPanel(new BorderLayout());
eventModify = new JButton(Messages.getString("GmObjectFrame.MODIFY")); //$NON-NLS-1
eventModify.addActionListener(this);
eventModify.setToolTipText(Messages.getString("GmObjectFrame.MODIFY_EVENT")); //$NON-NLS-1$
eventEdit = new JButton(Messages.getString("GmObjectFrame.EDIT")); //$NON-NLS-1
eventEdit.addActionListener(this);
eventEdit.setToolTipText(Messages.getString("GmObjectFrame.EDIT_EVENT")); //$NON-NLS-1$
eventDelete = new JButton(Messages.getString("GmObjectFrame.DELETE")); //$NON-NLS-1$
eventDelete.addActionListener(this);
eventDelete.setToolTipText(Messages.getString("GmObjectFrame.DELETE_EVENT")); //$NON-NLS-1$
eventButtonPane.setLayout(new GridLayout());
eventButtonPane.add(eventModify);
eventButtonPane.add(eventEdit);
eventButtonPane.add(eventDelete);
evtPane.add(eventButtonPane,BorderLayout.SOUTH);
actions = new ActionList(this);
if (Prefs.enableDragAndDrop)
{
editor = new ActionListEditor(actions);
}
ParallelGroup pg = null;
SequentialGroup sg = layout.createSequentialGroup();
if (Prefs.rightOrientation) {
if (Prefs.enableDragAndDrop)
{
sg.addComponent(editor);
}
sg.addComponent(evtPane)
/**/.addComponent(phyPane,DEFAULT_SIZE,PREFERRED_SIZE,PREFERRED_SIZE)
/**/.addComponent(propPane,DEFAULT_SIZE,PREFERRED_SIZE,PREFERRED_SIZE);
} else {
sg.addComponent(propPane,DEFAULT_SIZE,PREFERRED_SIZE,PREFERRED_SIZE)
/**/.addComponent(phyPane,DEFAULT_SIZE,PREFERRED_SIZE,PREFERRED_SIZE)
/**/.addComponent(evtPane);
if (Prefs.enableDragAndDrop)
{
sg.addComponent(editor);
}
}
layout.setHorizontalGroup(sg);
pg = layout.createParallelGroup()
/**/.addComponent(propPane)
/**/.addComponent(phyPane)
/**/.addComponent(evtPane);
if (Prefs.enableDragAndDrop)
{
pg.addComponent(editor);
}
layout.setVerticalGroup(pg);
pack();
phyPane.setVisible((Boolean) res.properties.get(PGmObject.PHYSICS_OBJECT));
propUpdateListener = new PropertyUpdateListener<PGmObject>()
{
public void updated(PropertyUpdateEvent<PGmObject> e)
{
if (e.key == PGmObject.PHYSICS_OBJECT)
{
phyPane.setVisible((Boolean) e.map.get(e.key));
}
}
};
res.properties.updateSource.addListener(propUpdateListener);
// Select first event
TreeNode event = (TreeNode) events.getModel().getRoot();
while (event.getChildCount() > 0)
event = event.getChildAt(0);
if (event != events.getModel().getRoot())
events.setSelectionPath(new TreePath(((DefaultMutableTreeNode) event).getPath()));
}
private JPanel makePhysicsPane()
{
JPanel panel = new JPanel();
panel.setBorder(BorderFactory.createTitledBorder(
Messages.getString("GmObjectFrame.PHYSICS_PROPERTIES")));
JCheckBox awakeCB = new JCheckBox(Messages.getString("GmObjectFrame.AWAKE"));
plf.make(awakeCB,PGmObject.PHYSICS_AWAKE);
JCheckBox kinematicCB = new JCheckBox(Messages.getString("GmObjectFrame.KINEMATIC"));
plf.make(kinematicCB,PGmObject.PHYSICS_KINEMATIC);
JCheckBox sensorCB = new JCheckBox(Messages.getString("GmObjectFrame.SENSOR"));
plf.make(sensorCB,PGmObject.PHYSICS_SENSOR);
JLabel densityLabel = new JLabel(Messages.getString("GmObjectFrame.DENSITY"));
NumberField densityField = new NumberField(0.0);
plf.make(densityField,PGmObject.PHYSICS_DENSITY);
JLabel restLabel = new JLabel(Messages.getString("GmObjectFrame.RESTITUTION"));
NumberField restField = new NumberField(0.0);
plf.make(restField,PGmObject.PHYSICS_RESTITUTION);
JLabel groupLabel = new JLabel(Messages.getString("GmObjectFrame.COLLISION_GROUP"));
NumberField groupField = new NumberField(0);
plf.make(groupField,PGmObject.PHYSICS_GROUP);
JLabel linearLabel = new JLabel(Messages.getString("GmObjectFrame.DAMPING_LINEAR"));
NumberField linearField = new NumberField(0.0);
plf.make(linearField,PGmObject.PHYSICS_DAMPING_LINEAR);
JLabel angularLabel = new JLabel(Messages.getString("GmObjectFrame.DAMPING_ANGULAR"));
NumberField angularField = new NumberField(0.0);
plf.make(angularField,PGmObject.PHYSICS_DAMPING_ANGULAR);
JLabel frictionLabel = new JLabel(Messages.getString("GmObjectFrame.FRICTION"));
NumberField frictionField = new NumberField(0.0);
plf.make(frictionField,PGmObject.PHYSICS_FRICTION);
JButton shapeBT = new JButton(Messages.getString("GmObjectFrame.COLLISION_SHAPE"));
GroupLayout layout = new GroupLayout(panel);
layout.setAutoCreateContainerGaps(true);
layout.setAutoCreateGaps(true);
panel.setLayout(layout);
layout.setHorizontalGroup(layout.createParallelGroup()
/**/.addComponent(shapeBT)
/**/.addGroup(layout.createSequentialGroup()
/* */.addComponent(awakeCB)
/* */.addComponent(kinematicCB))
/**/.addComponent(sensorCB)
/**/.addGroup(layout.createSequentialGroup()
/* */.addGroup(layout.createParallelGroup(Alignment.TRAILING)
/* */.addComponent(densityLabel)
/* */.addComponent(restLabel)
/* */.addComponent(groupLabel)
/* */.addComponent(linearLabel)
/* */.addComponent(angularLabel)
/* */.addComponent(frictionLabel))
/* */.addGroup(layout.createParallelGroup()
/* */.addComponent(densityField)
/* */.addComponent(restField)
/* */.addComponent(groupField)
/* */.addComponent(linearField)
/* */.addComponent(angularField)
/* */.addComponent(frictionField))));
layout.setVerticalGroup(layout.createSequentialGroup()
/**/.addComponent(shapeBT)
/**/.addGroup(layout.createParallelGroup()
/* */.addComponent(awakeCB)
/* */.addComponent(kinematicCB))
/**/.addComponent(sensorCB)
/**/.addGroup(layout.createParallelGroup(Alignment.CENTER)
/* */.addComponent(densityLabel)
/* */.addComponent(densityField, PREFERRED_SIZE, PREFERRED_SIZE, PREFERRED_SIZE))
/**/.addGroup(layout.createParallelGroup(Alignment.CENTER)
/* */.addComponent(restLabel)
/* */.addComponent(restField, PREFERRED_SIZE, PREFERRED_SIZE, PREFERRED_SIZE))
/**/.addGroup(layout.createParallelGroup(Alignment.CENTER)
/* */.addComponent(groupLabel)
/* */.addComponent(groupField, PREFERRED_SIZE, PREFERRED_SIZE, PREFERRED_SIZE))
/**/.addGroup(layout.createParallelGroup(Alignment.CENTER)
/* */.addComponent(linearLabel)
/* */.addComponent(linearField, PREFERRED_SIZE, PREFERRED_SIZE, PREFERRED_SIZE))
/**/.addGroup(layout.createParallelGroup(Alignment.CENTER)
/* */.addComponent(angularLabel)
/* */.addComponent(angularField, PREFERRED_SIZE, PREFERRED_SIZE, PREFERRED_SIZE))
/**/.addGroup(layout.createParallelGroup(Alignment.CENTER)
/* */.addComponent(frictionLabel)
/* */.addComponent(frictionField, PREFERRED_SIZE, PREFERRED_SIZE, PREFERRED_SIZE)));
return panel;
}
private JPanel makePropertiesPane()
{
JPanel panel = new JPanel();
GroupLayout layout = new GroupLayout(panel);
layout.setAutoCreateContainerGaps(true);
layout.setAutoCreateGaps(true);
panel.setLayout(layout);
JLabel nLabel = new JLabel(Messages.getString("GmObjectFrame.NAME")); //$NON-NLS-1$
JPanel origin = new JPanel();
GroupLayout oLayout = new GroupLayout(origin);
origin.setLayout(oLayout);
origin.setBorder(BorderFactory.createTitledBorder(Messages.getString("GmObjectFrame.SPRITE"))); //$NON-NLS-1$
String t = Messages.getString("GmObjectFrame.NO_SPRITE"); //$NON-NLS-1$
sprite = new ResourceMenu<Sprite>(Sprite.class,t,144);
plf.make(sprite,PGmObject.SPRITE);
newSprite = new JButton(Messages.getString("GmObjectFrame.NEW")); //$NON-NLS-1$
newSprite.addActionListener(this);
editSprite = new JButton(Messages.getString("GmObjectFrame.EDIT")); //$NON-NLS-1$
editSprite.addActionListener(this);
oLayout.setHorizontalGroup(oLayout.createSequentialGroup()
/**/.addContainerGap(4,4)
/**/.addGroup(oLayout.createParallelGroup()
/* */.addComponent(sprite)
/* */.addGroup(oLayout.createSequentialGroup()
/* */.addComponent(newSprite,DEFAULT_SIZE,DEFAULT_SIZE,MAX_VALUE)
/* */.addGap(4)
/* */.addComponent(editSprite,DEFAULT_SIZE,DEFAULT_SIZE,MAX_VALUE)))
/**/.addContainerGap(4,4));
oLayout.setVerticalGroup(oLayout.createSequentialGroup()
/**/.addComponent(sprite,DEFAULT_SIZE,DEFAULT_SIZE,PREFERRED_SIZE)
/**/.addGap(2)
/**/.addGroup(oLayout.createParallelGroup(Alignment.BASELINE)
/* */.addComponent(newSprite)
/* */.addComponent(editSprite))
/**/.addContainerGap(4,4));
visible = new JCheckBox(Messages.getString("GmObjectFrame.VISIBLE")); //$NON-NLS-1$
plf.make(visible,PGmObject.VISIBLE);
solid = new JCheckBox(Messages.getString("GmObjectFrame.SOLID")); //$NON-NLS-1$
plf.make(solid,PGmObject.SOLID);
JLabel dLabel = new JLabel(Messages.getString("GmObjectFrame.DEPTH")); //$NON-NLS-1$
depth = new NumberField(0);
plf.make(depth,PGmObject.DEPTH);
persistent = new JCheckBox(Messages.getString("GmObjectFrame.PERSISTENT")); //$NON-NLS-1$
plf.make(persistent,PGmObject.PERSISTENT);
physics = new JCheckBox(Messages.getString("GmObjectFrame.PHYSICS")); //$NON-NLS-1$
plf.make(physics,PGmObject.PHYSICS_OBJECT);
JLabel pLabel = new JLabel(Messages.getString("GmObjectFrame.PARENT")); //$NON-NLS-1$
t = Messages.getString("GmObjectFrame.NO_PARENT"); //$NON-NLS-1$
parent = new ResourceMenu<GmObject>(GmObject.class,t,110);
plf.make(parent,PGmObject.PARENT);
JLabel mLabel = new JLabel(Messages.getString("GmObjectFrame.MASK")); //$NON-NLS-1$
t = Messages.getString("GmObjectFrame.SAME_AS_SPRITE"); //$NON-NLS-1$
mask = new ResourceMenu<Sprite>(Sprite.class,t,110);
plf.make(mask,PGmObject.MASK);
information = new JButton(Messages.getString("GmObjectFrame.INFO"),INFO_ICON); //$NON-NLS-1$
information.addActionListener(this);
save.setText(Messages.getString("GmObjectFrame.SAVE")); //$NON-NLS-1$
layout.setHorizontalGroup(layout.createParallelGroup()
/**/.addGroup(layout.createSequentialGroup()
/* */.addComponent(nLabel)
/* */.addComponent(name,DEFAULT_SIZE,120,MAX_VALUE))
/**/.addComponent(origin)
/**/.addGroup(layout.createSequentialGroup()
/* */.addComponent(dLabel)
/* */.addComponent(depth))
/**/.addGroup(layout.createParallelGroup(Alignment.LEADING)
/* */.addComponent(pLabel)
/* */.addComponent(mLabel))
/**/.addGroup(layout.createSequentialGroup()
/* */.addGroup(layout.createParallelGroup(Alignment.TRAILING)
/* */.addComponent(parent,DEFAULT_SIZE,120,MAX_VALUE)
/* */.addComponent(mask,DEFAULT_SIZE,120,MAX_VALUE)))
/**/.addGroup(layout.createSequentialGroup()
/**/.addGroup(layout.createParallelGroup()
/* */.addComponent(visible)
/* */.addComponent(solid))
/**/.addGroup(layout.createParallelGroup()
/* */.addComponent(persistent)
/* */.addComponent(physics)))
/**/.addComponent(information,DEFAULT_SIZE,DEFAULT_SIZE,MAX_VALUE)
/**/.addComponent(save,DEFAULT_SIZE,DEFAULT_SIZE,MAX_VALUE));
layout.setVerticalGroup(layout.createSequentialGroup()
/**/.addGroup(layout.createParallelGroup(Alignment.BASELINE)
/* */.addComponent(nLabel)
/* */.addComponent(name))
/**/.addComponent(origin)
/**/.addGroup(layout.createParallelGroup(Alignment.BASELINE)
/* */.addComponent(dLabel)
/* */.addComponent(depth))
/**/.addComponent(pLabel)
/**/.addGap(4)
/**/.addComponent(parent,DEFAULT_SIZE,DEFAULT_SIZE,PREFERRED_SIZE)
/**/.addComponent(mLabel)
/**/.addGap(4)
/**/.addComponent(mask,DEFAULT_SIZE,DEFAULT_SIZE,PREFERRED_SIZE)
/**/.addGap(8)
/**/.addGroup(layout.createParallelGroup()
/**/.addGroup(layout.createSequentialGroup()
/* */.addComponent(visible)
/* */.addComponent(solid))
/**/.addGroup(layout.createSequentialGroup()
/* */.addComponent(persistent)
/* */.addComponent(physics)))
/**/.addGap(8,8,MAX_VALUE)
/**/.addComponent(information)
/**/.addComponent(save));
return panel;
}
public static class EventTree extends JTree
{
private static final long serialVersionUID = 1L;
public EventTree(TreeNode n)
{
super(n);
//otherwise, getToolTipText won't be called
setToolTipText(""); //$NON-NLS-1$
}
public String getToolTipText(MouseEvent e)
{
Point p = e.getPoint();
TreePath path = getPathForLocation(p.x,p.y);
if (path == null) return null;
Object c = path.getLastPathComponent();
if (c instanceof EventInstanceNode)
{
EventInstanceNode node = (EventInstanceNode) c;
Event ev = node.getUserObject();
return Messages.format("MainEvent.EVENT_HINT" + ev.mainId,ev.toString()); //$NON-NLS-1$
}
return Messages.format("MainEvent.EVENTS",c.toString()); //$NON-NLS-1$
}
}
private class EventNodeTransferHandler extends TransferHandler
{
private static final long serialVersionUID = 1L;
public EventNodeTransferHandler()
{
super();
}
protected Transferable createTransferable(JComponent c)
{
return null;
}
public int getSourceActions(JComponent c)
{
return NONE;
}
public boolean canImport(TransferHandler.TransferSupport support)
{
if (!support.isDataFlavorSupported(EventNode.EVENTNODE_FLAVOR)) return false;
EventNode t = (EventNode) LGM.eventSelect.events.getLastSelectedPathComponent();
if (t == null || !t.isValid()) return false;
if (LGM.eventSelect.function.getValue() != EventPanel.FUNCTION_ADD
&& !isValidEventInstanceNode(events.getLastSelectedPathComponent()))
return false;
if (rootEvent.contains(new Event(t.mainId,t.eventId,t.other))) return false;
for (DataFlavor f : support.getDataFlavors())
if (f == EventNode.EVENTNODE_FLAVOR) return true;
return false;
}
public boolean importData(TransferHandler.TransferSupport support)
{
if (!canImport(support)) return false;
try
{
EventNode t = (EventNode) support.getTransferable().getTransferData(
EventNode.EVENTNODE_FLAVOR);
if (!t.isValid()) return false;
Point p = support.getDropLocation().getDropPoint();
TreePath path = events.getPathForLocation(p.x,p.y);
functionEvent(t.mainId,t.eventId,t.other,path);
return true;
}
catch (Throwable e)
{
// This is just to stop the dnd system from silencing exceptions
e.printStackTrace();
}
return false;
}
}
public static class EventInstanceNode extends DefaultMutableTreeNode implements
Comparable<EventInstanceNode>
{
private static final long serialVersionUID = 1L;
public EventInstanceNode(Event e)
{
super(e);
}
public Event getUserObject()
{
return (Event) super.getUserObject();
}
public int compareTo(EventInstanceNode n)
{
return getUserObject().compareTo(n.getUserObject());
}
}
public class EventGroupNode extends DefaultMutableTreeNode
{
private static final long serialVersionUID = 1L;
public int mainId;
public EventGroupNode(int mainId)
{
super(Messages.getString("MainEvent.EVENT" + mainId)); //$NON-NLS-1$
this.mainId = mainId;
}
public boolean contains(Event e)
{
for (int i = 0; i < getChildCount(); i++)
{
if (getChildAt(i) instanceof EventInstanceNode)
{
if (((EventInstanceNode) getChildAt(i)).getUserObject().matchesType(e)) return true;
}
else if (((EventGroupNode) getChildAt(i)).contains(e)) return true;
}
return false;
}
@SuppressWarnings("unchecked")
public void sortChildren()
{
// This doesn't seem to have any unwanted effects (directly sorting the protected field)
Collections.sort((java.util.Vector<EventInstanceNode>) children);
}
public TreePath childPath(Event e)
{
for (int i = 0; i < getChildCount(); i++)
if (getChildAt(i) instanceof EventInstanceNode
&& ((EventInstanceNode) getChildAt(i)).getUserObject().matchesType(e))
return new TreePath(((EventInstanceNode) getChildAt(i)).getPath());
return null;
}
public void select(Event e)
{
TreePath p = childPath(e);
events.setSelectionPath(p);
events.updateUI();
events.scrollPathToVisible(p);
events.updateUI();
}
}
public void addEvent(Event e)
{
for (int i = 0; i < rootEvent.getChildCount(); i++)
{
DefaultMutableTreeNode n = (DefaultMutableTreeNode) rootEvent.getChildAt(i);
if (n instanceof EventGroupNode)
{
EventGroupNode group = (EventGroupNode) n;
if (group.mainId == e.mainId)
{
if (!group.contains(e))
{
group.add(new EventInstanceNode(e));
group.sortChildren();
}
group.select(e);
return;
}
}
else
{
EventInstanceNode ein = (EventInstanceNode) n;
if (ein.getUserObject().mainId == e.mainId)
{
if (!ein.getUserObject().matchesType(e))
{
EventGroupNode group = new EventGroupNode(e.mainId);
int ind = rootEvent.getIndex(ein);
rootEvent.remove(ind);
rootEvent.insert(group,ind);
group.add(ein);
group.add(new EventInstanceNode(e));
group.sortChildren();
group.select(e);
}
else
{
rootEvent.select(e);
}
return;
}
}
}
for (int i = 0; i < rootEvent.getChildCount(); i++)
{
int mid;
if (rootEvent.getChildAt(i) instanceof EventInstanceNode)
mid = ((EventInstanceNode) rootEvent.getChildAt(i)).getUserObject().mainId;
else
mid = ((EventGroupNode) rootEvent.getChildAt(i)).mainId;
if (mid > e.mainId)
{
EventInstanceNode ein = new EventInstanceNode(e);
rootEvent.insert(ein,i);
rootEvent.select(e);
return;
}
}
EventInstanceNode ein = new EventInstanceNode(e);
rootEvent.add(ein);
rootEvent.select(e);
}
public void removeEvent(EventInstanceNode n)
{
DefaultMutableTreeNode p = (DefaultMutableTreeNode) n.getParent();
if (p == null) return;
DefaultMutableTreeNode next = n.getNextSibling();
if (next == null) next = n.getPreviousSibling();
if (p == rootEvent)
n.removeFromParent();
else
{
if (p.getChildCount() < 3) //thunder
{
n.removeFromParent();
rootEvent.insert((DefaultMutableTreeNode) p.getChildAt(0),rootEvent.getIndex(p));
p.removeFromParent();
}
else
n.removeFromParent();
}
if (next == null && rootEvent.getChildCount() != 0)
next = (DefaultMutableTreeNode) rootEvent.getChildAt(0);
if (next == null)
actions.setActionContainer(null);
else
{
TreePath path = new TreePath((next instanceof EventInstanceNode ? next
: (DefaultMutableTreeNode) next.getChildAt(0)).getPath());
events.setSelectionPath(path);
events.scrollPathToVisible(path);
}
events.updateUI();
}
public void functionEvent(int mainId, int id, ResourceReference<GmObject> other, TreePath path)
{
if (path == null)
{
DefaultMutableTreeNode node = (DefaultMutableTreeNode) events.getLastSelectedPathComponent();
path = node == null ? null : new TreePath(node.getPath());
}
int func = path == null ? EventPanel.FUNCTION_ADD : LGM.eventSelect.function.getValue();
switch (func)
{
case EventPanel.FUNCTION_ADD:
addEvent(new Event(mainId,id,other));
break;
case EventPanel.FUNCTION_REPLACE:
DefaultMutableTreeNode dropNode = (DefaultMutableTreeNode) path.getLastPathComponent();
if (!(dropNode instanceof EventInstanceNode) || dropNode.getParent() == null) return;
EventInstanceNode drop = (EventInstanceNode) dropNode;
removeEvent(drop);
Event ev = drop.getUserObject();
ev.mainId = mainId;
ev.id = id;
ev.other = other;
addEvent(ev);
break;
case EventPanel.FUNCTION_DUPLICATE:
dropNode = (DefaultMutableTreeNode) path.getLastPathComponent();
if (!(dropNode instanceof EventInstanceNode) || dropNode.getParent() == null) return;
drop = (EventInstanceNode) dropNode;
ev = drop.getUserObject();
actions.save();
Event ev2 = ev.copy();
ev2.mainId = mainId;
ev2.id = id;
ev2.other = other;
addEvent(ev2);
break;
}
}
public void makeEventTree(GmObject res)
{
rootEvent = new EventGroupNode(-1);
for (int m = 0; m < 12; m++)
{
MainEvent me = res.mainEvents.get(m);
List<Event> ale = me.events;
if (ale.size() == 1)
{
rootEvent.add(new EventInstanceNode(ale.get(0)));
}
if (ale.size() > 1)
{
EventGroupNode node = new EventGroupNode(m);
rootEvent.add(node);
for (Event e : ale)
node.add(new EventInstanceNode(e));
}
}
if (res.getNode().newRes && rootEvent.getChildCount() == 0)
{
rootEvent.add(new EventInstanceNode(new Event(MainEvent.EV_CREATE,0,null)));
rootEvent.add(new EventInstanceNode(new Event(MainEvent.EV_STEP,Event.EV_STEP_NORMAL,null)));
rootEvent.add(new EventInstanceNode(new Event(MainEvent.EV_DRAW,0,null)));
}
events = new EventTree(rootEvent);
events.setScrollsOnExpand(true);
events.setCellRenderer(new EventNodeRenderer());
events.setRootVisible(false);
events.setShowsRootHandles(true);
events.setExpandsSelectedPaths(false);
events.addMouseListener(mListener);
events.getSelectionModel().setSelectionMode(TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION);
events.addTreeSelectionListener(this);
if (LGM.javaVersion >= 10600)
{
events.setDragEnabled(true);
events.setDropMode(DropMode.ON);
events.setTransferHandler(new EventNodeTransferHandler());
}
// This listener should be added to each node maybe
// otherwise you can click on the whitespace and open it
// but then again I suppose its fine like this because I have
// ensured checks to make sure there are no NPE's trying to edit
// an event that don't exist. Meh, this is fine as is.
MouseListener ml = new MouseAdapter()
{
public void mousePressed(MouseEvent e)
{
if (e.getClickCount() == 2)
{
editSelectedEvent();
}
}
};
events.addMouseListener(ml);
}
public void showInfoFrame()
{
// NOTE: This does affect reverting the resource, just makes it so
// the info frame can use the up to date version.
saveEvents();
if (infoFrame == null)
{
infoFrame = new ResourceInfoFrame();
}
infoFrame.updateObjectInfo(res.reference);
infoFrame.setVisible(true);
}
public void saveEvents()
{
actions.save();
Enumeration<?> dfe = rootEvent.depthFirstEnumeration();
for (MainEvent me : res.mainEvents)
me.events.clear();
while (dfe.hasMoreElements())
{
Object o = dfe.nextElement();
if (o instanceof EventInstanceNode)
{
EventInstanceNode ein = (EventInstanceNode) o;
if (!ein.getUserObject().actions.isEmpty())
{
Event e = ein.getUserObject();
res.mainEvents.get(e.mainId).events.add(e);
}
}
}
}
protected boolean areResourceFieldsEqual()
{
return Util.areInherentlyUniquesEqual(res.mainEvents,resOriginal.mainEvents);
}
public void commitChanges()
{
saveEvents();
res.setName(name.getText());
}
public void actionPerformed(ActionEvent e)
{
if (e.getSource() == newSprite)
{
ResNode n = Listener.getPrimaryParent(Sprite.class);
Sprite spr = LGM.currentFile.resMap.getList(Sprite.class).add();
Listener.putNode(LGM.tree,n,n,Sprite.class,n.getChildCount(),spr);
res.put(PGmObject.SPRITE,spr.reference);
return;
}
if (e.getSource() == editSprite)
{
Sprite spr = deRef(sprite.getSelected());
if (spr == null) return;
spr.getNode().openFrame();
return;
}
if (e.getSource() == information)
{
showInfoFrame();
return;
}
if (e.getSource() == eventModify || e.getSource() == eventModifyItem)
{
LGM.showEventPanel();
return;
}
if (e.getSource() == eventAddItem)
{
LGM.showEventPanel();
LGM.eventSelect.function.setValue(EventPanel.FUNCTION_ADD);
return;
}
if (e.getSource() == eventReplaceItem)
{
LGM.showEventPanel();
LGM.eventSelect.function.setValue(EventPanel.FUNCTION_REPLACE);
return;
}
if (e.getSource() == eventDuplicateItem)
{
LGM.showEventPanel();
LGM.eventSelect.function.setValue(EventPanel.FUNCTION_DUPLICATE);
return;
}
if (e.getSource() == eventEdit || e.getSource() == eventEditItem)
{
editSelectedEvent();
return;
}
if (e.getSource() == eventDelete || e.getSource() == eventDeleteItem)
{
DefaultMutableTreeNode comp = (DefaultMutableTreeNode) events.getLastSelectedPathComponent();
if (!isValidEventInstanceNode(comp)) return;
removeEvent((EventInstanceNode) comp);
return;
}
super.actionPerformed(e);
}
private TreeNode[] findEvent(DefaultMutableTreeNode node, int mainid, int id) {
Enumeration<?> children = node.children();
while (children.hasMoreElements()) {
DefaultMutableTreeNode child = (DefaultMutableTreeNode) children.nextElement();
if (child instanceof EventInstanceNode) {
EventInstanceNode evtNode = (EventInstanceNode) child;
Event evt = evtNode.getUserObject();
if (evt.mainId == mainid && evt.id == id) {
return evtNode.getPath();
}
} else if (child instanceof EventGroupNode) {
TreeNode[] ret = findEvent(child, mainid, id);
if (ret != null) return ret;
}
}
return null;
}
private TreeNode[] findEvent(int mainid, int id) {
return findEvent((DefaultMutableTreeNode) events.getModel().getRoot(), mainid, id);
}
public void setSelectedEvent(int mainid, int id) {
TreeNode[] nodes = findEvent(mainid, id);
if (nodes != null) {
TreePath path = new TreePath(nodes);
// TODO: Why does this not work? I tried wrapping it in SwingUtilities as well as reloading
// the tree model.
//events.expandPath(path);
// Using this temporarily.
events.setExpandsSelectedPaths(true);
events.setSelectionPath(path);
}
}
private void editSelectedEvent()
{
if (events.getModel().getChildCount(events.getModel().getRoot()) == 0)
{
return;
}
Action a = null;
LibAction la = null;
Boolean prependNew = true;
for (int i = 0; i < actions.model.list.size(); i++)
{
a = actions.model.list.get(i);
la = a.getLibAction();
if (la.actionKind == Action.ACT_CODE)
{
prependNew = false;
break;
}
}
if (prependNew)
{
a = new Action(LibManager.codeAction);
((ActionListModel) actions.getModel()).add(0,a);
actions.setSelectedValue(a,true);
}
MDIFrame af = ActionList.openActionFrame(actions.parent.get(),a);
EventInstanceNode evnode = (EventInstanceNode) events.getLastSelectedPathComponent();
af.setTitle(this.name.getText() + " : " + evnode.toString());
af.setFrameIcon(LGM.getIconForKey("EventNode.EVENT" + evnode.getUserObject().mainId));
return;
}
/**
* Check if a node is non-null, is an instance of EventInstanceNode, and that it has a parent and
* still exists in the events tree. This is useful with
* {@link javax.swing.JTree#getLastSelectedPathComponent() getLastSelectedPathComponent()} or
* {@link javax.swing.tree.TreePath#getLastPathComponent() getLastPathComponent()} because they
* can return nodes already removed from the tree.
*
* @param node The node to check for validity.
*
* @return Whether the node is valid.
*/
private static boolean isValidEventInstanceNode(Object node)
{
return (node != null && node instanceof EventInstanceNode
&& ((EventInstanceNode) node).getParent() != null);
}
@Override
public void dispose()
{
super.dispose();
events.removeTreeSelectionListener(this);
events.setModel(null);
events.setTransferHandler(null);
information.removeActionListener(this);
newSprite.removeActionListener(this);
editSprite.removeActionListener(this);
eventModify.removeActionListener(this);
eventEdit.removeActionListener(this);
eventDelete.removeActionListener(this);
if (infoFrame != null)
{
infoFrame.dispose();
}
res.properties.updateSource.removeListener(propUpdateListener);
if (editor != null)
{
((ActionListEditor) editor).dispose();
}
}
public void valueChanged(TreeSelectionEvent tse)
{
DefaultMutableTreeNode node = (DefaultMutableTreeNode) events.getLastSelectedPathComponent();
if (node == null || !node.isLeaf() || !(node.getUserObject() instanceof Event))
{
if (node != null && !node.isLeaf() && node.getParent() != null)
{
TreePath path = new TreePath(node.getPath());
if (events.isExpanded(path))
events.collapsePath(path);
else
events.expandPath(path);
}
if (lastValidEventSelection != null)
events.setSelectionPath(new TreePath(lastValidEventSelection.getPath()));
return;
}
lastValidEventSelection = node;
actions.setActionContainer((Event) node.getUserObject());
}
@Override
public void exceptionThrown(Exception e)
{
if (e instanceof ParentLoopException)
{
String msg = Messages.getString("GmObjectFrame.LOOPING_PARENTS"); //$NON-NLS-1$
String ttl = Messages.getString("GmObjectFrame.WARNING"); //$NON-NLS-1$
JOptionPane.showMessageDialog(this,msg,ttl,JOptionPane.WARNING_MESSAGE);
return;
}
super.exceptionThrown(e);
}
@Override
public Dimension getMinimumSize()
{
Dimension p = getContentPane().getSize();
Dimension l = getContentPane().getMinimumSize();
Dimension s = getSize();
l.width += s.width - p.width;
l.height += s.height - p.height;
return l;
}
public static class EventNodeRenderer extends DefaultTreeCellRenderer
{
private static final long serialVersionUID = 1L;
public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel,
boolean expanded, boolean leaf, int row, boolean hasFocus)
{
super.getTreeCellRendererComponent(tree,value,sel,expanded,leaf,row,hasFocus);
int mid = -1;
if (value instanceof EventNode) mid = ((EventNode) value).mainId;
if (value instanceof EventInstanceNode)
mid = ((EventInstanceNode) value).getUserObject().mainId;
if (value instanceof EventGroupNode) mid = ((EventGroupNode) value).mainId;
Icon i = LGM.getIconForKey("EventNode." + (leaf ? "EVENT" : "GROUP") + mid);
if (i != null && i.getIconWidth() != -1) setIcon(i);
return this;
}
}
private class MListener extends MouseAdapter
{
public void mouseReleased(MouseEvent e)
{
if (e.getSource() != events) return;
int button = e.getButton();
if (button == MouseEvent.BUTTON3)
{
TreePath path = events.getPathForLocation(e.getX(),e.getY());
if (path == null) return;
DefaultMutableTreeNode node = (DefaultMutableTreeNode) path.getLastPathComponent();
if (node.isLeaf())
{
lastValidEventSelection = node;
actions.setActionContainer((Event) node.getUserObject());
events.setSelectionPath(path);
JPopupMenu menu = new JPopupMenu();
eventModifyItem = new JMenu(Messages.getString("GmObjectFrame.MODIFY")); //$NON-NLS-1
menu.add(eventModifyItem);
eventModifyItem.addActionListener(GmObjectFrame.this);
eventAddItem = new JMenuItem(Messages.getString("GmObjectFrame.ADD")); //$NON-NLS-1
eventModifyItem.add(eventAddItem);
eventAddItem.addActionListener(GmObjectFrame.this);
eventReplaceItem = new JMenuItem(Messages.getString("GmObjectFrame.REPLACE")); //$NON-NLS-1
eventModifyItem.add(eventReplaceItem);
eventReplaceItem.addActionListener(GmObjectFrame.this);
eventDuplicateItem = new JMenuItem(Messages.getString("GmObjectFrame.DUPLICATE")); //$NON-NLS-1
eventModifyItem.add(eventDuplicateItem);
eventDuplicateItem.addActionListener(GmObjectFrame.this);
eventEditItem = new JMenuItem(Messages.getString("GmObjectFrame.EDIT")); //$NON-NLS-1
menu.add(eventEditItem);
eventEditItem.addActionListener(GmObjectFrame.this);
eventDeleteItem = new JMenuItem(Messages.getString("GmObjectFrame.DELETE")); //$NON-NLS-1
menu.add(eventDeleteItem);
eventDeleteItem.addActionListener(GmObjectFrame.this);
menu.show(e.getComponent(),e.getX(),e.getY());
}
}
}
}
}