From 875b9c17d3fc213acc46422bfd439ec3d60b4bf5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jindra=20Pet=C5=99=C3=ADk?= Date: Sun, 6 Oct 2024 09:40:05 +0200 Subject: [PATCH] Add to stage tag order fix. --- .../flash/DefineBeforeUsageFixer.java | 81 +++++++++++++++++++ .../src/com/jpexs/decompiler/flash/SWF.java | 22 ++--- .../flash/tags/DefineSpriteTag.java | 12 +-- .../flash/tags/DefineVideoStreamTag.java | 2 +- .../decompiler/flash/tags/base/ButtonTag.java | 2 +- .../decompiler/flash/timeline/Timeline.java | 3 +- .../decompiler/flash/easygui/MainFrame.java | 36 ++++++++- .../decompiler/flash/gui/ImagePanel.java | 7 +- .../flash/gui/tagtree/TagTreeContextMenu.java | 4 +- 9 files changed, 146 insertions(+), 23 deletions(-) create mode 100644 libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/DefineBeforeUsageFixer.java diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/DefineBeforeUsageFixer.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/DefineBeforeUsageFixer.java new file mode 100644 index 000000000..afd74b598 --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/DefineBeforeUsageFixer.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2010-2024 JPEXS, All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3.0 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. + */ +package com.jpexs.decompiler.flash; + +import com.jpexs.decompiler.flash.tags.Tag; +import com.jpexs.decompiler.flash.tags.VideoFrameTag; +import com.jpexs.decompiler.flash.tags.base.CharacterIdTag; +import com.jpexs.decompiler.flash.tags.base.CharacterTag; +import com.jpexs.decompiler.flash.tags.base.PlaceObjectTypeTag; +import com.jpexs.decompiler.flash.tags.base.RemoveTag; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.Set; + +/** + * This class fixes SWF that all CharacterTags (and attached CharacterIdTags) + * are placed before their usage. + * @author JPEXS + */ +public class DefineBeforeUsageFixer { + + public boolean fixDefineBeforeUsage(SWF swf) { + ReadOnlyTagList tags = swf.getTags(); + Set walkedCharacters = new HashSet<>(); + boolean changed = false; + for (int i = 0; i < tags.size(); i++) { + Tag t = tags.get(i); + Set needed = new LinkedHashSet<>(); + t.getNeededCharactersDeep(needed); + for (int chId : needed) { + if (walkedCharacters.contains(chId)) { + continue; + } + walkedCharacters.add(chId); + CharacterTag ch = swf.getCharacter(chId); + if (ch != null) { + int defineIndex = tags.indexOf(ch); + int usageIndex = i; + if (usageIndex < defineIndex) { + i += moveCharacter(swf, i, chId); + changed = true; + } + } + } + } + return changed; + } + + private int moveCharacter(SWF swf, int usageIndex, int characterId) { + int i = 0; + for (int j = 0; j < swf.getTags().size(); j++) { + Tag t2 = swf.getTags().get(j); + if ((t2 instanceof CharacterIdTag) + && !(t2 instanceof PlaceObjectTypeTag) + && !(t2 instanceof RemoveTag) + && !(t2 instanceof VideoFrameTag)) { + CharacterIdTag chit = (CharacterIdTag) t2; + if (chit.getCharacterId() == characterId) { + swf.removeTag(j); + swf.addTag(usageIndex + i, (Tag) chit); + i++; + } + } + } + return i; + } +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWF.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWF.java index 94d8a37f0..baefdb122 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWF.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWF.java @@ -1613,7 +1613,7 @@ public final class SWF implements SWFContainerItem, Timelined, Openable { * @param characters Map of characterId to CharacterTag * @param characterIdTags Map of characterId to list of CharacterIdTags */ - private void parseCharacters(Iterable list, Map externalImages2, Map characters, Map> characterIdTags) { + private synchronized void parseCharacters(Iterable list, Map externalImages2, Map characters, Map> characterIdTags) { Iterator iterator = list.iterator(); while (iterator.hasNext()) { Tag t = iterator.next(); @@ -5068,7 +5068,7 @@ public final class SWF implements SWFContainerItem, Timelined, Openable { * @param removeDependencies Remove dependencies? * @param listener Listener to call after removing each of character. */ - public void removeTags(Collection tags, boolean removeDependencies, TagRemoveListener listener) { + public synchronized void removeTags(Collection tags, boolean removeDependencies, TagRemoveListener listener) { Set timelineds = new HashSet<>(); for (Tag tag : tags) { Timelined timelined = tag.getTimelined(); @@ -5091,7 +5091,7 @@ public final class SWF implements SWFContainerItem, Timelined, Openable { * @param index Index of tag */ @Override - public void removeTag(int index) { + public synchronized void removeTag(int index) { setModified(true); tags.remove(index); updateCharacters(); @@ -5103,10 +5103,11 @@ public final class SWF implements SWFContainerItem, Timelined, Openable { * @param tag Tag */ @Override - public void removeTag(Tag tag) { + public synchronized void removeTag(Tag tag) { setModified(true); tags.remove(tag); updateCharacters(); + readOnlyTags = null; } /** @@ -5116,13 +5117,14 @@ public final class SWF implements SWFContainerItem, Timelined, Openable { * @param removeDependencies Remove dependencies? * @param listener Listener to call after removing each of character. */ - public void removeTag(Tag tag, boolean removeDependencies, TagRemoveListener listener) { + public synchronized void removeTag(Tag tag, boolean removeDependencies, TagRemoveListener listener) { Timelined timelined = tag.getTimelined(); removeTagInternal(timelined, tag, removeDependencies, listener); resetTimelines(timelined); updateCharacters(); clearImageCache(); clearShapeCache(); + readOnlyTags = null; } /** @@ -5161,7 +5163,7 @@ public final class SWF implements SWFContainerItem, Timelined, Openable { * @return Tags */ @Override - public ReadOnlyTagList getTags() { + public synchronized ReadOnlyTagList getTags() { if (readOnlyTags == null) { readOnlyTags = new ReadOnlyTagList(tags); } @@ -5175,10 +5177,11 @@ public final class SWF implements SWFContainerItem, Timelined, Openable { * @param tag Tag */ @Override - public void addTag(Tag tag) { + public synchronized void addTag(Tag tag) { setModified(true); tags.add(tag); updateCharacters(); + readOnlyTags = null; } /** @@ -5188,10 +5191,11 @@ public final class SWF implements SWFContainerItem, Timelined, Openable { * @param tag Tag */ @Override - public void addTag(int index, Tag tag) { + public synchronized void addTag(int index, Tag tag) { setModified(true); tags.add(index, tag); updateCharacters(); + readOnlyTags = null; } /** @@ -5201,7 +5205,7 @@ public final class SWF implements SWFContainerItem, Timelined, Openable { * @return Index or -1 when not found */ @Override - public int indexOfTag(Tag tag) { + public synchronized int indexOfTag(Tag tag) { return tags.indexOf(tag); } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineSpriteTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineSpriteTag.java index bb92ac69f..9a1dc2ef8 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineSpriteTag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineSpriteTag.java @@ -326,7 +326,7 @@ public class DefineSpriteTag extends DrawableTag implements Timelined { } @Override - public ReadOnlyTagList getTags() { + public synchronized ReadOnlyTagList getTags() { if (readOnlyTags == null) { readOnlyTags = new ReadOnlyTagList(subTags); } @@ -335,31 +335,31 @@ public class DefineSpriteTag extends DrawableTag implements Timelined { } @Override - public void removeTag(int index) { + public synchronized void removeTag(int index) { setModified(true); subTags.remove(index); } @Override - public void removeTag(Tag tag) { + public synchronized void removeTag(Tag tag) { setModified(true); subTags.remove(tag); } @Override - public void addTag(Tag tag) { + public synchronized void addTag(Tag tag) { setModified(true); subTags.add(tag); } @Override - public void addTag(int index, Tag tag) { + public synchronized void addTag(int index, Tag tag) { setModified(true); subTags.add(index, tag); } @Override - public int indexOfTag(Tag tag) { + public synchronized int indexOfTag(Tag tag) { return subTags.indexOf(tag); } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineVideoStreamTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineVideoStreamTag.java index 10367edd0..fef933111 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineVideoStreamTag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineVideoStreamTag.java @@ -486,7 +486,7 @@ public class DefineVideoStreamTag extends DrawableTag implements BoundedTag, Tim } @Override - public ReadOnlyTagList getTags() { + public synchronized ReadOnlyTagList getTags() { initTimeline(); return tags; } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/ButtonTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/ButtonTag.java index 39deab8b9..cfbef24f2 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/ButtonTag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/ButtonTag.java @@ -188,7 +188,7 @@ public abstract class ButtonTag extends DrawableTag implements Timelined { protected abstract void initTimeline(Timeline timeline); @Override - public ReadOnlyTagList getTags() { + public synchronized ReadOnlyTagList getTags() { return ReadOnlyTagList.EMPTY; } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/timeline/Timeline.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/timeline/Timeline.java index 36124a3ff..375b569b1 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/timeline/Timeline.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/timeline/Timeline.java @@ -449,7 +449,8 @@ public class Timeline { boolean newFrameNeeded = false; scenes = new ArrayList<>(); Scene prevScene = null; - for (Tag t : timelined.getTags()) { + List tagList = timelined.getTags().toArrayList(); + for (Tag t : tagList) { newFrameNeeded = true; boolean isNested = ShowFrameTag.isNestedTagType(t.getId()); if (isNested) { diff --git a/src/com/jpexs/decompiler/flash/easygui/MainFrame.java b/src/com/jpexs/decompiler/flash/easygui/MainFrame.java index ad0178845..a77f78d61 100644 --- a/src/com/jpexs/decompiler/flash/easygui/MainFrame.java +++ b/src/com/jpexs/decompiler/flash/easygui/MainFrame.java @@ -16,6 +16,8 @@ */ package com.jpexs.decompiler.flash.easygui; +import com.jpexs.decompiler.flash.DefineBeforeUsageFixer; +import com.jpexs.decompiler.flash.ReadOnlyTagList; import com.jpexs.decompiler.flash.SWF; import com.jpexs.decompiler.flash.gui.ImagePanel; import com.jpexs.decompiler.flash.gui.RegistrationPointPosition; @@ -47,6 +49,7 @@ import java.awt.geom.Point2D; import java.io.File; import java.io.FileInputStream; import java.io.IOException; +import java.util.List; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JPanel; @@ -183,14 +186,18 @@ public class MainFrame extends JFrame { private PlaceObject2Tag place; private RemoveObject2Tag remove; + private List tags; + private int frame = stagePanel.getFrame(); + private int depth = stagePanel.getSelectedDepth(); @Override public void doOperation() { + timelinePanel.setFrame(frame, depth); CharacterTag ch = (CharacterTag) tag; int maxDepth = stagePanel.getTimelined().getTimeline().getMaxDepth(); int newDepth = maxDepth + 1; Timelined timelined = stagePanel.getTimelined(); - ShowFrameTag showFrameTag = timelined.getTimeline().getFrame(stagePanel.getFrame()).showFrameTag; + ShowFrameTag showFrameTag = timelined.getTimeline().getFrame(frame).showFrameTag; place = new PlaceObject2Tag(timelined.getSwf()); place.depth = newDepth; place.placeFlagHasCharacter = true; @@ -207,6 +214,14 @@ public class MainFrame extends JFrame { remove.depth = newDepth; timelined.addTag(timelined.indexOfTag(showFrameTag) + 1, remove); } + + tags = timelined.getSwf().getTags().toArrayList(); + DefineBeforeUsageFixer fixer = new DefineBeforeUsageFixer(); + boolean tagOrderChanged = fixer.fixDefineBeforeUsage(timelined.getSwf()); + if (!tagOrderChanged) { + tags = null; + } + timelined.resetTimeline(); stagePanel.repaint(); timelinePanel.refresh(); @@ -221,8 +236,27 @@ public class MainFrame extends JFrame { timelined.removeTag(remove); } timelined.resetTimeline(); + + //Tag order changed, put the original tags back + if (tags != null) { + SWF swf = timelined.getSwf(); + ReadOnlyTagList newTags = swf.getTags(); + int size = newTags.size(); + for (int i = 0; i < size; i++) { + swf.removeTag(0); + } + for (int i = 0; i < tags.size(); i++) { + if (tags.get(i) == place) { + continue; + } + swf.addTag(tags.get(i)); + } + swf.resetTimeline(); + } + stagePanel.repaint(); timelinePanel.refresh(); + timelinePanel.setFrame(frame, depth); } @Override diff --git a/src/com/jpexs/decompiler/flash/gui/ImagePanel.java b/src/com/jpexs/decompiler/flash/gui/ImagePanel.java index df2086b3a..368b84d04 100644 --- a/src/com/jpexs/decompiler/flash/gui/ImagePanel.java +++ b/src/com/jpexs/decompiler/flash/gui/ImagePanel.java @@ -1144,13 +1144,14 @@ public final class ImagePanel extends JPanel implements MediaDisplay { if (altDown || selectionMode) { if (depthStateUnderCursor != null) { if (transformSelectionMode) { - if (freeTransformDepth != depthStateUnderCursor.depth) { + if (freeTransformDepth != depthStateUnderCursor.depth && mode == Cursor.DEFAULT_CURSOR) { freeTransformDepth(depthStateUnderCursor.depth); + firePlaceObjectSelected(); } } else if (selectionMode) { selectDepth(depthStateUnderCursor.depth); - } - firePlaceObjectSelected(); + firePlaceObjectSelected(); + } } if (!selectionMode) { return; diff --git a/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeContextMenu.java b/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeContextMenu.java index 1f1d39267..6637a5c03 100644 --- a/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeContextMenu.java +++ b/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeContextMenu.java @@ -91,6 +91,7 @@ import com.jpexs.decompiler.flash.tags.SymbolClassTag; import com.jpexs.decompiler.flash.tags.Tag; import com.jpexs.decompiler.flash.tags.TagTypeInfo; import com.jpexs.decompiler.flash.tags.UnknownTag; +import com.jpexs.decompiler.flash.tags.VideoFrameTag; import com.jpexs.decompiler.flash.tags.base.ASMSource; import com.jpexs.decompiler.flash.tags.base.BinaryDataInterface; import com.jpexs.decompiler.flash.tags.base.ButtonTag; @@ -5281,7 +5282,8 @@ public class TagTreeContextMenu extends JPopupMenu { Tag t2 = swf.getTags().get(j); if ((t2 instanceof CharacterIdTag) && !(t2 instanceof PlaceObjectTypeTag) - && !(t2 instanceof RemoveTag)) { + && !(t2 instanceof RemoveTag) + && !(t2 instanceof VideoFrameTag)) { CharacterIdTag chit = (CharacterIdTag) t2; if (chit.getCharacterId() == characterId) { swf.removeTag(j);