/*
* Copyright (C) 2006, 2007, 2009 IsmAvatar <IsmAvatar@gmail.com>
* Copyright (C) 2007 Clam <clamisgood@gmail.com>
* Copyright (C) 2008, 2009 Quadduc <quadduc@gmail.com>
* Copyright (C) 2010 Medo <smaxein@googlemail.com>
* Copyright (C) 2013, 2014 Robert B. Colton
* Copyright (C) 2014 egofree
*
* This file is part of LateralGM.
* LateralGM is free software and comes with ABSOLUTELY NO WARRANTY.
* See LICENSE for details.
*/
package org.lateralgm.components.impl;
import java.awt.Font;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Vector;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JInternalFrame;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.KeyStroke;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.MutableTreeNode;
import org.lateralgm.components.GmTreeGraphics;
import org.lateralgm.main.LGM;
import org.lateralgm.main.Listener;
import org.lateralgm.main.Prefs;
import org.lateralgm.main.UpdateSource;
import org.lateralgm.main.UpdateSource.UpdateEvent;
import org.lateralgm.main.UpdateSource.UpdateListener;
import org.lateralgm.main.UpdateSource.UpdateTrigger;
import org.lateralgm.main.Util;
import org.lateralgm.messages.Messages;
import org.lateralgm.resources.Background;
import org.lateralgm.resources.GmObject;
import org.lateralgm.resources.InstantiableResource;
import org.lateralgm.resources.Resource;
import org.lateralgm.resources.ResourceReference;
import org.lateralgm.resources.Sprite;
import org.lateralgm.subframes.InstantiableResourceFrame;
import org.lateralgm.subframes.ResourceFrame;
import org.lateralgm.subframes.ResourceFrame.ResourceFrameFactory;
import org.lateralgm.subframes.RoomFrame;
import org.lateralgm.subframes.SubframeInformer;
public class ResNode extends DefaultNode implements Transferable,UpdateListener
{
public static final Map<Class<?>,ImageIcon> ICON;
static
{
ICON = new HashMap<Class<?>,ImageIcon>();
for (Entry<String,Class<? extends Resource<?,?>>> k : Resource.kindsByName3.entrySet())
ICON.put(k.getValue(),LGM.getIconForKey("Resource." + k.getKey()));
}
private static final long serialVersionUID = 1L;
public static final DataFlavor NODE_FLAVOR = new DataFlavor(
DataFlavor.javaJVMLocalObjectMimeType,"Node");
private DataFlavor[] flavors = { NODE_FLAVOR };
public static final byte STATUS_PRIMARY = 1;
public static final byte STATUS_GROUP = 2;
public static final byte STATUS_SECONDARY = 3;
/** One of PRIMARY, GROUP, or SECONDARY*/
public byte status;
/** What kind of Resource this is */
public Class<?> kind;
/**
* The <code>Resource</code> this node represents.
*/
private final ResourceReference<? extends Resource<?,?>> res;
public ResourceFrame<?,?> frame = null;
private final NameUpdater nameUpdater = new NameUpdater(this);
private final UpdateTrigger trigger = new UpdateTrigger();
public final UpdateSource updateSource = new UpdateSource(this,trigger);
public boolean newRes = false;
@Override
public Icon getIcon()
{
if (status == STATUS_SECONDARY)
{
if (kind == Sprite.class || kind == Background.class || kind == GmObject.class)
{
if (icon == null) updateIcon();
return icon;
}
return ICON.get(kind);
}
if (Prefs.iconizeGroup && getChildCount() > 0)
{
ResNode n = (ResNode) getChildAt(0);
if (n.status == STATUS_SECONDARY) return n.getIcon();
}
return null;
}
@Override
public Icon getIconisedGroup() {
if (status != ResNode.STATUS_PRIMARY && kind == Sprite.class || kind == Background.class || kind == GmObject.class)
return getIcon();
return null;
}
@Override
public Icon getLeafIcon() {
if (status == ResNode.STATUS_SECONDARY)
return getIcon();
return null;
}
// NOTE: DO NOT use this for tree graphics, format with HTML instead because there is a Java LNF bug that causes this not to work.
public Font getFont(Font com) {
if (Prefs.boldPrimaryNodes && status == ResNode.STATUS_PRIMARY) {
com = com.deriveFont(Font.BOLD);
} else {
com = com.deriveFont(Font.PLAIN);
}
return com;
}
private void updateIcon()
{
icon = GmTreeGraphics.getResourceIcon(res);
}
public ResNode(String name, byte status, Class<?> kind,
ResourceReference<? extends Resource<?,?>> res)
{
super(name);
this.status = status;
this.kind = kind;
this.res = res;
Resource<?,?> r = deRef();
if (r != null)
{
r.setNode(this);
res.updateSource.addListener(this);
}
}
public ResNode(String name, byte status, Class<?> kind)
{
this(name,status,kind,null);
}
public ResNode addChild(String name, byte stat, Class<?> k)
{
ResNode b = new ResNode(name,stat,k,null);
add(b);
return b;
}
@Override
public boolean getAllowsChildren()
{
if (status == STATUS_SECONDARY) return false;
if (isRoot()) return false;
return true;
}
public DataFlavor[] getTransferDataFlavors()
{
return flavors;
}
public boolean isDataFlavorSupported(DataFlavor flavor)
{
return flavor == NODE_FLAVOR;
}
public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException
{
if (flavor != NODE_FLAVOR) throw new UnsupportedFlavorException(flavor);
return this;
}
@Override
public void openFrame()
{
openFrame(false);
}
public void openFrame(boolean newRes)
{
this.newRes = newRes;
Resource<?,?> r = deRef();
if (r != null) {
r.changed = newRes;
}
if (SubframeInformer.fireSubframeRequest(r,this)) return;
ResourceFrame<?,?> rf = frame;
boolean wasVisible = false;
if (frame == null)
{
ResourceFrameFactory factory = ResourceFrame.factories.get(kind);
rf = factory == null ? null : factory.makeFrame(r,this);
if (rf != null)
{
frame = rf;
if (rf instanceof InstantiableResourceFrame<?,?>) LGM.mdi.add(rf);
}
} else {
wasVisible = frame.isVisible();
}
if (rf != null)
{
SubframeInformer.fireSubframeAppear(rf,wasVisible);
rf.toTop();
}
}
private static JMenuItem makeMenuItem(String command, ActionListener al, boolean setIcon)
{
JMenuItem menuItem = new JMenuItem(Messages.getString(command));
menuItem.setActionCommand(command);
menuItem.addActionListener(al);
ImageIcon icon = LGM.getIconForKey(command);
if (icon != null && setIcon)
{
menuItem.setIcon(icon);
}
//menuItem.addKeyListener(kl);
menuItem.addKeyListener(null);
return menuItem;
}
public void showMenu(MouseEvent e)
{
JPopupMenu popup = new JPopupMenu();
ActionListener al = new Listener.NodeMenuListener(this);
//KeyListener kl = new Listener.NodeKeyListener(this);
if (!isInstantiable())
{
JMenuItem editItem = makeMenuItem("Listener.TREE_PROPERTIES",al,true);
popup.add(editItem); //$NON-NLS-1$
editItem.setFocusable(true);
editItem.setAccelerator(KeyStroke.getKeyStroke(Messages.getKeyboardString("Listener.TREE_PROPERTIES")));
popup.show(e.getComponent(),e.getX(),e.getY());
return;
}
if (status == ResNode.STATUS_SECONDARY)
{
JMenuItem insertItem = makeMenuItem("Listener.TREE_INSERT_RESOURCE",al,true);
insertItem.setFocusable(true);
popup.add(insertItem); //$NON-NLS-1$
insertItem.setAccelerator(KeyStroke.getKeyStroke(Messages.getKeyboardString("Listener.TREE_INSERT_RESOURCE")));
JMenuItem duplicateItem = makeMenuItem("Listener.TREE_DUPLICATE_RESOURCE",al,true);
duplicateItem.setFocusable(true);
popup.add(duplicateItem); //$NON-NLS-1$
duplicateItem.setAccelerator(KeyStroke.getKeyStroke(Messages.getKeyboardString("Listener.TREE_DUPLICATE_RESOURCE")));
popup.add(makeMenuItem("Listener.TREE_INSERT_GROUP",al,true)); //$NON-NLS-1$
}
else
{
popup.add(makeMenuItem("Listener.TREE_CREATE_RESOURCE",al,true)); //$NON-NLS-1$
popup.add(makeMenuItem("Listener.TREE_CREATE_GROUP",al,true)); //$NON-NLS-1$
}
if (status != ResNode.STATUS_SECONDARY) popup.add(makeMenuItem("Listener.TREE_SORT",al,false)); //$NON-NLS-1$
if (status != ResNode.STATUS_PRIMARY)
{
popup.addSeparator();
JMenuItem deleteItem = makeMenuItem("Listener.TREE_DELETE",al,true);
deleteItem.setFocusable(true);
deleteItem.requestFocus();
popup.add(deleteItem); //$NON-NLS-1$
// KeyStroke.getKeyStroke("BACK_SPACE"); for delete key on mac
deleteItem.setAccelerator(KeyStroke.getKeyStroke(Messages.getKeyboardString("Listener.TREE_DELETE")));
JMenuItem renameItem = makeMenuItem("Listener.TREE_RENAME",al,true);
renameItem.setFocusable(true);
popup.add(renameItem); //$NON-NLS-1$
renameItem.setAccelerator(KeyStroke.getKeyStroke(Messages.getKeyboardString("Listener.TREE_RENAME")));
}
if (status == ResNode.STATUS_SECONDARY)
{
JMenuItem editItem = makeMenuItem("Listener.TREE_PROPERTIES",al,true);
editItem.setFocusable(true);
popup.add(editItem); //$NON-NLS-1$
editItem.setAccelerator(KeyStroke.getKeyStroke(Messages.getKeyboardString("Listener.TREE_PROPERTIES")));
}
popup.show(e.getComponent(),e.getX(),e.getY());
}
public void add(MutableTreeNode arg0)
{
super.add(arg0);
fireUpdate();
}
public void insert(MutableTreeNode newChild, int childIndex)
{
super.insert(newChild,childIndex);
fireUpdate();
}
public void remove(int childIndex)
{
super.remove(childIndex);
fireUpdate();
}
private void fireUpdate()
{
fireUpdate(trigger.getEvent());
}
private Resource<?,?> deRef()
{
return Util.deRef((ResourceReference<?>) res);
}
private void fireUpdate(UpdateEvent e)
{
trigger.fire(e);
if (e != null && parent != null && parent instanceof ResNode) ((ResNode) parent).fireUpdate(e);
}
/**
* Recursively checks (from this node down) for a node with a res field
* referring to the same instance as res.
* @param res The resource to look for
* @return Whether the resource was found
*/
public boolean contains(ResourceReference<? extends Resource<?,?>> res)
{
if (this.res == res) return true; //Just in case
if (children != null) for (Object obj : children)
if (obj instanceof ResNode)
{
ResNode node = (ResNode) obj;
if (node.isLeaf())
{
if (node.res == res) return true;
}
else
{
if (node.contains(res)) return true;
}
}
return false;
}
//TODO: This should be generic if ResNode is ever changed to have
//generic tree node children.
public Vector<ResNode> getChildren()
{
return children;
}
public ResourceReference<? extends Resource<?,?>> getRes()
{
return res;
}
public void updated(UpdateEvent e)
{
// If a sprite, a background, or an object has been udpated, reset the undo
if (kind == Sprite.class || kind == Background.class || kind == GmObject.class)
{
for (JInternalFrame room : LGM.mdi.getAllFrames())
{
if (room instanceof RoomFrame) ((RoomFrame) room).resetUndoManager();
}
}
if (status == STATUS_SECONDARY)
{
icon = null;
Resource<?,?> r = deRef();
if (r != null)
{
setUserObject(r.getName());
Util.invokeOnceLater(nameUpdater);
}
else
removeFromParent();
}
fireUpdate(e);
}
private class NameUpdater implements Runnable
{
ResNode node;
public NameUpdater(ResNode resNode)
{
node = resNode;
}
public void run()
{
if (frame != null && frame instanceof InstantiableResourceFrame<?,?>)
{
InstantiableResourceFrame<?,?> resFrame = (InstantiableResourceFrame<?,?>) frame;
Resource<?,?> r = deRef();
if (r != null)
{
String n = r.getName();
resFrame.setTitle(n);
if (!resFrame.name.getText().equals(n)) resFrame.name.setText(n);
}
}
if (LGM.tree != null)
{
//FIXME: Update the tree by having it listen to its root node instead of here
LGM.tree.updateUI();
// Never update the entire tree UI for a single node, just reload the node.
DefaultTreeModel model = ((DefaultTreeModel) LGM.tree.getModel());
model.reload(node);
}
}
}
public boolean isInstantiable()
{
return InstantiableResource.class.isAssignableFrom(kind);
}
public boolean isEditable()
{
return isInstantiable();
}
}