diff --git a/CHANGELOG.md b/CHANGELOG.md index ddfb5b35a..7f0c82452 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ All notable changes to this project will be documented in this file. - Allowed copy/cut tags to clipboard across multiple SWFs - Keyboard shortcuts for tag clipboard operations - Hilight clipboard panel on copy/cut action for a few seconds +- Drag and drop to move/copy tags in the tag list view ## [16.2.0] - 2022-11-08 ### Added diff --git a/src/com/jpexs/decompiler/flash/gui/MainPanel.java b/src/com/jpexs/decompiler/flash/gui/MainPanel.java index 2e10a388e..f42b23d72 100644 --- a/src/com/jpexs/decompiler/flash/gui/MainPanel.java +++ b/src/com/jpexs/decompiler/flash/gui/MainPanel.java @@ -399,65 +399,65 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se private boolean clipboardCut = false; - private void handleTreeKeyPressed(KeyEvent e) { - AbstractTagTree tree = (AbstractTagTree)e.getSource(); + AbstractTagTree tree = (AbstractTagTree) e.getSource(); if ((e.getKeyCode() == 'C' || e.getKeyCode() == 'X') && (e.isControlDown())) { - TreePath[] paths = tree.getSelectionPaths(); - if (paths == null || paths.length == 0) { - return; - } + TreePath[] paths = tree.getSelectionPaths(); + if (paths == null || paths.length == 0) { + return; + } - List tagItems = new ArrayList<>(); - for (TreePath treePath : paths) { - TreeItem item = (TreeItem) treePath.getLastPathComponent(); - if (item instanceof TagScript) { - tagItems.add(((TagScript) item).getTag()); - } else if (item instanceof Tag) { - tagItems.add((Tag) item); + List tagItems = new ArrayList<>(); + for (TreePath treePath : paths) { + TreeItem item = (TreeItem) treePath.getLastPathComponent(); + if (item instanceof TagScript) { + tagItems.add(((TagScript) item).getTag()); + } else if (item instanceof Tag) { + tagItems.add((Tag) item); + } } - } - if (e.getKeyCode() == 'C') { - if (e.isShiftDown()) { - contextPopupMenu.copyTagToClipboardWithDependenciesActionPerformed(null, tagItems); - } else { - contextPopupMenu.copyTagToClipboardActionPerformed(null, tagItems); + if (e.getKeyCode() == 'C') { + if (e.isShiftDown()) { + contextPopupMenu.copyTagToClipboardWithDependenciesActionPerformed(null, tagItems); + } else { + contextPopupMenu.copyTagToClipboardActionPerformed(null, tagItems); + } } - } - if (e.getKeyCode() == 'X') { - if (e.isShiftDown()) { - contextPopupMenu.cutTagToClipboardWithDependenciesActionPerformed(null); - } else { - contextPopupMenu.cutTagToClipboardActionPerformed(null); + if (e.getKeyCode() == 'X') { + if (e.isShiftDown()) { + contextPopupMenu.cutTagToClipboardWithDependenciesActionPerformed(null); + } else { + contextPopupMenu.cutTagToClipboardActionPerformed(null); + } } + repaintTree(); } - repaintTree(); - } - if (e.getKeyCode() == 'V' && e.isControlDown()) { - TreePath[] paths = tree.getSelectionPaths(); - if (paths == null || paths.length == 0) { - return; - } + if (e.getKeyCode() == 'V' && e.isControlDown()) { + TreePath[] paths = tree.getSelectionPaths(); + if (paths == null || paths.length == 0) { + return; + } - List items = new ArrayList<>(); - for (TreePath treePath : paths) { - TreeItem item = (TreeItem) treePath.getLastPathComponent(); - items.add(item); + List items = new ArrayList<>(); + for (TreePath treePath : paths) { + TreeItem item = (TreeItem) treePath.getLastPathComponent(); + items.add(item); + } + if (items.size() > 1) { + return; + } + TreeItem firstItem = items.get(0); + if (!((firstItem instanceof Tag) || (firstItem instanceof Frame))) { + return; + } + if (e.isShiftDown()) { + contextPopupMenu.pasteAfterActionPerformed(null); + } else { + contextPopupMenu.pasteBeforeActionPerformed(null); + } } - if (items.size() > 1) { - return; - } - TreeItem firstItem = items.get(0); - if (!((firstItem instanceof Tag) || (firstItem instanceof Frame))) { - return; - } - if (e.isShiftDown()) { - contextPopupMenu.pasteAfterActionPerformed(null); - } else { - contextPopupMenu.pasteBeforeActionPerformed(null); - } - } } + public void gcClipboard() { for (int i = orderedClipboard.size() - 1; i >= 0; i--) { WeakReference ref = orderedClipboard.get(i); @@ -4830,9 +4830,11 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se calculateMissingNeededCharacters(missingNeededCharacters, swf); } } - this.missingNeededCharacters = missingNeededCharacters; - tagTree.setMissingNeededCharacters(missingNeededCharacters); - tagListTree.setMissingNeededCharacters(missingNeededCharacters); + if (!missingNeededCharacters.equals(this.missingNeededCharacters)) { + this.missingNeededCharacters = missingNeededCharacters; + tagTree.setMissingNeededCharacters(missingNeededCharacters); + tagListTree.setMissingNeededCharacters(missingNeededCharacters); + } } } diff --git a/src/com/jpexs/decompiler/flash/gui/taglistview/TagListTree.java b/src/com/jpexs/decompiler/flash/gui/taglistview/TagListTree.java index 19b8f53c1..bc5dd7e29 100644 --- a/src/com/jpexs/decompiler/flash/gui/taglistview/TagListTree.java +++ b/src/com/jpexs/decompiler/flash/gui/taglistview/TagListTree.java @@ -21,29 +21,49 @@ import com.jpexs.decompiler.flash.gui.MainPanel; import com.jpexs.decompiler.flash.gui.tagtree.AbstractTagTree; import static com.jpexs.decompiler.flash.gui.tagtree.AbstractTagTree.getSelection; import com.jpexs.decompiler.flash.tags.DoInitActionTag; +import com.jpexs.decompiler.flash.tags.Tag; +import com.jpexs.decompiler.flash.timeline.Frame; +import com.jpexs.decompiler.flash.timeline.Timelined; import com.jpexs.decompiler.flash.treeitems.TreeItem; +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.Transferable; +import java.awt.datatransfer.UnsupportedFlavorException; +import java.io.IOException; +import java.util.ArrayList; +import java.util.LinkedHashSet; import java.util.List; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.swing.DropMode; +import javax.swing.JComponent; +import javax.swing.JTree; +import javax.swing.TransferHandler; +import javax.swing.tree.TreePath; /** * * @author JPEXS */ public class TagListTree extends AbstractTagTree { - + public TagListTree(TagListTreeModel model, MainPanel mainPanel) { - super(model, mainPanel); - setCellRenderer(new TagListTreeCellRenderer()); - } - + super(model, mainPanel); + setCellRenderer(new TagListTreeCellRenderer()); + setDragEnabled(true); + setDropMode(DropMode.ON_OR_INSERT); + setTransferHandler(new TreeTransferHandler(mainPanel)); + } + @Override public List getSelection(SWF swf) { return getSelection(swf, getAllSelected()); } - + @Override public TagListTreeModel getModel() { return (TagListTreeModel) super.getModel(); - } + } @Override public String convertValueToText(Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) { @@ -51,7 +71,7 @@ public class TagListTree extends AbstractTagTree { DoInitActionTag tag = (DoInitActionTag) value; return DoInitActionTag.NAME + " (" + tag.spriteId + ")"; } - if(value != null) { + if (value != null) { String sValue = value.toString(); if (sValue != null) { return sValue; @@ -60,5 +80,173 @@ public class TagListTree extends AbstractTagTree { return ""; } - +} + +class TreeTransferHandler extends TransferHandler { + + DataFlavor nodesFlavor; + DataFlavor[] flavors = new DataFlavor[1]; + JTree.DropLocation dropLocation = null; + MainPanel mainPanel; + + public TreeTransferHandler(MainPanel mainPanel) { + this.mainPanel = mainPanel; + try { + String mimeType = DataFlavor.javaJVMLocalObjectMimeType + + ";class=\"" + + Tag[].class.getName() + + "\""; + nodesFlavor = new DataFlavor(mimeType); + flavors[0] = nodesFlavor; + } catch (ClassNotFoundException e) { + System.err.println("ClassNotFound: " + e.getMessage()); + } + } + + @Override + public boolean canImport(TransferSupport support) { + if (!support.isDrop()) { + return false; + } + support.setShowDropLocation(true); + if (!support.isDataFlavorSupported(nodesFlavor)) { + return false; + } + // Do not allow a drop on the drag source selections. + JTree.DropLocation dl = (JTree.DropLocation) support.getDropLocation(); + + if ((dl.getPath().getLastPathComponent() instanceof Tag) && dl.getChildIndex() == -1) { + return false; + } + + AbstractTagTree tree = (AbstractTagTree) support.getComponent(); + + List selected = tree.getSelected(); + TreePath destPath = dl.getPath(); + List parents = new ArrayList<>(); + for(int i = 0; i < destPath.getPathCount(); i++) { + parents.add((TreeItem) destPath.getPathComponent(i)); + } + for (TreeItem item : selected) { + if (parents.contains(item)) { + return false; + } + } + + int dropRow = tree.getRowForPath(dl.getPath()); + int[] selRows = tree.getSelectionRows(); + + for (int i = 0; i < selRows.length; i++) { + if (selRows[i] == dropRow) { + return false; + } + } + return true; + } + + @Override + protected Transferable createTransferable(JComponent c) { + AbstractTagTree tree = (AbstractTagTree) c; + dropLocation = null; + TreePath[] paths = tree.getSelectionPaths(); + if (paths == null) { + return null; + } + List tags = new ArrayList<>(); + for (TreePath path : paths) { + if (path.getLastPathComponent() instanceof Tag) { + tags.add((Tag) path.getLastPathComponent()); + } else { + return null; + } + } + Tag tagArr[] = tags.toArray(new Tag[tags.size()]); + return new TagsTransferable(tagArr); + } + + @Override + public int getSourceActions(JComponent c) { + return COPY_OR_MOVE; + } + + @Override + protected void exportDone(JComponent source, Transferable data, int action) { + AbstractTagTree tree = (AbstractTagTree) source; + if (dropLocation == null) { + return; + } + int childIndex = dropLocation.getChildIndex(); + TreeItem dest = (TreeItem) dropLocation.getPath().getLastPathComponent(); + Set sourceItems = new LinkedHashSet<>(tree.getSelected()); + Timelined timelined; + Tag position; + if (childIndex == -1) { + if (dest instanceof Tag) { + timelined = ((Tag) dest).getTimelined(); + position = (Tag) dest; + } else if (dest instanceof Frame) { + Frame frame = (Frame) dest; + position = frame.allInnerTags.get(frame.allInnerTags.size() - 1); + timelined = frame.timeline.timelined; + } else { + timelined = (Timelined) dest; + position = null; + } + } else { + if (dest instanceof Frame) { + Frame frame = (Frame) dest; + timelined = frame.timeline.timelined; + position = childIndex == frame.allInnerTags.size() ? null : frame.allInnerTags.get(childIndex); + } else { + timelined = ((Tag) dest).getTimelined(); + position = (Tag) dest; + } + } + mainPanel.getContextPopupMenu().copyOrMoveTagsBeforeAfter(sourceItems, (action & MOVE) == MOVE, timelined, position); + } + + @Override + public boolean importData(TransferSupport support) { + if (!canImport(support)) { + return false; + } + Transferable t = support.getTransferable(); + Tag[] tags = null; + try { + tags = (Tag[]) t.getTransferData(nodesFlavor); + } catch (UnsupportedFlavorException | IOException ex) { + Logger.getLogger(TreeTransferHandler.class.getName()).log(Level.SEVERE, null, ex); + } + + dropLocation = (JTree.DropLocation) support.getDropLocation(); + return true; + } + + public class TagsTransferable implements Transferable { + + Tag[] nodes; + + public TagsTransferable(Tag[] nodes) { + this.nodes = nodes; + } + + @Override + public Object getTransferData(DataFlavor flavor) + throws UnsupportedFlavorException { + if (!isDataFlavorSupported(flavor)) { + throw new UnsupportedFlavorException(flavor); + } + return nodes; + } + + @Override + public DataFlavor[] getTransferDataFlavors() { + return flavors; + } + + @Override + public boolean isDataFlavorSupported(DataFlavor flavor) { + return nodesFlavor.equals(flavor); + } + } } diff --git a/src/com/jpexs/decompiler/flash/gui/taglistview/TagListTreeCellRenderer.java b/src/com/jpexs/decompiler/flash/gui/taglistview/TagListTreeCellRenderer.java index 164446ae0..1dcc65bf3 100644 --- a/src/com/jpexs/decompiler/flash/gui/taglistview/TagListTreeCellRenderer.java +++ b/src/com/jpexs/decompiler/flash/gui/taglistview/TagListTreeCellRenderer.java @@ -17,7 +17,6 @@ package com.jpexs.decompiler.flash.gui.taglistview; import com.jpexs.decompiler.flash.gui.AppStrings; -import com.jpexs.decompiler.flash.gui.TreeNodeType; import com.jpexs.decompiler.flash.gui.View; import com.jpexs.decompiler.flash.gui.tagtree.AbstractTagTree; import com.jpexs.decompiler.flash.gui.tagtree.TagTree; @@ -57,9 +56,9 @@ public class TagListTreeCellRenderer extends DefaultTreeCellRenderer { setUI(new BasicLabelUI()); setOpaque(false); setBackgroundNonSelectionColor(Color.white); - } - } - + } + } + @Override protected void paintComponent(Graphics g) { super.paintComponent(g); @@ -73,8 +72,8 @@ public class TagListTreeCellRenderer extends DefaultTreeCellRenderer { g2d.fillRect(0, 0, getWidth(), getHeight()); } } - } - + } + @Override diff --git a/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeContextMenu.java b/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeContextMenu.java index 239d09eff..44b317635 100644 --- a/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeContextMenu.java +++ b/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeContextMenu.java @@ -2740,7 +2740,11 @@ public class TagTreeContextMenu extends JPopupMenu { } ReadOnlyTagList tags = timelined.getTags(); position = positionInt < tags.size() ? tags.get(positionInt) : null; - pasteBeforeAfter(true, timelined, position); + copyOrMoveTagsBeforeAfter(mainPanel.getClipboardContents(), mainPanel.isClipboardCut(), timelined, position); + + if (mainPanel.isClipboardCut()) { + mainPanel.emptyClipboard(); + } } public void pasteAfterActionPerformed(ActionEvent evt) { @@ -2758,7 +2762,11 @@ public class TagTreeContextMenu extends JPopupMenu { } ReadOnlyTagList tags = timelined.getTags(); position = positionInt < tags.size() ? tags.get(positionInt) : null; - pasteBeforeAfter(false, timelined, position); + copyOrMoveTagsBeforeAfter(mainPanel.getClipboardContents(), mainPanel.isClipboardCut(), timelined, position); + + if (mainPanel.isClipboardCut()) { + mainPanel.emptyClipboard(); + } } private void pasteInsideActionPerformed(ActionEvent evt) { @@ -2773,11 +2781,32 @@ public class TagTreeContextMenu extends JPopupMenu { timelined = (Timelined) item; position = null; } - pasteBeforeAfter(false, timelined, position); + copyOrMoveTagsBeforeAfter(mainPanel.getClipboardContents(), mainPanel.isClipboardCut(), timelined, position); + + if (mainPanel.isClipboardCut()) { + mainPanel.emptyClipboard(); + } } - private void pasteBeforeAfter(boolean before, Timelined targetTimelined, Tag position) { - Set items = mainPanel.getClipboardContents(); + public void copyOrMoveTagsBeforeAfter(Set items, boolean move, Timelined targetTimelined, Tag position) { + //do not move to self + if (items.size() == 1 && move) { + Tag tag = (Tag) items.iterator().next(); + if (targetTimelined == tag.getTimelined()) { + if (tag == position) { + return; + } + int index = tag.getTimelined().indexOfTag(tag); + ReadOnlyTagList tags = tag.getTimelined().getTags(); + Tag nextPosition; + if (index + 1 < tags.size()) { + nextPosition = tags.get(index + 1); + if (nextPosition == position) { + return; + } + } + } + } Set sourceSwfs = new LinkedHashSet<>(); SWF targetSwf = (targetTimelined instanceof SWF) ? (SWF) targetTimelined : ((DefineSpriteTag) targetTimelined).getSwf(); try { @@ -2791,7 +2820,15 @@ public class TagTreeContextMenu extends JPopupMenu { SWF sourceSwf = tag.getSwf(); sourceSwfs.add(sourceSwf); - if (mainPanel.isClipboardCut()) { + if (move) { + if (tag == position) { + int index = tag.getTimelined().indexOfTag(position); + if (tag.getTimelined().getTags().size() > index + 1) { + position = tag.getTimelined().getTags().get(index + 1); + } else { + position = null; + } + } tag.getTimelined().removeTag(tag); } @@ -2808,7 +2845,7 @@ public class TagTreeContextMenu extends JPopupMenu { realTargetTimelined = spriteTag.getTimelined(); //should be SWF realPosition = spriteTag; } - if (sourceSwf != targetSwf || !mainPanel.isClipboardCut()) { + if (sourceSwf != targetSwf || !move) { ReadOnlyTagList tags = realTargetTimelined.getTags(); int positionInt = realPosition == null ? tags.size() : tags.indexOf(realPosition); @@ -2828,7 +2865,7 @@ public class TagTreeContextMenu extends JPopupMenu { targetSwf.getCharacters(); // force rebuild character id cache copyTag.setModified(true); newTags.add(copyTag); - } else if (sourceSwf == targetSwf && mainPanel.isClipboardCut()) { + } else if (sourceSwf == targetSwf && move) { //mainPanel.isClipboardCut() ReadOnlyTagList tags = realTargetTimelined.getTags(); int positionInt = realPosition == null ? tags.size() : tags.indexOf(realPosition); realTargetTimelined.addTag(positionInt, tag); @@ -2857,10 +2894,7 @@ public class TagTreeContextMenu extends JPopupMenu { targetSwf.clearImageCache(); targetSwf.updateCharacters(); targetSwf.resetTimelines(targetSwf); - - if (mainPanel.isClipboardCut()) { - mainPanel.emptyClipboard(); - } + mainPanel.refreshTree(targetSwf); } catch (InterruptedException | IOException ex) { Logger.getLogger(TagTreeContextMenu.class.getName()).log(Level.SEVERE, null, ex);