diff --git a/CHANGELOG.md b/CHANGELOG.md index 909e9029e..eddc91b54 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,11 +4,13 @@ All notable changes to this project will be documented in this file. ## [Unreleased] ### Added - [#1414] Cancelling in-progress exportation +- [#1755] Copy tags to tag clipboard and paste them elsewhere ### Fixed - FLA export printing xxx string on exporting character with id 320 - Copy to with dependencies does not refresh timeline - Copy to with dependencies does not set the timelined, that can result to missing dependencies (red tags in the tree) +- Double warning/error when copy to / move to and same character id already exists ## [16.1.0] - 2022-11-06 ### Added @@ -2511,6 +2513,7 @@ All notable changes to this project will be documented in this file. [alpha 8]: https://github.com/jindrapetrik/jpexs-decompiler/compare/alpha7...alpha8 [alpha 7]: https://github.com/jindrapetrik/jpexs-decompiler/releases/tag/alpha7 [#1414]: https://www.free-decompiler.com/flash/issues/1414 +[#1755]: https://www.free-decompiler.com/flash/issues/1755 [#1459]: https://www.free-decompiler.com/flash/issues/1459 [#1832]: https://www.free-decompiler.com/flash/issues/1832 [#1849]: https://www.free-decompiler.com/flash/issues/1849 diff --git a/src/com/jpexs/decompiler/flash/gui/MainPanel.java b/src/com/jpexs/decompiler/flash/gui/MainPanel.java index d7ad40452..52f46e531 100644 --- a/src/com/jpexs/decompiler/flash/gui/MainPanel.java +++ b/src/com/jpexs/decompiler/flash/gui/MainPanel.java @@ -218,8 +218,11 @@ import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; @@ -379,6 +382,67 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se private Thread calculateMissingNeededThread; + private List> orderedClipboard = new ArrayList<>(); + private Map clipboard = new WeakHashMap<>(); + + private boolean clipboardCut = false; + + public void gcClipboard() { + for (int i = orderedClipboard.size() - 1; i >= 0; i--) { + WeakReference ref = orderedClipboard.get(i); + TreeItem item = ref.get(); + if (item != null) { + if (item.getSwf() == null) { + orderedClipboard.remove(i); + clipboard.remove(item); + } + } + } + } + + public void emptyClipboard() { + copyToClipboard(new ArrayList<>()); + } + + public void copyToClipboard(Collection items) { + orderedClipboard.clear(); + clipboard.clear(); + for (TreeItem item : items) { + orderedClipboard.add(new WeakReference<>(item)); + clipboard.put(item, true); + } + clipboardCut = false; + } + + public void cutToClipboard(Collection items) { + copyToClipboard(items); + clipboardCut = true; + } + + public boolean clipboardContains(TreeItem item) { + return clipboard.containsKey(item); + } + + public boolean clipboardEmpty() { + return clipboard.isEmpty(); + } + + public Set getClipboardContents() { + Set ret = new LinkedHashSet<>(); + for (WeakReference ref : orderedClipboard) { + TreeItem item = ref.get(); + if (item != null) { + ret.add(item); + } + } + return ret; + } + + public boolean isClipboardCut() { + return clipboardCut; + } + + private class MyTreeSelectionModel extends DefaultTreeSelectionModel { private boolean isModified() { @@ -939,6 +1003,7 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se updateUi(swf); } + gcClipboard(); doFilter(); reload(false); View.expandTreeNodes(tagTree, expandedNodes); @@ -955,6 +1020,8 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se if (swf != null) { updateUi(swf); } + + gcClipboard(); doFilter(); reload(false); @@ -1097,6 +1164,7 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se refreshTree(); + gcClipboard(); mainMenu.updateComponents(null); previewPanel.clear(); @@ -1152,6 +1220,8 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se refreshTree(); + gcClipboard(); + mainMenu.updateComponents(null); previewPanel.clear(); dumpPreviewPanel.clear(); diff --git a/src/com/jpexs/decompiler/flash/gui/graphics/clipboard16.png b/src/com/jpexs/decompiler/flash/gui/graphics/clipboard16.png new file mode 100644 index 000000000..e212f7844 Binary files /dev/null and b/src/com/jpexs/decompiler/flash/gui/graphics/clipboard16.png differ diff --git a/src/com/jpexs/decompiler/flash/gui/graphics/cut16.png b/src/com/jpexs/decompiler/flash/gui/graphics/cut16.png new file mode 100644 index 000000000..f215d6f6b Binary files /dev/null and b/src/com/jpexs/decompiler/flash/gui/graphics/cut16.png differ diff --git a/src/com/jpexs/decompiler/flash/gui/graphics/paste16.png b/src/com/jpexs/decompiler/flash/gui/graphics/paste16.png new file mode 100644 index 000000000..c0490eb79 Binary files /dev/null and b/src/com/jpexs/decompiler/flash/gui/graphics/paste16.png differ diff --git a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties index c5189d0df..5bcd2efe4 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties @@ -915,4 +915,13 @@ new.filename = untitled error.missing.characterTag.single = ERROR: The tag requires character tag %tag% but it is not defined before this tag. \ Define this character or change tag order by moving tag into proper position. error.missing.characterTags.multi = ERROR: The tag requires character tags %tags% but they are not defined before this tag. \ -Define these characters or change tag order by moving tags into proper position. \ No newline at end of file +Define these characters or change tag order by moving tags into proper position. + +#after 16.1.0 +contextmenu.clipboard = Tag clipboard +contextmenu.cutTag = Cut to tag clipboard +contextmenu.cutTagWithDependencies = Cut to tag clipboard with dependencies +contextmenu.paste = Paste +contextmenu.pasteBefore = Paste before +contextmenu.pasteAfter = Paste after +contextmenu.pasteInside = Paste inside \ No newline at end of file diff --git a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_cs.properties b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_cs.properties index 17afa9db5..2507673a9 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_cs.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_cs.properties @@ -885,4 +885,13 @@ new.filename = bezn\u00e1zvu error.missing.characterTag.single = CHYBA: Tento tag vy\u017eaduje charakterov\u00fd tag %tag%, ale ten nen\u00ed p\u0159ed t\u00edmto tagem definov\u00e1n. \ Definujte tento charakter nebo zm\u011b\u0148te po\u0159ad\u00ed tag\u016f jeho p\u0159esunut\u00edm do spr\u00e1vn\u00e9 polohy. error.missing.characterTags.multi = CHYBA: Tento tag vy\u017eaduje charakterov\u00e9 tagy %tags%, ale tyto nejsou p\u0159ed t\u00edmto tagem definov\u00e1ny. \ -Definujte tyto charaktery nebo zm\u011b\u0148te po\u0159ad\u00ed tag\u016f jejich p\u0159esunut\u00edm do spr\u00e1vn\u00e9 polohy. \ No newline at end of file +Definujte tyto charaktery nebo zm\u011b\u0148te po\u0159ad\u00ed tag\u016f jejich p\u0159esunut\u00edm do spr\u00e1vn\u00e9 polohy. + +#after 16.1.0 +contextmenu.clipboard = Tagov\u00e1 schr\u00e1nka +contextmenu.cutTag = Vyjmout do tagov\u00e9 schr\u00e1nky +contextmenu.cutTagWithDependencies = Vyjmout do tagov\u00e9 schr\u00e1nky se z\u00e1vislostmi +contextmenu.paste = Vlo\u017eit +contextmenu.pasteBefore = Vlo\u017eit p\u0159ed +contextmenu.pasteAfter = Vlo\u017eit za +contextmenu.pasteInside = Vlo\u017eit dovnit\u0159 \ No newline at end of file diff --git a/src/com/jpexs/decompiler/flash/gui/taglistview/TagListTreeCellRenderer.java b/src/com/jpexs/decompiler/flash/gui/taglistview/TagListTreeCellRenderer.java index 45b02cd82..164446ae0 100644 --- a/src/com/jpexs/decompiler/flash/gui/taglistview/TagListTreeCellRenderer.java +++ b/src/com/jpexs/decompiler/flash/gui/taglistview/TagListTreeCellRenderer.java @@ -25,9 +25,12 @@ import com.jpexs.decompiler.flash.tags.Tag; import com.jpexs.decompiler.flash.timeline.Frame; import com.jpexs.decompiler.flash.treeitems.SWFList; import com.jpexs.decompiler.flash.treeitems.TreeItem; +import java.awt.AlphaComposite; import java.awt.Color; import java.awt.Component; import java.awt.Font; +import java.awt.Graphics; +import java.awt.Graphics2D; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -46,6 +49,8 @@ public class TagListTreeCellRenderer extends DefaultTreeCellRenderer { private Font plainFont; private Font boldFont; + + private boolean semiTransparent = false; public TagListTreeCellRenderer() { if (View.isOceanic()) { @@ -55,6 +60,23 @@ public class TagListTreeCellRenderer extends DefaultTreeCellRenderer { } } + @Override + protected void paintComponent(Graphics g) { + super.paintComponent(g); + + if (semiTransparent) { + if (getIcon() != null) { + Color color = getBackground(); + Graphics2D g2d = (Graphics2D) g; + g2d.setColor(new Color(color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha() / 2)); + g2d.setComposite(AlphaComposite.SrcOver); + g2d.fillRect(0, 0, getWidth(), getHeight()); + } + } + } + + + @Override public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus) { TreeItem val = null; @@ -117,9 +139,14 @@ public class TagListTreeCellRenderer extends DefaultTreeCellRenderer { } else { lab.setToolTipText(AppStrings.translate("error.missing.characterTag.multi").replace("%tags%",String.join(", ", missingAsStr))); } - lab.setForeground(Color.red); + lab.setForeground(Color.red); } } + + semiTransparent = false; + if (aTree.getMainPanel().isClipboardCut() && aTree.getMainPanel().clipboardContains(val)) { + semiTransparent = true; + } } } return renderer; diff --git a/src/com/jpexs/decompiler/flash/gui/taglistview/TagListTreeModel.java b/src/com/jpexs/decompiler/flash/gui/taglistview/TagListTreeModel.java index f3946e74c..4a380628f 100644 --- a/src/com/jpexs/decompiler/flash/gui/taglistview/TagListTreeModel.java +++ b/src/com/jpexs/decompiler/flash/gui/taglistview/TagListTreeModel.java @@ -152,6 +152,9 @@ public class TagListTreeModel extends AbstractTagTreeModel { if (header == child) { return 0; } + if (!(child instanceof Frame)) { + return -1; + } return ((Frame)child).frame + 1; } else if (parentNode instanceof DefineSpriteTag) { if (((Frame)child).timeline != ((DefineSpriteTag)parentNode).getTimeline()) { diff --git a/src/com/jpexs/decompiler/flash/gui/tagtree/AbstractTagTree.java b/src/com/jpexs/decompiler/flash/gui/tagtree/AbstractTagTree.java index 8d5745450..875483a99 100644 --- a/src/com/jpexs/decompiler/flash/gui/tagtree/AbstractTagTree.java +++ b/src/com/jpexs/decompiler/flash/gui/tagtree/AbstractTagTree.java @@ -132,9 +132,13 @@ public abstract class AbstractTagTree extends JTree { String tagTypeStr = treeNodeType.toString().toLowerCase(Locale.ENGLISH).replace("_", ""); ICONS.put(treeNodeType, View.getIcon(tagTypeStr + "16")); } - } + } } + public MainPanel getMainPanel() { + return mainPanel; + } + public Map> getMissingNeededCharacters() { return missingNeededCharacters; } diff --git a/src/com/jpexs/decompiler/flash/gui/tagtree/TagTree.java b/src/com/jpexs/decompiler/flash/gui/tagtree/TagTree.java index e657c9daf..34a518529 100644 --- a/src/com/jpexs/decompiler/flash/gui/tagtree/TagTree.java +++ b/src/com/jpexs/decompiler/flash/gui/tagtree/TagTree.java @@ -123,9 +123,12 @@ import com.jpexs.decompiler.flash.treeitems.SWFList; import com.jpexs.decompiler.flash.treeitems.TreeItem; import com.jpexs.decompiler.flash.types.BUTTONCONDACTION; import com.jpexs.decompiler.flash.types.CLIPACTIONRECORD; +import java.awt.AlphaComposite; import java.awt.Color; import java.awt.Component; import java.awt.Font; +import java.awt.Graphics; +import java.awt.Graphics2D; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -142,12 +145,15 @@ import javax.swing.tree.TreePath; * @author JPEXS */ public class TagTree extends AbstractTagTree { + public static class TagTreeCellRenderer extends DefaultTreeCellRenderer { private Font plainFont; private Font boldFont; + private boolean semiTransparent = false; + public TagTreeCellRenderer() { setUI(new BasicLabelUI()); setOpaque(false); @@ -156,6 +162,21 @@ public class TagTree extends AbstractTagTree { } } + @Override + protected void paintComponent(Graphics g) { + super.paintComponent(g); + + if (semiTransparent) { + if (getIcon() != null) { + Color color = getBackground(); + Graphics2D g2d = (Graphics2D) g; + g2d.setColor(new Color(color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha() / 2)); + g2d.setComposite(AlphaComposite.SrcOver); + g2d.fillRect(0, 0, getWidth(), getHeight()); + } + } + } + @Override public Component getTreeCellRendererComponent( JTree tree, @@ -228,34 +249,39 @@ public class TagTree extends AbstractTagTree { } } setToolTipText(null); - + AbstractTagTree aTree = (AbstractTagTree) tree; - Map> allMissingNeededCharacters = aTree.getMissingNeededCharacters(); - if (allMissingNeededCharacters.containsKey((TreeItem)value)) { - Set missingNeededCharacters = allMissingNeededCharacters.get(value); - if (!missingNeededCharacters.isEmpty()) { - List missingAsStr = new ArrayList<>(); - for (int v:missingNeededCharacters) { - missingAsStr.add("" + v); - } - if (missingAsStr.size() == 1) { - setToolTipText(AppStrings.translate("error.missing.characterTag.single").replace("%tag%", missingAsStr.get(0))); - } else { - setToolTipText(AppStrings.translate("error.missing.characterTag.multi").replace("%tags%",String.join(", ", missingAsStr))); - } - setForeground(Color.red); + Map> allMissingNeededCharacters = aTree.getMissingNeededCharacters(); + if (allMissingNeededCharacters.containsKey((TreeItem) value)) { + Set missingNeededCharacters = allMissingNeededCharacters.get(value); + if (!missingNeededCharacters.isEmpty()) { + List missingAsStr = new ArrayList<>(); + for (int v : missingNeededCharacters) { + missingAsStr.add("" + v); } + if (missingAsStr.size() == 1) { + setToolTipText(AppStrings.translate("error.missing.characterTag.single").replace("%tag%", missingAsStr.get(0))); + } else { + setToolTipText(AppStrings.translate("error.missing.characterTag.multi").replace("%tags%", String.join(", ", missingAsStr))); + } + setForeground(Color.red); } + } + + semiTransparent = false; + if (aTree.getMainPanel().isClipboardCut() && aTree.getMainPanel().clipboardContains(val)) { + semiTransparent = true; + } return this; } } public TagTree(TagTreeModel treeModel, MainPanel mainPanel) { - super(treeModel, mainPanel); + super(treeModel, mainPanel); setCellRenderer(new TagTreeCellRenderer()); } - + public static List getSwfFolderItemNestedTagIds(String folderName, boolean gfx) { List ret = new ArrayList<>(); switch (folderName) { @@ -317,7 +343,7 @@ public class TagTree extends AbstractTagTree { return ret; } - + @Override public List getSelection(SWF swf) { List sel; @@ -358,8 +384,5 @@ public class TagTree extends AbstractTagTree { public TagTreeModel getModel() { return (TagTreeModel) super.getModel(); } - - - } diff --git a/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeContextMenu.java b/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeContextMenu.java index 0d565b46f..fa563faac 100644 --- a/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeContextMenu.java +++ b/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeContextMenu.java @@ -83,6 +83,7 @@ import com.jpexs.decompiler.graph.DottedChain; import com.jpexs.helpers.Helper; import com.jpexs.helpers.Reference; import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.io.IOException; @@ -163,6 +164,16 @@ public class TagTreeContextMenu extends JPopupMenu { private JMenu copyTagToWithDependenciesMenu; + private JMenuItem cutTagToClipboardMenuItem; + + private JMenuItem cutTagToClipboardWithDependenciesMenuItem; + + private JMenuItem pasteBeforeMenuItem; + + private JMenuItem pasteAfterMenuItem; + + private JMenuItem pasteInsideMenuItem; + private JMenuItem openSWFInsideTagMenuItem; private JMenuItem addAs12ScriptMenuItem; @@ -176,7 +187,7 @@ public class TagTreeContextMenu extends JPopupMenu { private JMenuItem showInResourcesViewTagMenuItem; private JMenuItem showInTagListViewTagMenuItem; - + private JMenuItem showInHexDumpViewTagMenuItem; private JMenuItem addFramesMenuItem; @@ -275,7 +286,7 @@ public class TagTreeContextMenu extends JPopupMenu { showInTagListViewTagMenuItem.addActionListener(this::showInTagListViewActionPerformed); showInTagListViewTagMenuItem.setIcon(View.getIcon("taglist16")); add(showInTagListViewTagMenuItem); - + showInHexDumpViewTagMenuItem = new JMenuItem(mainPanel.translate("contextmenu.showInHexDump")); showInHexDumpViewTagMenuItem.addActionListener(this::showInHexDumpViewActionPerformed); showInHexDumpViewTagMenuItem.setIcon(View.getIcon("viewhex16")); @@ -319,6 +330,31 @@ public class TagTreeContextMenu extends JPopupMenu { copyTagToWithDependenciesMenu.setIcon(View.getIcon("copy16")); add(copyTagToWithDependenciesMenu); + cutTagToClipboardMenuItem = new JMenuItem(mainPanel.translate("contextmenu.cutTag")); + cutTagToClipboardMenuItem.setIcon(View.getIcon("cut16")); + cutTagToClipboardMenuItem.addActionListener(this::cutTagToClipboardActionPerformed); + add(cutTagToClipboardMenuItem); + + cutTagToClipboardWithDependenciesMenuItem = new JMenuItem(mainPanel.translate("contextmenu.cutTagWithDependencies")); + cutTagToClipboardWithDependenciesMenuItem.setIcon(View.getIcon("cut16")); + cutTagToClipboardWithDependenciesMenuItem.addActionListener(this::cutTagToClipboardWithDependenciesActionPerformed); + add(cutTagToClipboardWithDependenciesMenuItem); + + pasteBeforeMenuItem = new JMenuItem(mainPanel.translate("contextmenu.pasteBefore")); + pasteBeforeMenuItem.setIcon(View.getIcon("paste16")); + pasteBeforeMenuItem.addActionListener(this::pasteBeforeActionPerformed); + add(pasteBeforeMenuItem); + + pasteAfterMenuItem = new JMenuItem(mainPanel.translate("contextmenu.pasteAfter")); + pasteAfterMenuItem.setIcon(View.getIcon("paste16")); + pasteAfterMenuItem.addActionListener(this::pasteAfterActionPerformed); + add(pasteAfterMenuItem); + + pasteInsideMenuItem = new JMenuItem(mainPanel.translate("contextmenu.pasteInside")); + pasteInsideMenuItem.setIcon(View.getIcon("paste16")); + pasteInsideMenuItem.addActionListener(this::pasteInsideActionPerformed); + add(pasteInsideMenuItem); + openSWFInsideTagMenuItem = new JMenuItem(mainPanel.translate("contextmenu.openswfinside")); openSWFInsideTagMenuItem.setIcon(View.getIcon("openinside16")); openSWFInsideTagMenuItem.addActionListener(this::openSwfInsideActionPerformed); @@ -596,6 +632,11 @@ public class TagTreeContextMenu extends JPopupMenu { moveTagToMenu.setVisible(false); copyTagToMenu.setVisible(false); copyTagToWithDependenciesMenu.setVisible(false); + cutTagToClipboardMenuItem.setVisible(false); + cutTagToClipboardWithDependenciesMenuItem.setVisible(false); + pasteAfterMenuItem.setVisible(false); + pasteBeforeMenuItem.setVisible(false); + pasteInsideMenuItem.setVisible(false); openSWFInsideTagMenuItem.setVisible(false); addAs12ScriptMenuItem.setVisible(false); addAs3ClassMenuItem.setVisible(false); @@ -603,7 +644,7 @@ public class TagTreeContextMenu extends JPopupMenu { moveTagMenuItem.setVisible(items.size() == 1 && (items.get(0) instanceof Tag)); showInResourcesViewTagMenuItem.setVisible(false); showInTagListViewTagMenuItem.setVisible(false); - showInHexDumpViewTagMenuItem.setVisible(false); + showInHexDumpViewTagMenuItem.setVisible(false); addFramesMenuItem.setVisible(false); addFramesBeforeMenuItem.setVisible(false); addFramesAfterMenuItem.setVisible(false); @@ -748,16 +789,52 @@ public class TagTreeContextMenu extends JPopupMenu { if (mainPanel.getCurrentView() == MainPanel.VIEW_RESOURCES && !isFolder) { showInTagListViewTagMenuItem.setVisible(true); } - + if (firstItem instanceof Tag) { showInHexDumpViewTagMenuItem.setVisible(true); } + + if (!mainPanel.clipboardEmpty()) { + if ((firstItem instanceof SWF) || (firstItem instanceof DefineSpriteTag) || (firstItem instanceof Frame)) { + pasteInsideMenuItem.setVisible(true); + } + if ((firstItem instanceof Tag) || (firstItem instanceof Frame)) { + pasteAfterMenuItem.setVisible(true); + pasteBeforeMenuItem.setVisible(true); + } + } } if (allSelectedIsInTheSameSwf && allSelectedIsTag && swfs.size() > 1) { moveTagToMenu.removeAll(); copyTagToMenu.removeAll(); copyTagToWithDependenciesMenu.removeAll(); + + if (allSelectedSameParent) { + JMenuItem copyToClipboardMenuItem = new JMenuItem(AppStrings.translate("contextmenu.clipboard"), View.getIcon("clipboard16")); + copyToClipboardMenuItem.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + copyTagToClipboardActionPerformed(e, items); + } + }); + + copyTagToMenu.add(copyToClipboardMenuItem); + + JMenuItem copyToClipboardWithDependenciesMenuItem = new JMenuItem(AppStrings.translate("contextmenu.clipboard"), View.getIcon("clipboard16")); + copyToClipboardWithDependenciesMenuItem.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + copyTagToClipboardWithDependenciesActionPerformed(e, items); + } + }); + + copyTagToWithDependenciesMenu.add(copyToClipboardWithDependenciesMenuItem); + + cutTagToClipboardMenuItem.setVisible(true); + cutTagToClipboardWithDependenciesMenuItem.setVisible(true); + } + for (SWFList targetSwfList : swfs) { if ((targetSwfList.size() == 1) && (targetSwfList.get(0) == singleSwf)) { continue; @@ -926,14 +1003,14 @@ public class TagTreeContextMenu extends JPopupMenu { addAddTagMenuItems(AbstractTagTree.getFrameNestedTagIds(), addTagMenu, item, listener); addTagMenu.addSeparator(); addTagMenu.add(createOthersMenu(item, listener)); - + return; } if (parent instanceof FolderItem) { List allowedTagTypes = new ArrayList<>(TagTree.getSwfFolderItemNestedTagIds(((FolderItem) parent).getName(), gfx)); - addAddTagMenuItems(allowedTagTypes, addTagMenu, item, listener); + addAddTagMenuItems(allowedTagTypes, addTagMenu, item, listener); addTagMenu.addSeparator(); addTagMenu.add(createOthersMenu(item, listener)); return; @@ -999,7 +1076,7 @@ public class TagTreeContextMenu extends JPopupMenu { } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException ex) { Logger.getLogger(TagTreeContextMenu.class.getName()).log(Level.SEVERE, null, ex); - } + } SWF swf = item.getSwf(); Timelined selectedTimelined = null; @@ -1156,8 +1233,8 @@ public class TagTreeContextMenu extends JPopupMenu { Tag tag = (Tag) item; sourceSwf.removeTag(tag); tag.setSwf(targetSwf, true); - targetSwf.addTag(tag); checkUniqueCharacterId(tag); + targetSwf.addTag(tag); targetSwf.updateCharacters(); tag.setModified(true); } @@ -1187,22 +1264,22 @@ public class TagTreeContextMenu extends JPopupMenu { Tag copyTag = tag.cloneTag(); copyTag.setSwf(targetSwf, true); copyTag.setTimelined(targetSwf); - targetSwf.addTag(copyTag); checkUniqueCharacterId(copyTag); + targetSwf.addTag(copyTag); targetSwf.updateCharacters(); copyTag.setModified(true); } - + if (lastIsShowFrame) { targetSwf.frameCount++; } - + targetSwf.resetTimelines(targetSwf); - + targetSwf.assignExportNamesToSymbols(); targetSwf.assignClassesToSymbols(); targetSwf.clearImageCache(); - targetSwf.updateCharacters(); + targetSwf.updateCharacters(); mainPanel.refreshTree(targetSwf); } catch (IOException | InterruptedException ex) { logger.log(Level.SEVERE, null, ex); @@ -1236,11 +1313,12 @@ public class TagTreeContextMenu extends JPopupMenu { if (!copiedTags.contains(neededTag)) { copyTag = neededTag.cloneTag(); copyTag.setSwf(targetSwf, true); - targetSwf.addTag(copyTag); copyTag.setTimelined(targetSwf); int oldCharacterId = neededTag.getCharacterId(); int newCharacterId = checkUniqueCharacterId(copyTag); changedCharacterIds.put(oldCharacterId, newCharacterId); + + targetSwf.addTag(copyTag); targetSwf.updateCharacters(); targetSwf.getCharacters(); // force rebuild character id cache @@ -1251,8 +1329,7 @@ public class TagTreeContextMenu extends JPopupMenu { } copyTag = tag.cloneTag(); - copyTag.setSwf(targetSwf, true); - targetSwf.addTag(copyTag); + copyTag.setSwf(targetSwf, true); copyTag.setTimelined(targetSwf); if (tag instanceof CharacterTag) { CharacterTag characterTag = (CharacterTag) copyTag; @@ -1260,6 +1337,7 @@ public class TagTreeContextMenu extends JPopupMenu { int newCharacterId = checkUniqueCharacterId(copyTag); changedCharacterIds.put(oldCharacterId, newCharacterId); } + targetSwf.addTag(copyTag); targetSwf.updateCharacters(); targetSwf.getCharacters(); // force rebuild character id cache @@ -1275,7 +1353,7 @@ public class TagTreeContextMenu extends JPopupMenu { } } } - + if (lastIsShowFrame) { targetSwf.frameCount++; } @@ -2239,10 +2317,10 @@ public class TagTreeContextMenu extends JPopupMenu { copyTag.setSwf(swf, true); Timelined timelined = tag.getTimelined(); int idx = timelined.indexOfTag(tag); - timelined.addTag(idx + 1, copyTag); copyTag.setTimelined(timelined); checkUniqueCharacterId(copyTag); + timelined.addTag(idx + 1, copyTag); copyTag.setModified(true); timelined.resetTimeline(); @@ -2267,10 +2345,12 @@ public class TagTreeContextMenu extends JPopupMenu { Tag copyTag = tag.cloneTag(); copyTag.setSwf(swf, true); - timelined.addTag(++i, copyTag); copyTag.setTimelined(timelined); checkUniqueCharacterId(copyTag); + + timelined.addTag(++i, copyTag); + copyTag.setModified(true); } @@ -2310,7 +2390,7 @@ public class TagTreeContextMenu extends JPopupMenu { mainPanel.setTagTreeSelectedNode(mainPanel.tagListTree, item); mainPanel.updateMenu(); } - + private void showInHexDumpViewActionPerformed(ActionEvent evt) { if (mainPanel.isModified()) { ViewMessages.showMessageDialog(Main.getDefaultMessagesComponent(), AppStrings.translate("message.warning.hexViewNotUpToDate"), AppStrings.translate("message.warning"), JOptionPane.WARNING_MESSAGE, Configuration.warningHexViewNotUpToDate); @@ -2334,7 +2414,7 @@ public class TagTreeContextMenu extends JPopupMenu { } if (timelined == null) { //should not happen return; - } + } SelectTagPositionDialog dialog = new SelectTagPositionDialog(Main.getDefaultDialogsOwner(), t.getSwf(), t, timelined, true, false); if (dialog.showDialog() == AppDialog.OK_OPTION) { @@ -2547,4 +2627,186 @@ public class TagTreeContextMenu extends JPopupMenu { logger.log(Level.SEVERE, null, ex); } } + + private void copyTagToClipboardActionPerformed(ActionEvent evt, List items) { + mainPanel.copyToClipboard(items); + } + + private Set getClipboardDependenciesSet(List items) { + SWF sourceSwf = items.get(0).getSwf(); + Set clipboardItems = new LinkedHashSet<>(); + for (TreeItem item : items) { + Set needed = new LinkedHashSet<>(); + Tag tag = (Tag) item; + tag.getNeededCharactersDeep(needed); + List neededList = new ArrayList<>(); + for (Integer characterId : needed) { + neededList.add(characterId); + } + + // first add dependencies in reverse order + for (int n = neededList.size() - 1; n >= 0; n--) { + int characterId = neededList.get(n); + CharacterTag neededTag = sourceSwf.getCharacter(characterId); + if (!clipboardItems.contains(neededTag)) { + clipboardItems.add(neededTag); + } + } + clipboardItems.add(item); + } + return clipboardItems; + } + + private void copyTagToClipboardWithDependenciesActionPerformed(ActionEvent evt, List items) { + mainPanel.copyToClipboard(getClipboardDependenciesSet(items)); + } + + private void cutTagToClipboardActionPerformed(ActionEvent evt) { + List items = getTree().getSelected(); + mainPanel.cutToClipboard(items); + mainPanel.repaintTree(); + } + + private void cutTagToClipboardWithDependenciesActionPerformed(ActionEvent evt) { + List items = getTree().getSelected(); + mainPanel.cutToClipboard(getClipboardDependenciesSet(items)); + mainPanel.repaintTree(); + } + + private void pasteBeforeActionPerformed(ActionEvent evt) { + TreeItem item = getTree().getCurrentTreeItem(); + Timelined timelined; + Tag position; + int positionInt; + if (item instanceof Frame) { + Frame frame = (Frame) item; + timelined = frame.timeline.timelined; + positionInt = calcFramePositionToAdd(frame, timelined, true, new Reference<>(false), false); + } else { + timelined = ((Tag) item).getTimelined(); + positionInt = timelined.indexOfTag((Tag) item); + } + ReadOnlyTagList tags = timelined.getTags(); + position = positionInt < tags.size() ? tags.get(positionInt) : null; + pasteBeforeAfter(true, timelined, position); + } + + private void pasteAfterActionPerformed(ActionEvent evt) { + TreeItem item = getTree().getCurrentTreeItem(); + Timelined timelined; + Tag position; + int positionInt; + if (item instanceof Frame) { + Frame frame = (Frame) item; + timelined = frame.timeline.timelined; + positionInt = calcFramePositionToAdd(frame, timelined, false, new Reference<>(false), false); + } else { + timelined = ((Tag) item).getTimelined(); + positionInt = timelined.indexOfTag((Tag) item) + 1; + } + ReadOnlyTagList tags = timelined.getTags(); + position = positionInt < tags.size() ? tags.get(positionInt) : null; + pasteBeforeAfter(false, timelined, position); + } + + private void pasteInsideActionPerformed(ActionEvent evt) { + TreeItem item = getTree().getCurrentTreeItem(); + Timelined timelined; + Tag position; + if (item instanceof Frame) { + Frame frame = (Frame) item; + position = frame.allInnerTags.get(frame.allInnerTags.size() - 1); + timelined = frame.timeline.timelined; + } else { + timelined = (Timelined) item; + position = null; + } + pasteBeforeAfter(false, timelined, position); + } + + private void pasteBeforeAfter(boolean before, Timelined targetTimelined, Tag position) { + Set items = mainPanel.getClipboardContents(); + SWF sourceSwf = items.iterator().next().getSwf(); + SWF targetSwf = (targetTimelined instanceof SWF) ? (SWF) targetTimelined : ((DefineSpriteTag) targetTimelined).getSwf(); + try { + List newTags = new ArrayList<>(); + Map changedCharacterIds = new HashMap<>(); + for (TreeItem item : items) { + Tag tag = (Tag) item; + if (tag.getSwf() == null) { + continue; + } + if (mainPanel.isClipboardCut()) { + tag.getTimelined().removeTag(tag); + } + + Timelined realTargetTimelined = targetTimelined; + Tag realPosition = position; + + //do not place Define tags in DefineSprites + if ((tag instanceof CharacterTag) && (targetTimelined instanceof DefineSpriteTag)) { + Tag spriteTag = ((DefineSpriteTag) targetTimelined); + //get to the topmost level DefineSprite + while (spriteTag.getTimelined() instanceof DefineSpriteTag) { + spriteTag = (DefineSpriteTag) spriteTag.getTimelined(); + } + realTargetTimelined = spriteTag.getTimelined(); //should be SWF + realPosition = spriteTag; + } + if (sourceSwf != targetSwf || !mainPanel.isClipboardCut()) { + ReadOnlyTagList tags = realTargetTimelined.getTags(); + int positionInt = realPosition == null ? tags.size() : tags.indexOf(realPosition); + + Tag copyTag = tag.cloneTag(); + copyTag.setSwf(targetSwf, true); + copyTag.setTimelined(realTargetTimelined); + if (tag instanceof CharacterTag) { + CharacterTag characterTag = (CharacterTag) copyTag; + int oldCharacterId = characterTag.getCharacterId(); + int newCharacterId = checkUniqueCharacterId(copyTag); + + changedCharacterIds.put(oldCharacterId, newCharacterId); + } + realTargetTimelined.addTag(positionInt, copyTag); + + targetSwf.updateCharacters(); + targetSwf.getCharacters(); // force rebuild character id cache + copyTag.setModified(true); + newTags.add(copyTag); + } else if (sourceSwf == targetSwf && mainPanel.isClipboardCut()) { + ReadOnlyTagList tags = realTargetTimelined.getTags(); + int positionInt = realPosition == null ? tags.size() : tags.indexOf(realPosition); + realTargetTimelined.addTag(positionInt, tag); + tag.setTimelined(realTargetTimelined); + } + } + for (int oldCharacterId : changedCharacterIds.keySet()) { + int newCharacterId = changedCharacterIds.get(oldCharacterId); + for (Tag newTag : newTags) { + // todo: avoid double replaces + newTag.replaceCharacter(oldCharacterId, newCharacterId); + } + } + + if (sourceSwf != targetSwf) { + sourceSwf.assignExportNamesToSymbols(); + sourceSwf.assignClassesToSymbols(); + sourceSwf.clearImageCache(); + sourceSwf.updateCharacters(); + sourceSwf.resetTimelines(sourceSwf); + } + targetSwf.assignExportNamesToSymbols(); + targetSwf.assignClassesToSymbols(); + 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); + } + } }