/*
* Copyright (C) 2007, 2008, 2010 IsmAvatar <IsmAvatar@gmail.com>
* Copyright (C) 2007, 2008 Clam <clamisgood@gmail.com>
* Copyright (C) 2007, 2008 Quadduc <quadduc@gmail.com>
* Copyright (C) 2013, 2014, 2016 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.components;
import static org.lateralgm.main.Util.deRef;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.ClipboardOwner;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.beans.PropertyVetoException;
import java.io.IOException;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.EmptyStackException;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Stack;
import java.util.TreeMap;
import java.util.WeakHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.swing.AbstractListModel;
import javax.swing.BorderFactory;
import javax.swing.DropMode;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.KeyStroke;
import javax.swing.ListCellRenderer;
import javax.swing.ListModel;
import javax.swing.TransferHandler;
import javax.swing.border.EmptyBorder;
import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener;
import javax.swing.undo.AbstractUndoableEdit;
import javax.swing.undo.CannotRedoException;
import javax.swing.undo.CannotUndoException;
import javax.swing.undo.UndoManager;
import org.lateralgm.components.ActionListEditor.LibActionButton;
import org.lateralgm.components.mdi.MDIFrame;
import org.lateralgm.main.LGM;
import org.lateralgm.main.Prefs;
import org.lateralgm.main.UpdateSource.UpdateEvent;
import org.lateralgm.main.UpdateSource.UpdateListener;
import org.lateralgm.messages.Messages;
import org.lateralgm.resources.GmObject;
import org.lateralgm.resources.ResourceReference;
import org.lateralgm.resources.library.LibAction;
import org.lateralgm.resources.library.LibManager;
import org.lateralgm.resources.sub.Action;
import org.lateralgm.resources.sub.ActionContainer;
import org.lateralgm.resources.sub.Argument;
import org.lateralgm.subframes.ActionFrame;
public class ActionList extends JList<Action> implements ActionListener,ClipboardOwner
{
private static final long serialVersionUID = 1L;
private static final Map<Action,WeakReference<ActionFrame>> FRAMES;
private static final ActionListKeyListener ALKL = new ActionListKeyListener();
protected ActionContainer actionContainer;
public ActionListModel model;
private final ActionRenderer renderer = new ActionRenderer(this);
public final WeakReference<MDIFrame> parent;
private final ActionListMouseListener alml;
public UndoManager undomanager;
static
{
FRAMES = new WeakHashMap<Action,WeakReference<ActionFrame>>();
}
private JMenuItem makeContextButton(String key)
{
JMenuItem b = new JMenuItem(Messages.getString(key));
b.setActionCommand(key);
b.setText(b.getText());
b.setIcon(LGM.getIconForKey(key));
b.setRequestFocusEnabled(false);
b.addActionListener(this);
return b;
}
public ActionList(MDIFrame parent)
{
// build popup menu
final JPopupMenu popup = new JPopupMenu();
JMenuItem item;
item = makeContextButton("ActionList.EDIT");
popup.add(item);
popup.addSeparator();
item = makeContextButton("ActionList.CUT");
popup.add(item);
item.setAccelerator(KeyStroke.getKeyStroke(Messages.getKeyboardString("ActionList.CUT")));
item = makeContextButton("ActionList.COPY");
popup.add(item);
item.setAccelerator(KeyStroke.getKeyStroke(Messages.getKeyboardString("ActionList.COPY")));
item = makeContextButton("ActionList.PASTE");
popup.add(item);
item.setAccelerator(KeyStroke.getKeyStroke(Messages.getKeyboardString("ActionList.PASTE")));
popup.addSeparator();
undomanager = new UndoManager();
final JMenuItem undoitem = makeContextButton("ActionList.UNDO");
popup.add(undoitem);
undoitem.setAccelerator(KeyStroke.getKeyStroke(Messages.getKeyboardString("ActionList.UNDO")));
final JMenuItem redoitem = makeContextButton("ActionList.REDO");
popup.add(redoitem);
redoitem.setAccelerator(KeyStroke.getKeyStroke(Messages.getKeyboardString("ActionList.REDO")));
popup.addSeparator();
item = makeContextButton("ActionList.SELECTALL");
popup.add(item);
item.setAccelerator(KeyStroke.getKeyStroke(Messages.getKeyboardString("ActionList.SELECTALL")));
popup.addSeparator();
item = makeContextButton("ActionList.DELETE");
popup.add(item);
item.setAccelerator(KeyStroke.getKeyStroke(Messages.getKeyboardString("ActionList.DELETE")));
item = makeContextButton("ActionList.CLEAR");
popup.add(item);
this.setComponentPopupMenu(popup);
popup.addPopupMenuListener(new PopupMenuListener()
{
@Override
public void popupMenuCanceled(PopupMenuEvent arg0)
{
}
@Override
public void popupMenuWillBecomeInvisible(PopupMenuEvent arg0)
{
}
@Override
public void popupMenuWillBecomeVisible(PopupMenuEvent arg0)
{
undoitem.setEnabled(undomanager.canUndo());
redoitem.setEnabled(undomanager.canRedo());
}
});
this.parent = new WeakReference<MDIFrame>(parent);
setActionContainer(null);
setBorder(BorderFactory.createEmptyBorder(0,0,24,0));
setTransferHandler(new ActionTransferHandler(this.parent,this));
setDragEnabled(true);
setDropMode(DropMode.ON_OR_INSERT);
alml = new ActionListMouseListener(this.parent);
addMouseListener(alml);
addKeyListener(ALKL);
setCellRenderer(renderer);
}
public void setActionContainer(ActionContainer ac)
{
save();
actionContainer = ac;
model = new ActionListModel(undomanager);
model.renderer = renderer;
setModel(model);
if (ac == null) return;
model.addAll(ac.actions,false);
}
public ActionContainer getActionContainer()
{
return actionContainer;
}
public void save()
{
if (actionContainer == null) return;
for (WeakReference<ActionFrame> a : FRAMES.values())
{
if (a != null)
{
ActionFrame af = a.get();
if (af != null && !af.isClosed()) af.commitChanges();
}
}
actionContainer.actions = model.list;
}
/**
* Opens an ActionFrame representing a given action.
* Actions like "else" etc. will not have a frame opened.
* @param a The action to open a frame for
* @return The frame opened or <code>null</code> if no
* frame was opened.
*/
public static MDIFrame openActionFrame(MDIFrame parent, Action a)
{
LibAction la = a.getLibAction();
if ((la.libArguments == null || la.libArguments.length == 0) && !la.canApplyTo
&& !la.allowRelative && !la.question) return null;
WeakReference<ActionFrame> fr = FRAMES.get(a);
ActionFrame af = fr == null ? null : fr.get();
if (af == null || af.isClosed())
{
af = new ActionFrame(a);
LGM.mdi.add(af);
if (parent != null) LGM.mdi.addZChild(parent,af);
FRAMES.put(a,new WeakReference<ActionFrame>(af));
}
af.setVisible(true);
//FIXME: Find out why parent is sent to back. This is a workaround.
if (parent != null) parent.toFront();
af.toFront();
try
{
af.setIcon(false);
af.setSelected(true);
}
catch (PropertyVetoException pve)
{
//Guess it doesn't want us doing that. Oh well.
LGM.showDefaultExceptionHandler(pve);
}
return af;
}
public static void closeFrames()
{
for (Map.Entry<Action,WeakReference<ActionFrame>> entry : FRAMES.entrySet())
{
ActionFrame frame = entry.getValue().get();
if (frame != null) {
frame.dispose();
}
}
}
private static class ActionListMouseListener extends MouseAdapter
{
public final WeakReference<MDIFrame> parent;
public ActionListMouseListener(WeakReference<MDIFrame> parent)
{
super();
this.parent = parent;
}
public void mouseClicked(MouseEvent e)
{
if (e.getClickCount() != 2 || !(e.getSource() instanceof ActionList)) return;
ActionList l = (ActionList) e.getSource();
Object o = l.getSelectedValue();
if (o == null && l.getModel().getSize() == 0 && l.getActionContainer() != null)
{
o = new Action(LibManager.codeAction);
((ActionListModel) l.getModel()).add((Action) o);
l.setSelectedValue(o,true);
}
if (o == null || !(o instanceof Action)) return;
openActionFrame(parent.get(),(Action) o);
}
}
private static class ActionListKeyListener extends KeyAdapter
{
public ActionListKeyListener()
{
super();
}
@Override
public void keyPressed(KeyEvent e)
{
if (!(e.getSource() instanceof ActionList))
return;
@SuppressWarnings("unchecked")
JList<Action> l = (JList<Action>) e.getSource();
KeyStroke stroke = KeyStroke.getKeyStrokeForEvent(e);
if (stroke != null)
{
if (stroke.equals(KeyStroke.getKeyStroke(Messages.getKeyboardString("ActionList.UNDO"))))
{
ActionsUndo(l);
}
else if (stroke.equals(KeyStroke.getKeyStroke(Messages.getKeyboardString("ActionList.REDO"))))
{
ActionsRedo(l);
}
}
switch (e.getKeyCode())
{
case KeyEvent.VK_DELETE:
ActionsDelete(l);
e.consume();
break;
}
}
}
public class UndoableActionEdit extends AbstractUndoableEdit {
/**
* NOTE: Default UID generated, change if necessary.
*/
private static final long serialVersionUID = 3005489569659632528L;
public static final byte ACTION_ADD = 0;
public static final byte ACTION_REMOVE = 1;
public static final byte ACTION_MOVE = 2;
public static final byte ACTION_EDIT = 3;
public int type;
List<Action> actions = null;
List<Integer> indices = null;
List<Integer> indicesmoved = null;
public UndoableActionEdit(int t, List<Action> acts) {
super();
type = t;
actions = acts;
}
public UndoableActionEdit(int t, List<Integer> inds, List<Action> acts) {
super();
type = t;
actions = acts;
indices = inds;
}
public UndoableActionEdit(int t, List<Integer> inds, List<Integer> moved, List<Action> acts) {
super();
type = t;
indices = inds;
indicesmoved = moved;
}
// Return a reasonable name for this edit.
@Override
public String getPresentationName() {
return "Action " + type;
}
@Override
public void redo() throws CannotRedoException {
super.redo();
if (type == ACTION_ADD) {
if (indices != null) {
model.addAll(indices,actions,false);
} else {
model.addAll(actions,false);
}
} else if (type == ACTION_REMOVE) {
if (indices != null) {
model.removeAll(indices,false);
} else {
model.clear(false);
}
} else if (type == ACTION_MOVE) {
model.moveAll(indices, indicesmoved, false);
} else if (type == ACTION_EDIT) {
//TODO: Implement
}
}
@Override
public void undo() throws CannotUndoException {
super.undo();
if (type == ACTION_ADD) {
if (indices != null) {
model.removeAll(indices,false);
} else {
model.clear(false);
}
} else if (type == ACTION_REMOVE) {
if (indices != null) {
model.addAll(indices,actions,false);
} else {
model.addAll(actions,false);
}
} else if (type == ACTION_MOVE) {
model.moveAll(indicesmoved, indices, false);
} else if (type == ACTION_EDIT) {
//TODO: Implement
}
}
}
//TODO: Make sure a change actually happened before you store it, i.e. when you just
//drag and drop an action to the same location it shouldn't create an unecessary undo
public class ActionListModel extends AbstractListModel<Action> implements UpdateListener
{
private static final long serialVersionUID = 1L;
public ArrayList<Action> list;
protected ArrayList<Integer> indents;
private ActionRenderer renderer;
private UndoManager undoManager;
public ActionListModel(UndoManager um)
{
list = new ArrayList<Action>();
indents = new ArrayList<Integer>();
undoManager = um;
}
public void add(Action a)
{
add(a, true);
}
public void add(Action a, boolean updateundo)
{
add(getSize(),a,updateundo);
}
public void add(int index, Action a)
{
add(index, a, true);
}
public void add(int index, Action a, boolean updateundo)
{
if (updateundo) {
List<Integer> indices = new ArrayList<Integer>();
List<Action> actions = new ArrayList<Action>();
indices.add(index);
actions.add(a);
undoManager.addEdit(new UndoableActionEdit(UndoableActionEdit.ACTION_ADD, indices, actions));
}
a.updateSource.addListener(this);
list.add(index,a);
updateIndentation();
fireIntervalAdded(this,index,index);
}
public void addAll(int index, List<Action> c, boolean updateundo)
{
int s = c.size();
if (s <= 0) return;
List<Integer> indices = new ArrayList<Integer>();
int i = index;
for (Action a : c)
{
indices.add(i++);
a.updateSource.addListener(this);
}
list.addAll(index,c);
updateIndentation();
fireIntervalAdded(this,index,index + s - 1);
if (updateundo) {
undoManager.addEdit(new UndoableActionEdit(UndoableActionEdit.ACTION_ADD, indices, c));
}
}
public void addAll(int index, List<Action> c)
{
addAll(index, c, true);
}
public void addAll(List<Action> c, boolean updateundo)
{
int s = c.size();
if (s <= 0) return;
for (Action a : c)
{
a.updateSource.addListener(this);
}
list.addAll(c);
updateIndentation();
fireIntervalAdded(this,0,list.size());
if (updateundo) {
undoManager.addEdit(new UndoableActionEdit(UndoableActionEdit.ACTION_ADD, c));
}
}
public void addAll(List<Action> c) {
addAll(c, true);
}
public void addAll(List<Integer> indices, List<Action> c, boolean updateundo)
{
int s = c.size();
if (s <= 0) return;
// sort small to large to avoid oob
TreeMap<Integer,Action> map = new TreeMap<Integer,Action>();
for (int i = 0; i < indices.size(); i++) {
Integer ind = indices.get(i);
Action act = c.get(i);
map.put(ind,act);
}
for (Entry<Integer,Action> entry : map.entrySet()) {
Action a = entry.getValue();
Integer ind = entry.getKey();
a.updateSource.addListener(this);
list.add(ind, a);
fireIntervalAdded(this,ind,ind);
}
updateIndentation();
if (updateundo) {
undoManager.addEdit(new UndoableActionEdit(UndoableActionEdit.ACTION_ADD, indices, c));
}
}
public void addAll(List<Integer> indices, List<Action> c)
{
addAll(indices, c, true);
}
public void remove(int index, boolean updateundo)
{
if (updateundo) {
ArrayList<Integer> indices = new ArrayList<Integer>();
ArrayList<Action> actions = new ArrayList<Action>();
indices.add(index);
actions.add(list.get(index));
undoManager.addEdit(new UndoableActionEdit(UndoableActionEdit.ACTION_REMOVE, indices, actions));
}
list.remove(index).updateSource.removeListener(this);
updateIndentation();
fireIntervalRemoved(this,index,index);
}
public void remove(int index)
{
remove(index, true);
}
public void removeAll(List<Integer> indices, boolean updateundo)
{
List<Action> removed = new ArrayList<Action>();
// sort large to small to avoid oob
List<Integer> copy = new ArrayList<Integer>(indices);
Collections.sort(copy, new Comparator<Integer>() {
public int compare(Integer a, Integer b) {
//TODO: handle null
return b.compareTo(a);
}
});
// collect the removed ones in order
for (int i = 0; i < indices.size(); i++) {
int ind = indices.get(i);
removed.add(list.get(ind));
}
// now remove them in sorted order
for (int i = 0; i < copy.size(); i++) {
int ind = copy.get(i);
list.remove(ind).updateSource.removeListener(this);
fireIntervalRemoved(this,ind,ind);
}
if (updateundo) {
undoManager.addEdit(new UndoableActionEdit(UndoableActionEdit.ACTION_REMOVE, indices, removed));
}
updateIndentation();
}
public void removeAll(List<Integer> indices)
{
removeAll(indices, true);
}
public void clear(boolean updateundo)
{
ArrayList<Action> removed = new ArrayList<Action>(list);
list.clear();
fireIntervalRemoved(this,0,removed.size());
if (updateundo) {
undoManager.addEdit(new UndoableActionEdit(UndoableActionEdit.ACTION_REMOVE, removed));
}
}
public void clear() {
clear(true);
}
public int move(int prev, int next, ArrayList<Action> unchanged, boolean updateundo) {
Action a = unchanged.get(prev);
list.remove(prev).updateSource.removeListener(this);
fireIntervalRemoved(this,prev,prev);
if (next > list.size())
{
next = list.size();
}
a.updateSource.addListener(this);
list.add(next,a);
fireIntervalAdded(this,next,next);
if (updateundo)
{
ArrayList<Integer> indices = new ArrayList<Integer>(1);
ArrayList<Integer> indicesmoved = new ArrayList<Integer>(1);
indices.add(prev);
indicesmoved.add(next);
undoManager.addEdit(new UndoableActionEdit(UndoableActionEdit.ACTION_MOVE, indices, indicesmoved, null));
}
return next;
}
public void move(int prev, int next) {
move(prev, next, new ArrayList<Action>(list), true);
}
public void moveAll(List<Integer> indices, List<Integer> indicesmoved, boolean updateundo)
{
ArrayList<Action> unchanged = new ArrayList<Action>(list);
removeAll(indices, false);
for (int i = 0; i < indices.size(); i++)
{
Integer prev = indices.get(i);
Integer next = indicesmoved.get(i);
add(next,unchanged.get(prev),false);
}
fireContentsChanged(this, 0, list.size());
if (updateundo)
{
undoManager.addEdit(new UndoableActionEdit(UndoableActionEdit.ACTION_MOVE, indices,
indicesmoved, null));
}
}
public void moveAll(List<Integer> indices, List<Integer> indicesmoved)
{
moveAll(indices, indicesmoved, true);
}
public void moveAll(List<Integer> indices, int index)
{
List<Integer> indicesmoved = new ArrayList<Integer>();
for (int i = 0; i < indices.size(); i++) {
indicesmoved.add(index + i);
}
moveAll(indices, indicesmoved, true);
}
public Action getElementAt(int index)
{
return list.get(index);
}
public int getSize()
{
return list.size();
}
private void updateIndentation()
{
int lms = list.size();
indents.clear();
indents.ensureCapacity(lms);
Stack<Integer> levelIndents = new Stack<Integer>();
Stack<Stack<Integer>> questions = new Stack<Stack<Integer>>();
levelIndents.push(0);
questions.push(new Stack<Integer>());
int nextIndent = 0;
for (int i = 0; i < lms; i++)
{
Action a = list.get(i);
LibAction la = a.getLibAction();
int indent = nextIndent;
switch (la.actionKind)
{
case Action.ACT_BEGIN:
levelIndents.push(indent);
questions.push(new Stack<Integer>());
break;
case Action.ACT_END:
indent = levelIndents.peek();
if (levelIndents.size() > 1)
{
levelIndents.pop();
questions.pop();
}
nextIndent = levelIndents.peek();
break;
case Action.ACT_ELSE:
try
{
int j = questions.peek().pop();
if (j >= 0) indent = indents.get(j);
}
catch (EmptyStackException e)
{ //Silly user put a standalone Else
}
nextIndent = indent + 1;
break;
case Action.ACT_REPEAT:
nextIndent++;
break;
case Action.ACT_EXIT:
nextIndent = levelIndents.peek();
break;
default:
if (la.question)
{
questions.peek().push(i);
nextIndent++;
}
else if (la.execType != Action.EXEC_NONE) nextIndent = levelIndents.peek();
}
indents.add(indent);
}
}
public void updated(UpdateEvent e)
{
if (renderer != null) renderer.clearCache();
}
}
public static final DataFlavor ACTION_FLAVOR = new DataFlavor(Action.class,"Action"); //$NON-NLS-1$
public static final DataFlavor ACTION_ARRAY_FLAVOR = new DataFlavor(List.class,"Action array"); //$NON-NLS-1$
public static final DataFlavor LIB_ACTION_FLAVOR = new DataFlavor(LibAction.class,
"Library action"); //$NON-NLS-1$
public static class LibActionTransferable implements Transferable
{
private static final DataFlavor[] FLAVORS = { LIB_ACTION_FLAVOR };
private final LibAction libAction;
public LibActionTransferable(LibAction la)
{
libAction = la;
}
public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException
{
if (flavor == LIB_ACTION_FLAVOR)
{
return libAction;
}
throw new UnsupportedFlavorException(flavor);
}
public DataFlavor[] getTransferDataFlavors()
{
return FLAVORS;
}
public boolean isDataFlavorSupported(DataFlavor flavor)
{
return flavor == LIB_ACTION_FLAVOR;
}
}
public static class LibActionTransferHandler extends TransferHandler
{
private static final long serialVersionUID = 1L;
public boolean canImport(TransferHandler.TransferSupport info)
{
return false;
}
public boolean importData(TransferHandler.TransferSupport info)
{
return false;
}
public int getSourceActions(JComponent c)
{
return COPY;
}
protected Transferable createTransferable(JComponent c)
{
LibActionButton lab = (LibActionButton) c;
LibAction la = lab.getLibAction();
return new LibActionTransferable(la);
}
}
public static class ActionTransferable implements Transferable
{
private final ArrayList<Action> actions;
private final DataFlavor[] flavors;
public ActionTransferable(ArrayList<Action> a)
{
actions = a;
ArrayList<DataFlavor> fl = new ArrayList<DataFlavor>(2);
fl.add(ACTION_ARRAY_FLAVOR);
if (a.size() == 1) fl.add(ACTION_FLAVOR);
flavors = fl.toArray(new DataFlavor[fl.size()]);
}
public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException
{
if (flavor == ACTION_FLAVOR && actions.size() == 1)
{
return actions.get(0);
}
if (flavor == ACTION_ARRAY_FLAVOR)
{
return actions;
}
throw new UnsupportedFlavorException(flavor);
}
public DataFlavor[] getTransferDataFlavors()
{
return flavors;
}
public boolean isDataFlavorSupported(DataFlavor flavor)
{
for (DataFlavor f : flavors)
{
if (f == flavor) return true;
}
return false;
}
}
public static class ActionTransferHandler extends TransferHandler
{
private static final long serialVersionUID = 1L;
private int[] indices = null; //Location of dragged items (to be deleted)
private int addIndex = -1; //Location where items were added
private final WeakReference<MDIFrame> parent;
private ActionList list = null;
public ActionTransferHandler(WeakReference<MDIFrame> parent, ActionList l)
{
super();
this.parent = parent;
this.list = l;
}
@Override
protected void exportDone(JComponent source, Transferable data, int action)
{
if (indices != null)
{
ActionListModel model = (ActionListModel) list.getModel();
List<Integer> inds = new ArrayList<Integer>(indices.length);
int index = addIndex;
for (int i = 0; i < indices.length; i++)
{
inds.add(indices[i]);
if (indices[i] < addIndex) index--;
}
if (action == MOVE)
{
if (addIndex != -1)
{
model.moveAll(inds, index);
list.setSelectionInterval(index, index + inds.size() - 1);
}
else
{
model.removeAll(inds);
}
}
}
indices = null;
addIndex = -1;
}
public boolean canImport(TransferHandler.TransferSupport info)
{
DataFlavor[] f = info.getDataFlavors();
boolean supported = false;
for (DataFlavor flav : f)
{
if (flav == ACTION_FLAVOR || flav == ACTION_ARRAY_FLAVOR || flav == LIB_ACTION_FLAVOR)
supported = true;
}
if (!supported) return false;
ActionList list = (ActionList) info.getComponent();
if (list.actionContainer == null) return false;
if (info.isDrop() && ((JList.DropLocation) info.getDropLocation()).getIndex() == -1)
return false;
return true;
}
public boolean importData(TransferHandler.TransferSupport info)
{
if (!canImport(info)) return false;
ActionList list = (ActionList) info.getComponent();
ActionListModel alm = (ActionListModel) list.getModel();
Transferable t = info.getTransferable();
int index = list.getSelectedIndex();
index = index < 0 ? alm.getSize() : index;
if (info.isDrop()) index = ((JList.DropLocation) info.getDropLocation()).getIndex();
if (info.isDataFlavorSupported(ACTION_FLAVOR))
{
Action a;
try
{
a = (Action) t.getTransferData(ACTION_FLAVOR);
}
catch (Exception e)
{
LGM.showDefaultExceptionHandler(e);
return false;
}
//clone properly for drag-copy or clipboard paste
if (!info.isDrop() || info.getDropAction() == COPY) a = a.copy();
if (info.isDrop() && info.getDropAction() == MOVE && indices != null)
{
addIndex = index;
}
else
{
alm.add(index, a);
list.setSelectionInterval(index,index);
}
return true;
}
if (info.isDataFlavorSupported(ACTION_ARRAY_FLAVOR))
{
List<Action> a;
try
{
a = ((List<Action>) t.getTransferData(ACTION_ARRAY_FLAVOR));
}
catch (Exception e)
{
LGM.showDefaultExceptionHandler(e);
return false;
}
//clone properly for drag-copy or clipboard paste
if (!info.isDrop() || info.getDropAction() == COPY) for (int i = 0; i < a.size(); i++)
a.set(i,a.get(i).copy());
if (info.isDrop() && info.getDropAction() == MOVE && indices != null)
{
addIndex = index;
}
else
{
alm.addAll(index, a);
list.setSelectionInterval(index,index + a.size() - 1);
}
return true;
}
if (info.isDataFlavorSupported(LIB_ACTION_FLAVOR))
{
LibAction la;
Action a;
try
{
la = (LibAction) t.getTransferData(LIB_ACTION_FLAVOR);
a = new Action(la);
ActionList.openActionFrame(parent.get(),a);
}
catch (Exception e)
{
LGM.showDefaultExceptionHandler(e);
return false;
}
alm.add(index, a);
list.setSelectionInterval(index,index);
return true;
}
return false;
}
@Override
public int getSourceActions(JComponent c)
{
return COPY_OR_MOVE;
}
@Override
protected Transferable createTransferable(JComponent c)
{
indices = list.getSelectedIndices();
return new ActionTransferable((ArrayList<Action>) list.getSelectedValuesList());
}
}
private static class ActionRenderer implements ListCellRenderer<Action>
{
private final WeakHashMap<Action,SoftReference<ActionRendererComponent>> lcrMap;
private final ActionList list;
public ActionRenderer(ActionList l)
{
super();
lcrMap = new WeakHashMap<Action,SoftReference<ActionRendererComponent>>();
this.list = l;
}
public void clearCache()
{
lcrMap.clear();
}
public static String parse(String s, Action a)
{
String escape = "FrNw01234567"; //$NON-NLS-1$
StringBuilder ret = new StringBuilder();
int k = 0;
int p = s.indexOf('@');
while (p != -1)
{
ret.append(s.substring(k,p));
char c = s.charAt(p + 1);
if (!escape.contains(String.valueOf(c)))
{
ret.append('@');
k = p + 1;
p = s.indexOf('@',k);
continue;
}
if (c == 'F')
{
if (s.charAt(p + 2) == 'B' || s.charAt(p + 2) == 'I')
p += 2;
else
ret.append('@');
k = p + 1;
p = s.indexOf('@',k);
continue;
}
if (c == 'r' && a.isRelative()) ret.append(Messages.getString("Action.RELATIVE")); //$NON-NLS-1$
if (c == 'N' && a.isNot()) ret.append(Messages.getString("Action.NOT")); //$NON-NLS-1$
ResourceReference<GmObject> at = a.getAppliesTo();
if (c == 'w' && !at.equals(GmObject.OBJECT_SELF))
{
if (at.equals(GmObject.OBJECT_OTHER))
ret.append(Messages.getString("Action.APPLIES_OTHER")); //$NON-NLS-1$
else
{
GmObject applies = deRef(at);
ret.append(Messages.format("Action.APPLIES",applies == null ? at.toString() //$NON-NLS-1$
: applies.getName()));
}
}
if (c >= '0' && c < '8')
{
int arg = c - '0';
List<Argument> args = a.getArguments();
if (arg >= args.size())
ret.append('0');
else
{
Argument aa = args.get(arg);
ret.append(aa.toString(a.getLibAction().libArguments[arg]));
}
}
k = p + 2;
p = s.indexOf('@',k);
}
return ret + s.substring(k);
}
public static String escape(String s)
{
s = s.replaceAll("&","&"); //$NON-NLS-1$ //$NON-NLS-2$
s = s.replaceAll("<","<"); //$NON-NLS-1$ //$NON-NLS-2$
s = s.replaceAll(">",">"); //$NON-NLS-1$ //$NON-NLS-2$
s = s.replaceAll("\n","<br>"); //$NON-NLS-1$ //$NON-NLS-2$
s = s.replaceAll("\\\\#","\n"); //$NON-NLS-1$ //$NON-NLS-2$
s = s.replaceAll("#","<br>"); //$NON-NLS-1$ //$NON-NLS-2$
s = s.replaceAll("\n","#"); //$NON-NLS-1$ //$NON-NLS-2$
return s.replaceAll(" "," "); //$NON-NLS-1$ //$NON-NLS-2$
}
private static class ActionLineComponent extends JLabel {
/**
* NOTE: Default UID generated, change if necessary.
*/
private static final long serialVersionUID = 4152567649770789101L;
private JList<Action> list = null;
private int index = 0;
private int maxwidth = 0;
public ActionLineComponent(int ind, JList<Action> l)
{
super();
this.setText(Integer.toString(ind));
//setBackground(Color.red);
index = ind;
list = l;
}
@Override
public void paintComponent(Graphics g) {
int width = g.getFontMetrics().stringWidth(this.getText());
int height = g.getFontMetrics().getHeight();
g.setColor(this.getBackground());
g.fillRect(0,0,this.getWidth(),this.getHeight());
g.setColor(this.getForeground());
g.drawString(this.getText(),5 + maxwidth - width,(int)((this.getPreferredSize().getHeight() - height)/2 + getFontMetrics(getFont()).getAscent()));
g.fillRect(5 + maxwidth + 5,0,2,this.getPreferredSize().height);
}
public void setIndex(int ind)
{
index = ind;
this.setText(Integer.toString(index));
}
public void updatePreferredSize()
{
maxwidth = getFontMetrics(getFont()).stringWidth(Integer.toString(list.getModel().getSize() - 1));
setPreferredSize(new Dimension(5 + maxwidth + 7, this.getPreferredSize().height));
}
}
private static class ActionRendererComponent extends JPanel
{
private static final long serialVersionUID = 1L;
//private int indent;
private boolean selected;
private final JList<Action> list;
JLabel actlabel = null;
ActionLineComponent linelabel = null;
public ActionRendererComponent(int index, JList<Action> l)
{
this.list = l;
this.setLayout(new FlowLayout(FlowLayout.LEFT,0,0));
actlabel = new JLabel();
linelabel = new ActionLineComponent(index, l);
setOpaque(true);
setBackground(selected ? list.getSelectionBackground() : list.getBackground());
actlabel.setForeground(selected ? list.getSelectionForeground() : list.getForeground());
linelabel.setBackground(list.getBackground());
linelabel.setForeground(list.getForeground());
Action a = l.getModel().getElementAt(index);
LibAction la = a.getLibAction();
if (la.actImage == null)
actlabel.setText(Messages.getString("Action.UNKNOWN")); //$NON-NLS-1$
else
{
StringBuilder sb = null;
// let the user supply their own description using
// a special comment like in GM8.1 and GMS
if (a.getLibAction().actionKind == Action.ACT_CODE)
{
Pattern r = Pattern.compile("^\\s*//[/!]+\\s*(.+)([\r\n]|$)"); //$NON-NLS-1$
Matcher m = r.matcher(a.getArguments().get(0).getVal());
if (m.find())
{
sb = new StringBuilder(m.group(1));
}
}
if (sb == null)
{
sb = new StringBuilder("<html>"); //$NON-NLS-1$
if (la.listText.contains("@FI")) //$NON-NLS-1$
sb.append("<i>"); //$NON-NLS-1$
if (la.listText.contains("@FB")) //$NON-NLS-1$
sb.append("<b>"); //$NON-NLS-1$
sb.append(escape(parse(la.listText,a)));
}
actlabel.setText(sb.toString());
actlabel.setIcon(new ImageIcon(la.actImage));
if (Prefs.actionToolTipLines > 0 && Prefs.actionToolTipColumns > 0)
{
sb = new StringBuilder();
String snip = parse(la.hintText.replaceAll("(?<!\\\\)#","\n"),a); //$NON-NLS-1$
int last, next = -1;
for (int i = 0; i < Prefs.actionToolTipLines; i++)
{
last = next + 1;
next = snip.indexOf('\n',last);
if (next == -1)
{
sb.append(snip.substring(last));
break;
}
if (next > last + Prefs.actionToolTipColumns)
{
sb.append(snip.substring(last,last + Prefs.actionToolTipColumns));
sb.append("...");
}
else
sb.append(snip.substring(last,next));
sb.append('\n');
}
if (next != -1) sb.append(Messages.getString("Action.HINT_MORE"));
setToolTipText("<html><font face=\"Courier\">" + escape(sb.toString()));
}
}
linelabel.setPreferredSize(new Dimension(linelabel.getPreferredSize().width, actlabel.getPreferredSize().height + 4));
this.add(linelabel);
this.add(actlabel);
}
// public JToolTip createToolTip()
// {
// JToolTip tip = new JToolTip();
// tip.setComponent(this);
// return tip;
// }
/**
* Overridden to address java bug 6700748 by returning false.
* In WinXP, the cursor flickers between two states on drag & drop.
*/
public boolean isVisible()
{
return false;
}
public void setIndent(int indent)
{
//if (this.indent == indent) return;
//this.indent = indent;
actlabel.setBorder(new EmptyBorder(2,2 + 8 * indent,2,2));
linelabel.updatePreferredSize();
}
public void setSelected(boolean selected)
{
if (this.selected == selected) return;
this.selected = selected;
if (selected)
{
setBackground(list.getSelectionBackground());
actlabel.setForeground(list.getSelectionForeground());
}
else
{
setBackground(list.getBackground());
actlabel.setForeground(list.getForeground());
}
}
public void setIndex(int index)
{
linelabel.setIndex(index);
}
}
public Component getListCellRendererComponent(JList<? extends Action> l, Action cell,
int index, boolean isSelected, boolean hasFocus)
{
final Action cellAction = (Action) cell;
SoftReference<ActionRendererComponent> arcref = lcrMap.get(cellAction);
ActionRendererComponent arc = null;
if (arcref != null) arc = arcref.get();
if (arc == null)
{
arc = new ActionRendererComponent(index,(JList<Action>) list);
lcrMap.put(cellAction,new SoftReference<ActionRendererComponent>(arc));
}
ListModel<Action> lm = (ListModel<Action>) list.getModel();
try
{
if (lm instanceof ActionListModel)
arc.setIndent(((ActionListModel) lm).indents.get(index));
arc.setIndex(index);
}
catch (IndexOutOfBoundsException e)
{
//Lazy way of dealing with an invalid index value passed in.
}
arc.setSelected(isSelected);
return arc;
}
}
public void ActionsEdit(JList<Action> list)
{
int index = list.getSelectedIndex();
if (index == -1) return;
ActionListModel alm = (ActionListModel) list.getModel();
ActionList.openActionFrame(parent.get(),(Action) alm.getElementAt(index));
}
public void ActionsCut(JList<Action> list)
{
ActionsCopy(list);
ActionsDelete(list);
}
public void ActionsCopy(JList<Action> list)
{
int[] indices = list.getSelectedIndices();
ArrayList<Action> actions = (ArrayList<Action>) list.getSelectedValuesList();
if (indices.length <= 0) return;
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
ActionTransferable at = new ActionTransferable(actions);
clipboard.setContents(at,this);
}
public void ActionsPaste(JList<Action> list)
{
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
Transferable clipboardContents = clipboard.getContents(this);
for (DataFlavor flavor : clipboardContents.getTransferDataFlavors())
{
Object content = null;
try
{
content = clipboardContents.getTransferData(flavor);
}
catch (UnsupportedFlavorException e)
{
LGM.showDefaultExceptionHandler(e);
}
catch (IOException e)
{
LGM.showDefaultExceptionHandler(e);
}
if (flavor.equals(ACTION_ARRAY_FLAVOR))
{
ActionListModel alm = (ActionListModel) list.getModel();
@SuppressWarnings("unchecked")
ArrayList<Action> actions = (ArrayList<Action>) content;
int ind = list.getSelectedIndex();
if (ind < 0) {
ind = alm.getSize();
}
alm.addAll(ind, (List<Action>) actions);
list.setSelectionInterval(ind,ind += actions.size() - 1);
}
// throw unsupported flavor exception?
}
}
public static void ActionsUndo(JList<Action> list)
{
if (!(list instanceof ActionList)) {
return;
}
ActionList l = (ActionList) list;
if (l.undomanager.canUndo())
l.undomanager.undo();
}
public static void ActionsRedo(JList<Action> list)
{
if (!(list instanceof ActionList)) {
return;
}
ActionList l = (ActionList) list;
if (l.undomanager.canRedo())
l.undomanager.redo();
}
public static void ActionsDelete(JList<Action> list)
{
int[] indices = list.getSelectedIndices();
ActionListModel alm = (ActionListModel) list.getModel();
List<Integer> inds = new ArrayList<Integer>();
for (int i : indices) {
inds.add(i);
}
alm.removeAll(inds);
if (indices.length != 0) list.setSelectedIndex(Math.min(alm.getSize() - 1,indices[0]));
}
public static void ActionsSelectAll(JList<Action> list)
{
int start = 0;
int end = list.getModel().getSize() - 1;
if (end >= 0)
{
list.setSelectionInterval(start,end);
}
}
public static void ActionsClear(JList<Action> list)
{
ActionListModel alm = (ActionListModel) list.getModel();
alm.clear();
}
public void actionPerformed(ActionEvent ev)
{
String com = ev.getActionCommand();
if (com.endsWith("EDIT"))
{
ActionsEdit(this);
}
else if (com.endsWith("CUT"))
{
ActionsCut(this);
}
else if (com.endsWith("COPY"))
{
ActionsCopy(this);
}
else if (com.endsWith("PASTE"))
{
ActionsPaste(this);
}
else if (com.endsWith("UNDO"))
{
ActionsUndo(this);
}
else if (com.endsWith("REDO"))
{
ActionsRedo(this);
}
else if (com.endsWith("SELECTALL"))
{
ActionsSelectAll(this);
}
else if (com.endsWith("DELETE"))
{
ActionsDelete(this);
}
else if (com.endsWith("CLEAR"))
{
ActionsClear(this);
}
}
public void lostOwnership(Clipboard arg0, Transferable arg1)
{
// TODO Auto-generated method stub
// You could hold the transferable in something like a lastTransferable
// field so if the user hits paste it uses the last transferable instead
// of doing nothing, assuming this is the purpose of this lost ownership
// method in Java.
}
}