/*
* Copyright (C) 2007, 2008 IsmAvatar <IsmAvatar@gmail.com>
* Copyright (C) 2007, 2008, 2009 Quadduc <quadduc@gmail.com>
* Copyright (C) 2007 Clam <clamisgood@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;
import static javax.swing.GroupLayout.PREFERRED_SIZE;
import static org.lateralgm.main.Util.deRef;
import java.awt.Component;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.io.IOException;
import javax.swing.BorderFactory;
import javax.swing.GroupLayout;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JToolTip;
import javax.swing.TransferHandler;
import javax.swing.border.EtchedBorder;
import javax.swing.GroupLayout.Alignment;
import javax.swing.GroupLayout.ParallelGroup;
import javax.swing.GroupLayout.SequentialGroup;
import org.lateralgm.components.impl.ResNode;
import org.lateralgm.components.visual.AbstractImagePreview;
import org.lateralgm.components.visual.ImageToolTip;
import org.lateralgm.main.LGM;
import org.lateralgm.main.Listener;
import org.lateralgm.main.Prefs;
import org.lateralgm.main.Util;
import org.lateralgm.main.UpdateSource.UpdateEvent;
import org.lateralgm.main.UpdateSource.UpdateListener;
import org.lateralgm.resources.Resource;
import org.lateralgm.resources.ResourceReference;
import org.lateralgm.util.PropertyEditor;
import org.lateralgm.util.PropertyLink;
import org.lateralgm.util.PropertyMap;
import org.lateralgm.util.PropertyMap.PropertyUpdateEvent;
public class ResourceMenu<R extends Resource<R,?>> extends JPanel implements ActionListener,
UpdateListener,PropertyEditor<ResourceReference<R>>
{
private static final long serialVersionUID = 1L;
private JLabel label;
private JButton button;
protected ResourceReference<R> selected;
protected JPopupMenu pm;
public JMenuItem noResource;
protected String defStr;
protected boolean onlyOpen;
private ActionEvent actionEvent;
protected Class<? extends Resource<?,?>> kind;
private MListener mListener = new MListener();
protected static final ImageIcon GROUP_ICO = LGM.getIconForKey("GmTreeGraphics.GROUP"); //$NON-NLS-1$;
private final Preview rPreview;
private final MenuBuilder builder = new MenuBuilder();
public class ResourceJMenu extends JMenu
{
private static final long serialVersionUID = 1L;
public ResNode node;
public ResourceJMenu(ResNode node)
{
super(node.getUserObject().toString());
this.node = node;
node.updateSource.addListener(ResourceMenu.this);
}
public void update()
{
setText(node.getUserObject().toString());
ResourceMenu.this.setSelected(selected); //update text
}
public boolean isVisible()
{
return !onlyOpen || hasVisibleChildren();
}
private boolean hasVisibleChildren()
{
for (int i = 0; i < getPopupMenu().getComponentCount(); i++)
if (getPopupMenu().getComponent(i).isVisible()) return true;
return false;
}
}
public class ResourceMenuItem extends JMenuItem
{
private static final long serialVersionUID = 1L;
public ResNode node;
public ResourceMenuItem(ResNode node)
{
super(node.getUserObject().toString());
this.node = node;
node.updateSource.addListener(ResourceMenu.this);
setIcon(node.getIcon());
}
public void setIcon(Icon ico)
{
super.setIcon(ico == null ? GROUP_ICO : ico);
}
public void update()
{
setIcon(node.getIcon());
setText(node.getUserObject().toString());
if (selected == node.getRes()) ResourceMenu.this.setSelected(selected); //update text
}
public boolean isVisible()
{
return !onlyOpen || node.frame != null;
}
}
public static class Preview extends JLabel
{
private static final long serialVersionUID = 1L;
private BufferedImage displayImage;
public Preview()
{
//Must be set or else toolTip won't show
setToolTipText(new String());
}
public <R extends Resource<R,?>>void setResource(ResourceReference<R> r)
{
Resource<R,?> res = Util.deRef(r);
if (res == null || !(res instanceof Resource.Viewable))
{
displayImage = null;
setIcon(null);
return;
}
displayImage = ((Resource.Viewable) res).getDisplayImage();
ResNode rn = res.getNode();
setIcon(rn == null ? null : rn.getIcon());
}
public JToolTip createToolTip()
{
return new ImageToolTip(new AbstractImagePreview()
{
private static final long serialVersionUID = 1L;
public BufferedImage getImage()
{
return displayImage;
}
});
}
}
class ResourceTransferHandler<K extends Resource<K,?>> extends TransferHandler {
/**
* NOTE: Default UID generated, change if necessary.
*/
private static final long serialVersionUID = 1716244867900780500L;
private ResourceMenu<K> menu;
public ResourceTransferHandler(ResourceMenu<K> menu)
{
this.menu = menu;
}
public int getSourceActions(JComponent c)
{
return COPY_OR_MOVE;
}
public boolean canImport(TransferSupport ts) {
if (!ts.isDataFlavorSupported(ResNode.NODE_FLAVOR))
{
return false;
}
try
{
ResNode data = (ResNode) ts.getTransferable().getTransferData(ResNode.NODE_FLAVOR);
if (data.kind.equals(menu.kind) && data.status == ResNode.STATUS_SECONDARY)
{
return true;
}
}
catch (UnsupportedFlavorException | IOException e)
{
// Should never occur.
LGM.showDefaultExceptionHandler(e);
}
return false;
}
@SuppressWarnings("unchecked")
public boolean importData(TransferSupport ts)
{
try
{
ResNode data = (ResNode) ts.getTransferable().getTransferData(ResNode.NODE_FLAVOR);
menu.setSelected((ResourceReference<K>) data.getRes());
fireActionPerformed();
return true;
}
catch (UnsupportedFlavorException | IOException e)
{
// Should never occur.
LGM.showDefaultExceptionHandler(e);
}
return false;
}
}
/**
* Creates a Resource Menu of given Resource kind.
* @param kind - One of the kind constants defined in Resource (eg Resource.SPRITE)
* @param def - The default value to display if no resource is selected
* @param showDef - Whether to display the default value as a selectable menu option
* @param width - The component width desired
* @param onlyOpen - Whether to only show open frames on the menu
* @param preview - Whether to display a preview icon
*/
public ResourceMenu(Class<? extends Resource<?,?>> kind, String def, boolean showDef, int width,
boolean onlyOpen, boolean preview)
{
this.setTransferHandler(new ResourceTransferHandler<R>(this));
this.kind = kind;
this.onlyOpen = onlyOpen;
this.defStr = def;
GroupLayout layout = new GroupLayout(this);
setLayout(layout);
label = new JLabel(def);
label.setBorder(BorderFactory.createEtchedBorder(EtchedBorder.LOWERED));
label.addMouseListener(mListener);
button = new JButton(ResNode.ICON.get(kind));
button.addMouseListener(mListener);
button.setMaximumSize(button.getPreferredSize());
int freeWidth = width - (preview ? 40 : 20);
rPreview = preview ? new Preview() : null;
SequentialGroup hg = layout.createSequentialGroup();
ParallelGroup vg = layout.createParallelGroup(Alignment.CENTER,false);
if (preview)
{
hg.addComponent(rPreview,PREFERRED_SIZE,20,PREFERRED_SIZE);
vg.addComponent(rPreview,PREFERRED_SIZE,20,PREFERRED_SIZE);
}
layout.setHorizontalGroup(hg
/**/.addComponent(label,PREFERRED_SIZE,freeWidth,Integer.MAX_VALUE)
/**/.addComponent(button,PREFERRED_SIZE,22,PREFERRED_SIZE));
layout.setVerticalGroup(vg
/**/.addComponent(label,PREFERRED_SIZE,20,PREFERRED_SIZE)
/**/.addComponent(button,PREFERRED_SIZE,22,PREFERRED_SIZE));
pm = new JPopupMenu();
if (showDef)
{
noResource = pm.add(new JMenuItem(def));
noResource.addActionListener(this);
}
populate(kind);
LGM.root.updateSource.addListener(ResourceMenu.this);
}
/**
* Creates a Resource Menu of given Resource kind.
* @param kind - One of the kind constants defined in Resource (eg Resource.SPRITE)
* @param def - The default value to display if no resource is selected
* @param showDef - Whether to display the default value as a selectable menu option
* @param width - The component width desired
*/
public ResourceMenu(Class<? extends Resource<?,?>> kind, String def, boolean showDef, int width)
{
this(kind,def,showDef,width,false,canPreview(kind));
}
/**
* Convenience method for creating a Resource Menu that does display the default value
* as a selectable menu option.
* @param kind - One of the kind constants defined in Resource (eg Resource.SPRITE)
* @param def - The default value to display if no resource is selected (selectable in menu)
* @param width - The component width desired
*/
public ResourceMenu(Class<? extends Resource<?,?>> kind, String def, int width)
{
this(kind,def,true,width,false,canPreview(kind));
}
@Override
public int getBaseline(int width, int height)
{
return label.getBaseline(width,height);
}
public static boolean canPreview(Class<? extends Resource<?,?>> kind)
{
for (Class<?> i : kind.getInterfaces())
if (i == Resource.Viewable.class) return true;
return false;
}
protected void populate(Class<? extends Resource<?,?>> kind)
{
if (Prefs.groupKind)
{
for (int m = 0; m < LGM.root.getChildCount(); m++)
{
ResNode group = (ResNode) LGM.root.getChildAt(m);
if (group.kind == kind)
{
populate(pm,group,kind);
return;
} //found group
} //root loop
} //group kind
populate(pm,LGM.root,kind);
return;
}
private void populate(JComponent parent, ResNode group, Class<? extends Resource<?,?>> kind)
{
for (int i = 0; i < group.getChildCount(); i++)
{
ResNode child = (ResNode) group.getChildAt(i);
if (child.status != ResNode.STATUS_SECONDARY)
{
JMenuItem newParent;
if (child.getChildCount() == 0)
newParent = new ResourceMenuItem(child);
else
newParent = new ResourceJMenu(child);
newParent.setIcon(GROUP_ICO);
parent.add(newParent);
populate(newParent,child,kind);
continue;
}
if (child.kind != kind) continue;
ResourceMenuItem newParent = new ResourceMenuItem(child);
newParent.addActionListener(this);
parent.add(newParent);
}
}
public void addActionListener(ActionListener il)
{
listenerList.add(ActionListener.class,il);
}
public void removeActionListener(ActionListener il)
{
listenerList.remove(ActionListener.class,il);
}
protected void fireActionPerformed()
{
// Guaranteed to return a non-null array
Object[] listeners = listenerList.getListenerList();
// Process the listeners last to first, notifying
// those that are interested in this event
for (int i = listeners.length - 2; i >= 0; i -= 2)
{
if (listeners[i] == ActionListener.class)
{
if (actionEvent == null)
actionEvent = new ActionEvent(this,ActionEvent.ACTION_PERFORMED,""); //$NON-NLS-1$
((ActionListener) listeners[i + 1]).actionPerformed(actionEvent);
}
}
}
public void showPopup(Component c, int x, int y)
{
if (pm.getComponentCount() == 0) return;
pm.show(c,x,y);
}
public ResourceReference<R> getSelected()
{
return selected;
}
public void setSelected(ResourceReference<R> res)
{
selected = res;
Resource<R,?> r = deRef(res);
label.setText(r == null ? defStr : r.getName());
if (rPreview != null) rPreview.setResource(res);
}
public void setEnabled(boolean enabled)
{
label.setEnabled(enabled);
button.setEnabled(enabled);
super.setEnabled(enabled);
}
@SuppressWarnings("unchecked")
public void actionPerformed(ActionEvent e)
{
JMenuItem source = (JMenuItem) e.getSource();
if (source instanceof ResourceMenu.ResourceMenuItem)
setSelected((ResourceReference<R>) ((ResourceMenuItem) source).node.getRes());
else
setSelected(null);
fireActionPerformed();
}
//TODO: Possibly replace with addComponentPopupListener?
//Though maybe not since this is used on a label.
private class MListener extends MouseAdapter
{
public MListener()
{
super();
}
public void mouseClicked(MouseEvent e)
{
if (!isEnabled()) return;
if (pm.getComponentCount() == 0) return;
showPopup(e.getComponent(),e.getX(),e.getY());
}
}
public void updated(UpdateEvent e)
{
Util.invokeOnceLater(builder);
}
private class MenuBuilder implements Runnable
{
public void run()
{
pm.removeAll();
if (noResource != null) pm.add(noResource);
populate(kind);
if (selected == null || !Listener.getPrimaryParent(kind).contains(selected))
setSelected(null);
setSelected(selected);
}
}
public <K extends Enum<K>>PropertyLink<K,ResourceReference<R>> getLink(PropertyMap<K> m, K k)
{
return new ResourceMenuLink<K>(m,k);
}
private class ResourceMenuLink<K extends Enum<K>> extends PropertyLink<K,ResourceReference<R>>
implements ActionListener
{
public ResourceMenuLink(PropertyMap<K> m, K k)
{
super(m,k);
reset();
addActionListener(this);
}
protected void setComponent(ResourceReference<R> r)
{
setSelected(r);
}
@Override
public void remove()
{
super.remove();
removeActionListener(this);
}
@Override
public void updated(PropertyUpdateEvent<K> e)
{
editComponentIfChanged(getSelected());
}
public void actionPerformed(ActionEvent e)
{
if (selected == null ? map.get(key) == null : selected.equals(map.get(key))) return;
editProperty(selected);
}
}
}