diff --git a/src/com/jpexs/decompiler/flash/gui/CollectDepthAsSpritesDialogue.java b/src/com/jpexs/decompiler/flash/gui/CollectDepthAsSpritesDialogue.java
new file mode 100644
index 000000000..91972e90f
--- /dev/null
+++ b/src/com/jpexs/decompiler/flash/gui/CollectDepthAsSpritesDialogue.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2010-2023 JPEXS
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package com.jpexs.decompiler.flash.gui;
+
+import java.awt.BorderLayout;
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.Window;
+import java.awt.event.ActionEvent;
+import java.util.Collection;
+import java.util.List;
+import java.util.Vector;
+import javax.swing.BoxLayout;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.ListSelectionModel;
+
+/**
+ *
+ * @author JPEXS
+ */
+public class CollectDepthAsSpritesDialogue extends AppDialog {
+
+ private final JButton okButton = new JButton(translate("button.ok"));
+
+ private final JButton cancelButton = new JButton(translate("button.cancel"));
+
+ private final JList depthsList;
+
+ private final JCheckBox replaceCheckBox;
+
+ private final JCheckBox offsetCheckBox;
+
+ private int result = ERROR_OPTION;
+
+ public CollectDepthAsSpritesDialogue(Window owner) {
+ super(owner);
+ setSize(400, 150);
+ setDefaultCloseOperation(JDialog.HIDE_ON_CLOSE);
+ Container cnt = getContentPane();
+ cnt.setLayout(new BoxLayout(cnt, BoxLayout.Y_AXIS));
+ cnt.add(new JLabel(translate("collect.depths")));
+
+ depthsList = new JList<>();
+ depthsList.setVisibleRowCount(7);
+ depthsList.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
+
+ JScrollPane listScroller = new FasterScrollPane(depthsList);
+ listScroller.setPreferredSize(new Dimension(400, 200));
+ cnt.add(listScroller);
+
+ replaceCheckBox = new JCheckBox(translate("collect.replace"));
+ cnt.add(replaceCheckBox);
+
+ offsetCheckBox = new JCheckBox(translate("collect.offset"));
+ cnt.add(offsetCheckBox);
+
+ JPanel panButtons = new JPanel(new FlowLayout());
+ okButton.addActionListener(this::okButtonActionPerformed);
+ cancelButton.addActionListener(this::cancelButtonActionPerformed);
+ panButtons.add(okButton);
+ panButtons.add(cancelButton);
+
+ add(panButtons, BorderLayout.SOUTH);
+
+ setModalityType(ModalityType.APPLICATION_MODAL);
+ View.setWindowIcon(this);
+ setTitle(translate("dialog.title"));
+ getRootPane().setDefaultButton(okButton);
+ pack();
+ View.centerScreen(this);
+ }
+
+ private void okButtonActionPerformed(ActionEvent evt) {
+ result = OK_OPTION;
+ setVisible(false);
+ }
+
+ private void cancelButtonActionPerformed(ActionEvent evt) {
+ result = CANCEL_OPTION;
+ setVisible(false);
+ }
+
+ public List getDepths() {
+ if (result == ERROR_OPTION) {
+ return null;
+ }
+
+ return depthsList.getSelectedValuesList();
+ }
+
+ public boolean getReplace() {
+ return replaceCheckBox.isSelected();
+ }
+
+ public boolean getOffset() {
+ return offsetCheckBox.isSelected();
+ }
+
+ public int showDialog(Collection depths) {
+ depthsList.setListData(depths.toArray(new Integer[depths.size()]));
+ depthsList.setVisibleRowCount(7);
+ setVisible(true);
+ return result;
+ }
+}
diff --git a/src/com/jpexs/decompiler/flash/gui/locales/CollectDepthAsSpritesDialogue.properties b/src/com/jpexs/decompiler/flash/gui/locales/CollectDepthAsSpritesDialogue.properties
new file mode 100644
index 000000000..e8397776b
--- /dev/null
+++ b/src/com/jpexs/decompiler/flash/gui/locales/CollectDepthAsSpritesDialogue.properties
@@ -0,0 +1,22 @@
+# Copyright (C) 2010-2016 JPEXS
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+
+dialog.title = Collect options
+collect.depths = Depths to collect:
+collect.replace = Replace original tags with sprite
+collect.offset = Offset top left corner to (0,0)
+
+button.ok = OK
+button.cancel = Cancel
diff --git a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties
index cb5c92cb4..b1da3a083 100644
--- a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties
+++ b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties
@@ -1256,4 +1256,5 @@ node.scenes = scenes
contextmenu.showInFramesFolder = Show in frames folder
#after 20.1.0
-contextmenu.collapseAll = Collapse all
\ No newline at end of file
+contextmenu.collapseAll = Collapse all
+contextmenu.collectDepthAsSprites = Collect items at same depth as sprites
\ No newline at end of file
diff --git a/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeContextMenu.java b/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeContextMenu.java
index 0c073e3c1..b01439a98 100644
--- a/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeContextMenu.java
+++ b/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeContextMenu.java
@@ -36,6 +36,7 @@ import com.jpexs.decompiler.flash.configuration.SwfSpecificCustomConfiguration;
import com.jpexs.decompiler.flash.gui.AppDialog;
import com.jpexs.decompiler.flash.gui.AppStrings;
import com.jpexs.decompiler.flash.gui.ClipboardType;
+import com.jpexs.decompiler.flash.gui.CollectDepthAsSpritesDialogue;
import com.jpexs.decompiler.flash.gui.Main;
import com.jpexs.decompiler.flash.gui.MainPanel;
import com.jpexs.decompiler.flash.gui.ReplaceCharacterDialog;
@@ -118,6 +119,7 @@ import com.jpexs.decompiler.flash.types.CLIPACTIONRECORD;
import com.jpexs.decompiler.flash.types.CLIPACTIONS;
import com.jpexs.decompiler.flash.types.CXFORMWITHALPHA;
import com.jpexs.decompiler.flash.types.HasCharacterId;
+import com.jpexs.decompiler.flash.types.MATRIX;
import com.jpexs.decompiler.graph.CompilationException;
import com.jpexs.decompiler.graph.DottedChain;
import com.jpexs.helpers.ByteArrayRange;
@@ -143,6 +145,7 @@ import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
+import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import java.util.TreeSet;
@@ -308,6 +311,8 @@ public class TagTreeContextMenu extends JPopupMenu {
private JMenuItem replaceWithGifMenuItem;
+ private JMenuItem collectDepthAsSpritesItem;
+
private List items = new ArrayList<>();
private static final int KIND_TAG_MOVETO = 0;
@@ -637,6 +642,10 @@ public class TagTreeContextMenu extends JPopupMenu {
pasteInsideMenuItem.addActionListener(this::pasteInsideActionPerformed);
add(pasteInsideMenuItem);
+ collectDepthAsSpritesItem = new JMenuItem(mainPanel.translate("contextmenu.collectDepthAsSprites"));
+ collectDepthAsSpritesItem.addActionListener(this::collectDepthAsSprites);
+ add(collectDepthAsSpritesItem);
+
applyUnpackerMenu = new JMenu(mainPanel.translate("contextmenu.applyUnpacker"));
applyUnpackerMenu.setIcon(View.getIcon("openinside16"));
@@ -1083,6 +1092,14 @@ public class TagTreeContextMenu extends JPopupMenu {
}
}
+ boolean allSelectedIsFrame = true;
+ for (TreeItem item : items) {
+ if (!(item instanceof Frame)) {
+ allSelectedIsFrame = false;
+ break;
+ }
+ }
+
boolean hasExportableNodes = tree.hasExportableNodes();
expandRecursiveMenuItem.setVisible(false);
@@ -1140,6 +1157,7 @@ public class TagTreeContextMenu extends JPopupMenu {
pasteAfterMenuItem.setVisible(false);
pasteBeforeMenuItem.setVisible(false);
pasteInsideMenuItem.setVisible(false);
+ collectDepthAsSpritesItem.setVisible(allSelectedIsFrame && allSelectedSameParent);
applyUnpackerMenu.setVisible(false);
openSWFInsideTagMenuItem.setVisible(false);
addAs12ScriptMenuItem.setVisible(false);
@@ -4088,6 +4106,146 @@ public class TagTreeContextMenu extends JPopupMenu {
}
}
+ private void collectDepthAsSprites(ActionEvent e) {
+ List frames = getSelectedItems();
+ Timelined timelined = ((Frame) frames.get(0)).timeline.timelined;
+ SWF swf = timelined instanceof SWF ? (SWF) timelined : ((DefineSpriteTag) timelined).getSwf();
+
+ Set originalDepths = new HashSet<>();
+
+ for (TreeItem item : frames) {
+ Frame f = (Frame) item;
+ for (Tag t : f.innerTags) {
+ if (t instanceof RemoveTag) {
+ originalDepths.add(((RemoveTag) t).getDepth());
+ } else if (t instanceof PlaceObjectTypeTag) {
+ originalDepths.add(((PlaceObjectTypeTag) t).getDepth());
+ }
+ }
+ }
+
+ CollectDepthAsSpritesDialogue dialog = new CollectDepthAsSpritesDialogue(Main.getDefaultDialogsOwner());
+
+ if (dialog.showDialog(originalDepths) == CollectDepthAsSpritesDialogue.OK_OPTION) {
+ List depths = dialog.getDepths();
+ boolean replace = dialog.getReplace();
+ boolean offset = dialog.getOffset();
+
+ Map sprites = new HashMap<>();
+ for (int d : depths) {
+ DefineSpriteTag sprite = new DefineSpriteTag(swf);
+ sprite.frameCount = frames.size();
+
+ for (int i = 0; i < frames.size(); i++) {
+ Tag showFrame = new ShowFrameTag(swf);
+ showFrame.setTimelined(sprite);
+ sprite.addTag(showFrame);
+ }
+
+ swf.addTag(sprite);
+ sprites.put(d, sprite);
+ }
+
+ try {
+ for (int i = frames.size() - 1; i > -1; i--) {
+ Frame f = (Frame) frames.get(i);
+ for (int j = 0; j < f.innerTags.size(); j++) {
+ Tag t = f.innerTags.get(j);
+
+ int depth = -1;
+ if (t instanceof RemoveTag) {
+ depth = ((RemoveTag) t).getDepth();
+ } else if (t instanceof PlaceObjectTypeTag) {
+ depth = ((PlaceObjectTypeTag) t).getDepth();
+ }
+
+ if (depth != -1) {
+ DefineSpriteTag sprite = sprites.get(depth);
+ Tag clone = t.cloneTag();
+ clone.setTimelined(sprite);
+ sprite.addTag(i, clone);
+
+ if (replace) {
+ f.innerTags.remove(t);
+ t.getTimelined().removeTag(t);
+ swf.removeTag(t);
+ j--;
+ }
+ }
+ }
+ }
+
+ Frame first = (Frame) frames.get(0);
+
+ for (Entry entry : sprites.entrySet()) {
+ DefineSpriteTag sprite = entry.getValue();
+
+ if (offset || replace) {
+ int minX = Integer.MAX_VALUE;
+ int minY = Integer.MAX_VALUE;
+
+ for (Tag t : sprite.getTags()) {
+ if (t instanceof PlaceObjectTypeTag) {
+ PlaceObjectTypeTag placeTag = (PlaceObjectTypeTag) t;
+ MATRIX m = placeTag.getMatrix();
+
+ if (m != null) {
+ if (m.translateX < minX) {
+ minX = m.translateX;
+ }
+
+ if (m.translateY < minY) {
+ minY = m.translateY;
+ }
+ }
+ }
+ }
+
+ if (offset) {
+ for (Tag t : sprite.getTags()) {
+ if (t instanceof PlaceObjectTypeTag) {
+ PlaceObjectTypeTag placeTag = (PlaceObjectTypeTag) t;
+ MATRIX m = placeTag.getMatrix();
+
+ if (m != null) {
+ m.translateX -= minX;
+ m.translateY -= minY;
+ }
+ }
+ }
+ }
+
+ if (replace) {
+ PlaceObject3Tag place = new PlaceObject3Tag(swf);
+ place.placeFlagHasCharacter = true;
+ place.characterId = sprite.getCharacterId();
+ place.depth = entry.getKey();
+ if (offset) {
+ place.placeFlagHasMatrix = true;
+ place.placeFlagMove = true;
+ place.matrix = new MATRIX();
+ place.matrix.translateX = minX;
+ place.matrix.translateY = minY;
+ }
+
+ place.setTimelined(first.timeline.timelined);
+ first.innerTags.add(place);
+ }
+ }
+ }
+
+ swf.updateCharacters();
+ if(replace) {
+ swf.computeDependentCharacters();
+ swf.computeDependentFrames();
+ }
+ mainPanel.refreshTree(swf);
+ } catch (InterruptedException | IOException ex) {
+ Logger.getLogger(TagTreeContextMenu.class.getName()).log(Level.SEVERE, null, ex);
+ }
+ }
+ }
+
/**
*
* @param items
@@ -4852,7 +5010,7 @@ public class TagTreeContextMenu extends JPopupMenu {
}
}
- try (FileOutputStream fos = new FileOutputStream(fileName)) {
+ try ( FileOutputStream fos = new FileOutputStream(fileName)) {
container.getABC().saveTo(fos);
}