diff --git a/CHANGELOG.md b/CHANGELOG.md
index 585753211..3c1ff0cc2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,7 @@ All notable changes to this project will be documented in this file.
- data-characterId and data-characterName tags to SVG export
- [#1731] Image viewer zoom support
- Cloning of tags and frames
+- Changing tag position
### Fixed
- [#1834] PlaceObject4 tags appear as Unresolved inside of DefineSprite
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 6573f0071..59264b4c1 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
@@ -131,12 +131,12 @@ public class Timeline {
}
}
- public List getFrames() {
+ public synchronized List getFrames() {
ensureInitialized();
return frames;
}
- public Frame getFrame(int index) {
+ public synchronized Frame getFrame(int index) {
ensureInitialized();
if (index >= frames.size()) {
return null;
@@ -144,7 +144,7 @@ public class Timeline {
return frames.get(index);
}
- public void addFrame(Frame frame) {
+ public synchronized void addFrame(Frame frame) {
ensureInitialized();
frames.add(frame);
maxDepth = getMaxDepthInternal();
@@ -174,7 +174,7 @@ public class Timeline {
reset(swf, swf, 0, swf.displayRect);
}
- public void reset(SWF swf, Timelined timelined, int id, RECT displayRect) {
+ public synchronized void reset(SWF swf, Timelined timelined, int id, RECT displayRect) {
initialized = false;
frames.clear();
depthMaxFrame.clear();
@@ -196,7 +196,7 @@ public class Timeline {
return maxDepth;
}
- private int getMaxDepthInternal() {
+ private synchronized int getMaxDepthInternal() {
int max_depth = 0;
for (Frame f : frames) {
for (int depth : f.layers.keySet()) {
@@ -212,12 +212,12 @@ public class Timeline {
return max_depth;
}
- public int getFrameCount() {
+ public synchronized int getFrameCount() {
ensureInitialized();
return frames.size();
}
- public int getRealFrameCount() {
+ public synchronized int getRealFrameCount() {
ensureInitialized();
int cnt = 1;
@@ -263,7 +263,7 @@ public class Timeline {
return -1;
}
- private void initialize() {
+ private synchronized void initialize() {
int frameIdx = 0;
Frame frame = new Frame(this, frameIdx++);
frame.layersChanged = true;
@@ -418,7 +418,7 @@ public class Timeline {
initialized = true;
}
- private void detectTweens() {
+ private synchronized void detectTweens() {
for (int d = 1; d <= maxDepth; d++) {
int characterId = -1;
int len = 0;
@@ -455,7 +455,7 @@ public class Timeline {
}
}
- private void calculateMaxDepthFrames() {
+ private synchronized void calculateMaxDepthFrames() {
depthMaxFrame.clear();
for (int d = 1; d <= maxDepth; d++) {
for (int f = frames.size() - 1; f >= 0; f--) {
diff --git a/src/com/jpexs/decompiler/flash/gui/MainPanel.java b/src/com/jpexs/decompiler/flash/gui/MainPanel.java
index 999714ba0..13bd16c2f 100644
--- a/src/com/jpexs/decompiler/flash/gui/MainPanel.java
+++ b/src/com/jpexs/decompiler/flash/gui/MainPanel.java
@@ -3391,9 +3391,30 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se
t = di.getTag();
}
- showPreview(t, dumpPreviewPanel);
+ showPreview(t, dumpPreviewPanel, getFrameForTreeItem(t), getTimelinedForTreeItem(treeItem));
}
}
+
+ private int getFrameForTreeItem(TreeItem treeItem) {
+ TreePath path = tagTree.getModel().getTreePath(treeItem);
+ for (int i = path.getPathCount() - 1; i >= 0; i--) {
+ if (path.getPathComponent(i) instanceof Frame) {
+ Frame frame = (Frame)path.getPathComponent(i);
+ return frame.frame;
+ }
+ }
+ return -1;
+ }
+
+ private Timelined getTimelinedForTreeItem(TreeItem treeItem) {
+ TreePath path = tagTree.getModel().getTreePath(treeItem);
+ for (int i = path.getPathCount() - 1; i >= 0; i--) {
+ if (path.getPathComponent(i) instanceof Timelined) {
+ return (Timelined)path.getPathComponent(i);
+ }
+ }
+ return null;
+ }
public void unloadFlashPlayer() {
if (flashPanel != null) {
@@ -3436,7 +3457,7 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se
}
}
- public boolean isAdobeFlashPlayerEnabled() {
+ public static boolean isAdobeFlashPlayerEnabled() {
return Configuration.useAdobeFlashPlayerForPreviews.get();
}
@@ -3614,7 +3635,7 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se
previewPanel.closeTag();
}
- public void showPreview(TreeItem treeItem, PreviewPanel previewPanel) {
+ public static void showPreview(TreeItem treeItem, PreviewPanel previewPanel, int frame, Timelined timelinedContainer) {
previewPanel.clear();
if (treeItem == null) {
previewPanel.showEmpty();
@@ -3633,10 +3654,10 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se
//}
}
- } else if ((treeItem instanceof PlaceObjectTypeTag) && (previewPanel != dumpPreviewPanel)) {
- TreePath path = tagTree.getModel().getTreePath(treeItem);
- Frame frame = (Frame) path.getParentPath().getLastPathComponent();
- previewPanel.showPlaceTagPanel((PlaceObjectTypeTag) treeItem, frame.frame);
+ } else if ((treeItem instanceof PlaceObjectTypeTag)) {// && (previewPanel != dumpPreviewPanel)) {
+ //TreePath path = tagTree.getModel().getTreePath(treeItem);
+ //Frame frame = (Frame) path.getParentPath().getLastPathComponent();
+ previewPanel.showPlaceTagPanel((PlaceObjectTypeTag) treeItem, frame);
} else if (treeItem instanceof MetadataTag) {
MetadataTag metadataTag = (MetadataTag) treeItem;
previewPanel.showMetaDataPanel(metadataTag);
@@ -3668,6 +3689,14 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se
Frame fn = (Frame) treeItem;
SWF swf = fn.getSwf();
previewPanel.showImagePanel(fn.timeline.timelined, swf, fn.frame, true);
+ } else if (treeItem instanceof ShowFrameTag) {
+ SWF swf;
+ if (timelinedContainer instanceof DefineSpriteTag) {
+ swf = ((DefineSpriteTag)timelinedContainer).getSwf();
+ } else {
+ swf = (SWF)timelinedContainer;
+ }
+ previewPanel.showImagePanel(timelinedContainer, swf, frame, true);
} else if ((treeItem instanceof SoundTag)) { //&& isInternalFlashViewerSelected() && (Arrays.asList("mp3", "wav").contains(((SoundTag) tagObj).getExportFormat())))) {
previewPanel.showImagePanel(new SerializableImage(View.loadImage("sound32")));
previewPanel.setImageReplaceButtonVisible(((Tag) treeItem).isReadOnly() && (treeItem instanceof DefineSoundTag), false);
@@ -3804,44 +3833,44 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se
} else if (treeItem instanceof FolderItem) {
showFolderPreview((FolderItem) treeItem);
} else if (treeItem instanceof SWF) {
- showPreview(treeItem, previewPanel);
+ showPreview(treeItem, previewPanel, -1, null);
showCard(CARDPREVIEWPANEL);
} else if (treeItem instanceof MetadataTag) {
- showPreview(treeItem, previewPanel);
+ showPreview(treeItem, previewPanel, -1, null);
showCard(CARDPREVIEWPANEL);
} else if (treeItem instanceof DefineBinaryDataTag) {
- showPreview(treeItem, previewPanel);
+ showPreview(treeItem, previewPanel, -1, null);
showCard(CARDPREVIEWPANEL);
} else if (treeItem instanceof UnknownTag) {
- showPreview(treeItem, previewPanel);
+ showPreview(treeItem, previewPanel, -1, null);
showCard(CARDPREVIEWPANEL);
} else if (treeItem instanceof ASMSource && (!(treeItem instanceof DrawableTag) || preferScript)) {
getActionPanel().setSource((ASMSource) treeItem, !forceReload);
showCard(CARDACTIONSCRIPTPANEL);
} else if (treeItem instanceof ImageTag) {
- showPreview(treeItem, previewPanel);
+ showPreview(treeItem, previewPanel, -1, null);
showCard(CARDPREVIEWPANEL);
} else if ((treeItem instanceof DrawableTag) && (!(treeItem instanceof TextTag)) && (!(treeItem instanceof FontTag)) && internalViewer) {
- showPreview(treeItem, previewPanel);
+ showPreview(treeItem, previewPanel, -1, null);
showCard(CARDPREVIEWPANEL);
} else if ((treeItem instanceof FontTag) && internalViewer) {
- showPreview(treeItem, previewPanel);
+ showPreview(treeItem, previewPanel, -1, null);
showCard(CARDPREVIEWPANEL);
} else if ((treeItem instanceof TextTag) && internalViewer) {
- showPreview(treeItem, previewPanel);
+ showPreview(treeItem, previewPanel, -1, null);
showCard(CARDPREVIEWPANEL);
} else if (treeItem instanceof Frame && internalViewer) {
- showPreview(treeItem, previewPanel);
+ showPreview(treeItem, previewPanel, -1, null);
showCard(CARDPREVIEWPANEL);
} else if ((treeItem instanceof SoundTag)) { //&& isInternalFlashViewerSelected() && (Arrays.asList("mp3", "wav").contains(((SoundTag) tagObj).getExportFormat())))) {
- showPreview(treeItem, previewPanel);
+ showPreview(treeItem, previewPanel, -1, null);
showCard(CARDPREVIEWPANEL);
} else if ((treeItem instanceof Frame) || (treeItem instanceof CharacterTag) || (treeItem instanceof FontTag) || (treeItem instanceof SoundStreamHeadTypeTag)) {
- showPreview(treeItem, previewPanel);
+ showPreview(treeItem, previewPanel, -1, null);
showCard(CARDPREVIEWPANEL);
} else if (treeItem instanceof PlaceObjectTypeTag) {
- showPreview(treeItem, previewPanel);
+ showPreview(treeItem, previewPanel, getFrameForTreeItem(treeItem), null);
showCard(CARDPREVIEWPANEL);
} else if (treeItem instanceof Tag) {
showGenericTag((Tag) treeItem);
diff --git a/src/com/jpexs/decompiler/flash/gui/PreviewPanel.java b/src/com/jpexs/decompiler/flash/gui/PreviewPanel.java
index d9bbbd07f..5cf359df2 100644
--- a/src/com/jpexs/decompiler/flash/gui/PreviewPanel.java
+++ b/src/com/jpexs/decompiler/flash/gui/PreviewPanel.java
@@ -712,17 +712,18 @@ public class PreviewPanel extends JPersistentSplitPane implements TagEditorPanel
showCardLeft(PLACE_TAG_CARD);
placeTag = tag;
oldMatrix = tag.getMatrix();
- placeSplitPane.setDividerLocation((int) (0.6 * this.getWidth()));
+ placeSplitPane.setDividerLocation((int) (0.6 * this.getWidth()));
+ placeGenericPanel.setVisible(!readOnly);
placeGenericPanel.setEditMode(false, tag);
placeImagePanel.selectDepth(-1);
placeImagePanel.setTimelined(((Tag) tag).getTimelined(), ((Tag) tag).getSwf(), frame, true);
placeImagePanel.selectDepth(tag.getDepth());
parametersPanel.setVisible(false);
- placeEditButton.setVisible(!tag.isReadOnly());
+ placeEditButton.setVisible(!tag.isReadOnly() && !readOnly);
placeEditButton.setEnabled(true);
placeSaveButton.setVisible(false);
placeCancelButton.setVisible(false);
- placeFreeTransformButton.setVisible(true);
+ placeFreeTransformButton.setVisible(!readOnly);
}
public void setImageReplaceButtonVisible(boolean show, boolean showAlpha) {
diff --git a/src/com/jpexs/decompiler/flash/gui/SelectTagPositionDialog.java b/src/com/jpexs/decompiler/flash/gui/SelectTagPositionDialog.java
new file mode 100644
index 000000000..e7fe5d55f
--- /dev/null
+++ b/src/com/jpexs/decompiler/flash/gui/SelectTagPositionDialog.java
@@ -0,0 +1,544 @@
+/*
+ * Copyright (C) 2022 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 com.jpexs.decompiler.flash.SWF;
+import static com.jpexs.decompiler.flash.gui.AppDialog.CANCEL_OPTION;
+import com.jpexs.decompiler.flash.gui.tagtree.TagTree;
+import com.jpexs.decompiler.flash.tags.DefineBinaryDataTag;
+import com.jpexs.decompiler.flash.tags.DefineSoundTag;
+import com.jpexs.decompiler.flash.tags.DefineSpriteTag;
+import com.jpexs.decompiler.flash.tags.DefineVideoStreamTag;
+import com.jpexs.decompiler.flash.tags.DoABC2Tag;
+import com.jpexs.decompiler.flash.tags.DoABCTag;
+import com.jpexs.decompiler.flash.tags.DoActionTag;
+import com.jpexs.decompiler.flash.tags.DoInitActionTag;
+import com.jpexs.decompiler.flash.tags.FileAttributesTag;
+import com.jpexs.decompiler.flash.tags.MetadataTag;
+import com.jpexs.decompiler.flash.tags.SetBackgroundColorTag;
+import com.jpexs.decompiler.flash.tags.ShowFrameTag;
+import com.jpexs.decompiler.flash.tags.SoundStreamBlockTag;
+import com.jpexs.decompiler.flash.tags.Tag;
+import com.jpexs.decompiler.flash.tags.base.ButtonTag;
+import com.jpexs.decompiler.flash.tags.base.FontTag;
+import com.jpexs.decompiler.flash.tags.base.ImageTag;
+import com.jpexs.decompiler.flash.tags.base.MorphShapeTag;
+import com.jpexs.decompiler.flash.tags.base.PlaceObjectTypeTag;
+import com.jpexs.decompiler.flash.tags.base.RemoveTag;
+import com.jpexs.decompiler.flash.tags.base.ShapeTag;
+import com.jpexs.decompiler.flash.tags.base.SoundStreamHeadTypeTag;
+import com.jpexs.decompiler.flash.tags.base.TextTag;
+import com.jpexs.decompiler.flash.timeline.Timelined;
+import com.jpexs.decompiler.flash.treeitems.TreeItem;
+import java.awt.BasicStroke;
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Rectangle;
+import java.awt.Window;
+import java.awt.event.ActionEvent;
+import java.awt.geom.GeneralPath;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.List;
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JSplitPane;
+import javax.swing.JTree;
+import javax.swing.event.TreeSelectionEvent;
+import javax.swing.plaf.basic.BasicLabelUI;
+import javax.swing.plaf.basic.BasicTreeUI;
+import javax.swing.tree.DefaultTreeCellRenderer;
+import javax.swing.tree.TreeNode;
+import javax.swing.tree.TreePath;
+
+/**
+ *
+ * @author JPEXS
+ */
+public class SelectTagPositionDialog extends AppDialog {
+
+ private final JButton okButton = new JButton(translate("button.ok"));
+
+ private final JButton cancelButton = new JButton(translate("button.cancel"));
+
+ private JTree positionTree;
+
+ private PreviewPanel previewPanel;
+
+ private final SWF swf;
+
+ private int result = ERROR_OPTION;
+
+ private Tag selectedTag = null;
+ private Timelined selectedTimelined = null;
+
+ private static class MyTreeNode implements TreeNode {
+
+ private final List children = new ArrayList<>();
+ private TreeNode parent;
+ private Object data;
+
+ @Override
+ public String toString() {
+ if (data instanceof DoInitActionTag) {
+ DoInitActionTag doinit = (DoInitActionTag) data;
+ String exportName = doinit.getSwf().getExportName(doinit.spriteId);
+ if (exportName != null && !exportName.isEmpty()) {
+ return DoInitActionTag.NAME + " (" + doinit.spriteId + ") : " + exportName;
+ }
+ }
+ return data.toString();
+ }
+
+ public void setData(Object data) {
+ this.data = data;
+ }
+
+ public Object getData() {
+ return data;
+ }
+
+ public void addChild(TreeNode node) {
+ children.add(node);
+ }
+
+ public void setParent(TreeNode parent) {
+ this.parent = parent;
+ }
+
+ @Override
+ public TreeNode getChildAt(int childIndex) {
+ return children.get(childIndex);
+ }
+
+ @Override
+ public int getChildCount() {
+ return children.size();
+ }
+
+ @Override
+ public TreeNode getParent() {
+ return parent;
+ }
+
+ @Override
+ public int getIndex(TreeNode node) {
+ return children.indexOf(node);
+ }
+
+ @Override
+ public boolean getAllowsChildren() {
+ return true;
+ }
+
+ @Override
+ public boolean isLeaf() {
+ return children.isEmpty();
+ }
+
+ @Override
+ public Enumeration extends TreeNode> children() {
+ return Collections.enumeration(children);
+ }
+ }
+
+ private static class MyTimelineEnd {
+
+ @Override
+ public String toString() {
+ return AppDialog.translateForDialog("timeline.end", SelectTagPositionDialog.class);
+ }
+ }
+
+ private static class MyFrame {
+
+ private final int frame;
+ private boolean invalid;
+
+ public MyFrame(int frame) {
+ this.frame = frame;
+ }
+
+ public int getFrame() {
+ return frame;
+ }
+
+ public void setInvalid(boolean invalid) {
+ this.invalid = invalid;
+ }
+
+ public boolean isInvalid() {
+ return invalid;
+ }
+
+ @Override
+ public String toString() {
+ return "frame " + frame;
+ }
+ }
+
+ private void populateNodes(MyTreeNode root, Timelined tim, int currentFrame) {
+ int f = 1;
+
+ MyTreeNode frameNode = new MyTreeNode();
+ frameNode.setData(new MyFrame(1));
+ frameNode.setParent(root);
+ root.addChild(frameNode);
+
+ for (Tag t : tim.getTags()) {
+ MyTreeNode node = new MyTreeNode();
+ node.setData(t);
+ frameNode.addChild(node);
+
+ if (t instanceof DefineSpriteTag) {
+ populateNodes(node, (DefineSpriteTag) t, 1);
+ }
+ if (t instanceof ShowFrameTag) {
+ f++;
+ frameNode = new MyTreeNode();
+ frameNode.setData(new MyFrame(f));
+ frameNode.setParent(root);
+ root.addChild(frameNode);
+ }
+ }
+ if (frameNode.isLeaf()) {
+ root.children.remove(root.children.size() - 1);
+ }
+ MyTimelineEnd end = new MyTimelineEnd();
+ MyTreeNode endNode = new MyTreeNode();
+ endNode.setData(end);
+ root.addChild(endNode);
+ }
+
+ private void selectCurrent(MyTreeNode root, Timelined timelined, List