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); }