/*
* Copyright (C) 2006, 2007, 2009 IsmAvatar <IsmAvatar@gmail.com>
* Copyright (C) 2007 Clam <clamisgood@gmail.com>
* Copyright (C) 2008, 2009 Quadduc <quadduc@gmail.com>
*
* 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.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.EnumMap;
import java.util.Map;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.MutableTreeNode;
import org.lateralgm.components.GmTreeGraphics;
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.UpdateSource;
import org.lateralgm.main.Util;
import org.lateralgm.main.UpdateSource.UpdateEvent;
import org.lateralgm.main.UpdateSource.UpdateListener;
import org.lateralgm.main.UpdateSource.UpdateTrigger;
import org.lateralgm.messages.Messages;
import org.lateralgm.resources.Background;
import org.lateralgm.resources.Font;
import org.lateralgm.resources.GmObject;
import org.lateralgm.resources.Path;
import org.lateralgm.resources.Resource;
import org.lateralgm.resources.ResourceReference;
import org.lateralgm.resources.Room;
import org.lateralgm.resources.Script;
import org.lateralgm.resources.Sound;
import org.lateralgm.resources.Sprite;
import org.lateralgm.resources.Timeline;
import org.lateralgm.resources.Resource.Kind;
import org.lateralgm.subframes.BackgroundFrame;
import org.lateralgm.subframes.FontFrame;
import org.lateralgm.subframes.GmObjectFrame;
import org.lateralgm.subframes.PathFrame;
import org.lateralgm.subframes.ResourceFrame;
import org.lateralgm.subframes.RoomFrame;
import org.lateralgm.subframes.ScriptFrame;
import org.lateralgm.subframes.SoundFrame;
import org.lateralgm.subframes.SpriteFrame;
import org.lateralgm.subframes.SubframeInformer;
import org.lateralgm.subframes.TimelineFrame;
public class ResNode extends DefaultMutableTreeNode implements Transferable,UpdateListener
{
public static final Map<Kind,ImageIcon> ICON;
static
{
Map<Kind,ImageIcon> m = new EnumMap<Kind,ImageIcon>(Kind.class);
for (Kind k : Kind.values())
m.put(k,LGM.getIconForKey("Resource." + k.name()));
ICON = java.util.Collections.unmodifiableMap(m);
}
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 Resource.Kind kind;
/**
* The <code>Resource</code> this node represents.
*/
private final ResourceReference<? extends Resource<?,?>> res;
public ResourceFrame<?,?> frame = null;
private Icon icon;
private final NameUpdater nameUpdater = new NameUpdater();
private final UpdateTrigger trigger = new UpdateTrigger();
public final UpdateSource updateSource = new UpdateSource(this,trigger);
public boolean newRes = false;
public Icon getIcon()
{
if (status == STATUS_SECONDARY) switch (kind)
{
case SPRITE:
case BACKGROUND:
case OBJECT:
if (icon == null) updateIcon();
return icon;
default:
return ICON.get(kind);
}
if (Prefs.iconizeGroup && getChildCount() > 0)
{
ResNode n = (ResNode) getChildAt(0);
if (n.status == STATUS_SECONDARY) return n.getIcon();
}
return null;
}
private void updateIcon()
{
icon = GmTreeGraphics.getResourceIcon(res);
}
public ResNode(String name, byte status, Resource.Kind 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, Kind kind)
{
this(name,status,kind,null);
}
public ResNode addChild(String name, byte stat, Kind k)
{
ResNode b = new ResNode(name,stat,k,null);
add(b);
return b;
}
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;
}
public void openFrame()
{
openFrame(false);
}
public void openFrame(boolean newRes)
{
this.newRes = newRes;
Resource<?,?> r = deRef();
if (SubframeInformer.fireSubframeRequest(r,this)) return;
if (frame == null)
{
MDIFrame rf = null;
switch (kind)
{
case SPRITE:
rf = new SpriteFrame((Sprite) r,this);
break;
case SOUND:
rf = new SoundFrame((Sound) r,this);
break;
case BACKGROUND:
rf = new BackgroundFrame((Background) r,this);
break;
case PATH:
rf = new PathFrame((Path) r,this);
break;
case SCRIPT:
rf = new ScriptFrame((Script) r,this);
break;
case FONT:
rf = new FontFrame((Font) r,this);
break;
case TIMELINE:
rf = new TimelineFrame((Timeline) r,this);
break;
case OBJECT:
rf = new GmObjectFrame((GmObject) r,this);
break;
case ROOM:
rf = new RoomFrame((Room) r,this);
break;
case GAMEINFO:
rf = LGM.getGameInfo();
SubframeInformer.fireSubframeAppear(rf);
rf.toTop();
return;
case GAMESETTINGS:
rf = LGM.getGameSettings();
SubframeInformer.fireSubframeAppear(rf);
rf.toTop();
return;
case EXTENSIONS:
//TODO: Create Extensions front-end
return;
}
if (rf != null)
{
frame = (ResourceFrame<?,?>) rf;
LGM.mdi.add(rf);
}
}
if (frame != null)
{
SubframeInformer.fireSubframeAppear(frame);
frame.toTop();
}
}
private JMenuItem makeMenuItem(String command, ActionListener al)
{
JMenuItem menuItem = new JMenuItem(Messages.getString(command));
menuItem.setActionCommand(command);
menuItem.addActionListener(al);
return menuItem;
}
public void showMenu(MouseEvent e)
{
JPopupMenu popup = new JPopupMenu();
ActionListener al = new Listener.NodeMenuListener(this);
switch (kind)
{
case GAMESETTINGS:
case GAMEINFO:
case EXTENSIONS:
popup.add(makeMenuItem("Listener.TREE_EDIT",al)); //$NON-NLS-1$
popup.show(e.getComponent(),e.getX(),e.getY());
return;
}
if (status == ResNode.STATUS_SECONDARY)
{
popup.add(makeMenuItem("Listener.TREE_EDIT",al)); //$NON-NLS-1$
popup.addSeparator();
popup.add(makeMenuItem("Listener.TREE_INSERT",al)); //$NON-NLS-1$
popup.add(makeMenuItem("Listener.TREE_DUPLICATE",al)); //$NON-NLS-1$
}
else
popup.add(makeMenuItem("Listener.TREE_ADD",al)); //$NON-NLS-1$
popup.addSeparator();
popup.add(makeMenuItem("Listener.TREE_GROUP",al)); //$NON-NLS-1$
if (status != ResNode.STATUS_SECONDARY) popup.add(makeMenuItem("Listener.TREE_SORT",al)); //$NON-NLS-1$
if (status != ResNode.STATUS_PRIMARY)
{
popup.addSeparator();
popup.add(makeMenuItem("Listener.TREE_DELETE",al)); //$NON-NLS-1$
popup.add(makeMenuItem("Listener.TREE_RENAME",al)); //$NON-NLS-1$
}
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;
}
public ResourceReference<? extends Resource<?,?>> getRes()
{
return res;
}
public void updated(UpdateEvent e)
{
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
{
public void run()
{
if (frame != null)
{
Resource<?,?> r = deRef();
if (r != null)
{
String n = r.getName();
frame.setTitle(n);
if (!frame.name.getText().equals(n)) frame.name.setText(n);
}
}
//FIXME: Update the tree by having it listen to its root node instead of here
LGM.tree.updateUI();
}
}
}