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 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 path) { + + for (int i = 0; i < root.getChildCount(); i++) { + MyTreeNode node = (MyTreeNode) root.getChildAt(i); + + List subPath = new ArrayList<>(path); + subPath.add(node); + + if (node.getData() == selectedTag && timelined == selectedTimelined) { + Object[] pathArray = subPath.toArray(new Object[subPath.size()]); + TreePath tpath = new TreePath(pathArray); + positionTree.setSelectionPath(tpath); + positionTree.scrollPathToVisible(tpath); + return; + } + + if (node.getData() instanceof DefineSpriteTag) { + selectCurrent(node, (DefineSpriteTag) node.getData(), subPath); + } else { + selectCurrent(node, timelined, subPath); + } + } + } + + public SelectTagPositionDialog(Window parent, SWF swf) { + this(parent, swf, null, null); + } + + private static class PositionTreeCellRenderer extends DefaultTreeCellRenderer { + + private boolean selected; + + public PositionTreeCellRenderer() { + if (View.isOceanic()) { + setUI(new BasicLabelUI()); + setOpaque(false); + setBackgroundNonSelectionColor(Color.white); + } + } + + @Override + public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus) { + Component renderer = super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus); + this.selected = sel; + if (renderer instanceof JLabel) { + JLabel lab = (JLabel) renderer; + + Object subValue = value; + if (value instanceof MyTreeNode) { + subValue = ((MyTreeNode) value).getData(); + } + TreeNodeType nodeType; + if (subValue instanceof MyFrame) { + nodeType = TreeNodeType.FRAME; + } else if (subValue instanceof FontTag) { + nodeType = TreeNodeType.FONT; + } else if (subValue instanceof TextTag) { + nodeType = TreeNodeType.TEXT; + } else if (subValue instanceof ImageTag) { + nodeType = TreeNodeType.IMAGE; + } else if (subValue instanceof ShapeTag) { + nodeType = TreeNodeType.SHAPE; + } else if (subValue instanceof MorphShapeTag) { + nodeType = TreeNodeType.MORPH_SHAPE; + } else if (subValue instanceof DefineSpriteTag) { + nodeType = TreeNodeType.SPRITE; + } else if (subValue instanceof ButtonTag) { + nodeType = TreeNodeType.BUTTON; + } else if (subValue instanceof DefineVideoStreamTag) { + nodeType = TreeNodeType.MOVIE; + } else if ((subValue instanceof DefineSoundTag) || (subValue instanceof SoundStreamHeadTypeTag) || (subValue instanceof SoundStreamBlockTag)) { + nodeType = TreeNodeType.SOUND; + } else if (subValue instanceof DefineBinaryDataTag) { + nodeType = TreeNodeType.BINARY_DATA; + } else if ((subValue instanceof DoActionTag) + || (subValue instanceof DoInitActionTag) + || (subValue instanceof DoABCTag) + || (subValue instanceof DoABC2Tag)) { + nodeType = TreeNodeType.AS; + } else if (subValue instanceof ShowFrameTag) { + nodeType = TreeNodeType.FRAME; + } else if (subValue instanceof SetBackgroundColorTag) { + nodeType = TreeNodeType.SET_BACKGROUNDCOLOR; + } else if (subValue instanceof FileAttributesTag) { + nodeType = TreeNodeType.FILE_ATTRIBUTES; + } else if (subValue instanceof MetadataTag) { + nodeType = TreeNodeType.METADATA; + } else if (subValue instanceof PlaceObjectTypeTag) { + nodeType = TreeNodeType.PLACE_OBJECT; + } else if (subValue instanceof RemoveTag) { + nodeType = TreeNodeType.REMOVE_OBJECT; + } else if (subValue instanceof MyTimelineEnd) { + nodeType = null; + } else { + nodeType = TreeNodeType.OTHER_TAG; + } + if (nodeType == null) { + //nothing + } else { + lab.setIcon(TagTree.getIconForType(nodeType)); + } + } + return renderer; + } + + } + + public SelectTagPositionDialog(Window parent, SWF swf, Tag selectedTag, Timelined selectedTimelined) { + super(parent); + this.swf = swf; + this.selectedTag = selectedTag; + this.selectedTimelined = selectedTimelined; + setTitle(translate("dialog.title")); + setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE); + Container cnt = getContentPane(); + cnt.setLayout(new BorderLayout()); + + JPanel buttonsPanel = new JPanel(new FlowLayout()); + + okButton.addActionListener(this::okButtonActionPerformed); + cancelButton.addActionListener(this::cancelButtonActionPerformed); + + buttonsPanel.add(okButton); + buttonsPanel.add(cancelButton); + cnt.add(buttonsPanel, BorderLayout.SOUTH); + + MyTreeNode root = new MyTreeNode(); + root.setData("root"); + + populateNodes(root, swf, 1); + + positionTree = new JTree(root) { + @Override + protected void paintComponent(Graphics g) { + super.paintComponent(g); + int rows[] = getSelectionRows(); + if (rows.length == 1) { + int row = rows[0]; + + Object selection = getLastSelectedPathComponent(); + boolean onFrame = false; + if (selection != null && (((MyTreeNode)selection).getData() instanceof MyFrame)) { // && !isCollapsed(row)) { + onFrame = true; + return; + } + Rectangle rect = this.getRowBounds(row); + int sideWidth = 6; + int sideHeight = 6; + int offsetX = -5; + + + int lineStartX = offsetX + rect.x; + int backStartX = getWidth() + offsetX; + if (onFrame) { + g.fillRect(lineStartX, rect.y + rect.height - 1, getWidth() - lineStartX, 1); + } else { + g.fillRect(lineStartX, rect.y, getWidth() - lineStartX, 1); + } + Graphics2D g2d = (Graphics2D) g; + g2d.setPaint(getForeground()); + GeneralPath path = new GeneralPath(); + + if (onFrame) { + path.moveTo(backStartX - 6, rect.y + rect.height - 1 - 6); + path.lineTo(backStartX, rect.y + rect.height - 1); + path.lineTo(backStartX + 6, rect.y + rect.height - 1 - 6); + } else { + path.moveTo(lineStartX - 6, rect.y + 6); + path.lineTo(lineStartX, rect.y); + path.lineTo(lineStartX + 6, rect.y + 6); + } + path.closePath(); + g2d.fill(path); + } + } + + }; + if (View.isOceanic()) { + positionTree.setBackground(Color.white); + positionTree.setUI(new BasicTreeUI() { + { + setHashColor(Color.gray); + } + }); + } + positionTree.setCellRenderer(new PositionTreeCellRenderer()); + positionTree.setRootVisible(false); + positionTree.setShowsRootHandles(true); + positionTree.addTreeSelectionListener(this::spriteValueChanged); + + previewPanel = new PreviewPanel(Main.getMainFrame().getPanel(), null); + previewPanel.setReadOnly(true); + previewPanel.setPreferredSize(new Dimension(300, 1)); + previewPanel.showEmpty(); + previewPanel.setParametersPanelVisible(false); + + JScrollPane positionTreeScrollPane = new FasterScrollPane(positionTree); + JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, positionTreeScrollPane, previewPanel); + //splitPane.setDividerLocation(600); + cnt.add(splitPane, BorderLayout.CENTER); + + List path = new ArrayList<>(); + path.add(root); + selectCurrent(root, swf, path); + + setSize(1024, 600); + setModal(true); + setResizable(true); + View.centerScreen(this); + View.setWindowIcon(this); + } + + private int getCurrentSelectedFrame() { + TreePath path = positionTree.getSelectionPath(); + for (int i = path.getPathCount() - 1; i >= 0; i--) { + MyTreeNode node = (MyTreeNode) path.getPathComponent(i); + if (node.getData() instanceof MyFrame) { + MyFrame frame = (MyFrame) node.getData(); + return frame.frame; + } + } + return -1; + } + + private Timelined getCurrentSelectedTimelined() { + TreePath path = positionTree.getSelectionPath(); + for (int i = path.getPathCount() - 1; i >= 0; i--) { + MyTreeNode node = (MyTreeNode) path.getPathComponent(i); + if ("root".equals(node.getData())) { + return swf; + } + if (node.getData() instanceof DefineSpriteTag) { + return (Timelined) node.getData(); + } + } + return null; + } + + private void spriteValueChanged(TreeSelectionEvent e) { + + TreePath selection = positionTree.getSelectionPath(); + if (selection == null) { + previewPanel.showEmpty(); + return; + } + MyTreeNode tnode = (MyTreeNode) selection.getLastPathComponent(); + if (tnode.getData() instanceof Tag) { + MainPanel.showPreview((TreeItem) tnode.getData(), previewPanel, getCurrentSelectedFrame() - 1, getCurrentSelectedTimelined()); + } else if (tnode.getData() instanceof MyFrame) { + int f = ((MyFrame) tnode.getData()).frame; + Object parent = ((MyTreeNode) tnode.getParent()).getData(); + if (parent instanceof DefineSpriteTag) { + previewPanel.showImagePanel((DefineSpriteTag) parent, swf, f - 1, true); + } else { + previewPanel.showImagePanel(swf, swf, f - 1, true); + } + } else { + previewPanel.showEmpty(); + } + } + + private void cancelButtonActionPerformed(ActionEvent evt) { + result = CANCEL_OPTION; + previewPanel.clear(); + setVisible(false); + } + + private void okButtonActionPerformed(ActionEvent evt) { + MyTreeNode node = (MyTreeNode)positionTree.getLastSelectedPathComponent(); + if (node.getData() instanceof MyFrame) { + return; + } + if (node.getData() instanceof MyTimelineEnd) { + selectedTag = null; + } else { + selectedTag = (Tag) node.getData(); + } + + selectedTimelined = getCurrentSelectedTimelined(); + + result = OK_OPTION; + previewPanel.clear(); + setVisible(false); + } + + public int showDialog() { + setVisible(true); + return result; + } + + /** + * Gets current selected tag to determine position. null = end of timeline position + * @return + */ + public Tag getSelectedTag() { + return selectedTag; + } + + /** + * Gets selected timelined + * @return + */ + public Timelined getSelectedTimelined() { + return selectedTimelined; + } + +} diff --git a/src/com/jpexs/decompiler/flash/gui/SoundTagPlayer.java b/src/com/jpexs/decompiler/flash/gui/SoundTagPlayer.java index f4dca37f4..df4f367ba 100644 --- a/src/com/jpexs/decompiler/flash/gui/SoundTagPlayer.java +++ b/src/com/jpexs/decompiler/flash/gui/SoundTagPlayer.java @@ -268,111 +268,115 @@ public class SoundTagPlayer implements MediaDisplay { } private void playLoop() { - final byte[] data = new byte[16]; - setPausedFlag(false); - setActiveFlag(true); - long posBytes = 0; - try { - int numReadBytes = 0; - while (numReadBytes != -1) { + loop: while (true) { + + final byte[] data = new byte[16]; + + setPausedFlag(false); + setActiveFlag(true); + long posBytes = 0; + try { + int numReadBytes = 0; + while (numReadBytes != -1) { + + if (getClosedFlag()) { + break; + } + if (!getPausedFlag()) { + if (newPositionMicrosec != null) { + long newPosBytes = (long) (newPositionMicrosec / microsecPerByte); + audioStream.close(); + reloadAudioStream(); + audioStream.skip(newPosBytes); + newPositionMicrosec = null; + posBytes = newPosBytes; + } + + numReadBytes = audioStream.read(data, 0, data.length); + if (numReadBytes != -1) { + posBytes += numReadBytes; + if (sourceLine.isControlSupported(FloatControl.Type.MASTER_GAIN)) { + //((FloatControl) sourceLine.getControl(FloatControl.Type.MASTER_GAIN)).setValue(volume_dB); + } + sourceLine.write(data, 0, numReadBytes); + synchronized (playLock) { + positionMicrosec = microsecPerByte * posBytes; + } + } + } + + if (getPausedFlag()) { + synchronized (thread) { + try { + thread.wait(1000); + } catch (InterruptedException ex) { + Logger.getLogger(SoundTagPlayer.class.getName()).log(Level.SEVERE, null, ex); + } + } + } + } if (getClosedFlag()) { - break; - } - if (!getPausedFlag()) { - if (newPositionMicrosec != null) { - long newPosBytes = (long) (newPositionMicrosec / microsecPerByte); - audioStream.close(); - reloadAudioStream(); - audioStream.skip(newPosBytes); - newPositionMicrosec = null; - posBytes = newPosBytes; - } - - numReadBytes = audioStream.read(data, 0, data.length); - if (numReadBytes != -1) { - posBytes += numReadBytes; - if (sourceLine.isControlSupported(FloatControl.Type.MASTER_GAIN)) { - //((FloatControl) sourceLine.getControl(FloatControl.Type.MASTER_GAIN)).setValue(volume_dB); - } - sourceLine.write(data, 0, numReadBytes); - synchronized (playLock) { - positionMicrosec = microsecPerByte * posBytes; - } - } - } - - if (getPausedFlag()) { - synchronized (thread) { - try { - thread.wait(1000); - } catch (InterruptedException ex) { - Logger.getLogger(SoundTagPlayer.class.getName()).log(Level.SEVERE, null, ex); - } - } + sourceLine.drain(); + sourceLine.stop(); + sourceLine.close(); } + audioStream.close(); + } catch (IOException ex) { + Logger.getLogger(SoundTagPlayer.class.getName()).log(Level.SEVERE, null, ex); + } catch (UnsupportedAudioFileException ex) { + Logger.getLogger(SoundTagPlayer.class.getName()).log(Level.SEVERE, null, ex); } - if (getClosedFlag()) { - sourceLine.drain(); - sourceLine.stop(); - sourceLine.close(); - } - audioStream.close(); - } catch (IOException ex) { - Logger.getLogger(SoundTagPlayer.class.getName()).log(Level.SEVERE, null, ex); - } catch (UnsupportedAudioFileException ex) { - Logger.getLogger(SoundTagPlayer.class.getName()).log(Level.SEVERE, null, ex); - } + if (!getClosedFlag()) { + decreaseLoopCount(); - if (!getClosedFlag()) { - decreaseLoopCount(); - - int currentLoopCount; - synchronized (playLock) { - currentLoopCount = loopCount; - } - - if (currentLoopCount > 0) { - try { - reloadAudioStream(); - playLoop(); - } catch (IOException ex) { - Logger.getLogger(SoundTagPlayer.class.getName()).log(Level.SEVERE, null, ex); - } catch (UnsupportedAudioFileException ex) { - Logger.getLogger(SoundTagPlayer.class.getName()).log(Level.SEVERE, null, ex); + int currentLoopCount; + synchronized (playLock) { + currentLoopCount = loopCount; } - } else { - setActiveFlag(false); - firePlayingFinished(); - if (getClosedFlag()) { - return; - } - synchronized (thread) { + if (currentLoopCount > 0) { try { - thread.wait(); - } catch (InterruptedException ex) { + reloadAudioStream(); + continue loop; + } catch (IOException ex) { + Logger.getLogger(SoundTagPlayer.class.getName()).log(Level.SEVERE, null, ex); + } catch (UnsupportedAudioFileException ex) { + Logger.getLogger(SoundTagPlayer.class.getName()).log(Level.SEVERE, null, ex); + } + } else { + setActiveFlag(false); + firePlayingFinished(); + + if (getClosedFlag()) { return; } - } + synchronized (thread) { + try { + thread.wait(); + } catch (InterruptedException ex) { + return; + } + } - if (getClosedFlag()) { - return; - } - setActiveFlag(true); + if (getClosedFlag()) { + return; + } + setActiveFlag(true); - try { - reloadAudioStream(); - playLoop(); - } catch (IOException ex) { - Logger.getLogger(SoundTagPlayer.class.getName()).log(Level.SEVERE, null, ex); - } catch (UnsupportedAudioFileException ex) { - Logger.getLogger(SoundTagPlayer.class.getName()).log(Level.SEVERE, null, ex); + try { + reloadAudioStream(); + continue loop; + } catch (IOException ex) { + Logger.getLogger(SoundTagPlayer.class.getName()).log(Level.SEVERE, null, ex); + } catch (UnsupportedAudioFileException ex) { + Logger.getLogger(SoundTagPlayer.class.getName()).log(Level.SEVERE, null, ex); + } } } - + break; } } diff --git a/src/com/jpexs/decompiler/flash/gui/dumpview/DumpTree.java b/src/com/jpexs/decompiler/flash/gui/dumpview/DumpTree.java index c8fe3843b..0201a6165 100644 --- a/src/com/jpexs/decompiler/flash/gui/dumpview/DumpTree.java +++ b/src/com/jpexs/decompiler/flash/gui/dumpview/DumpTree.java @@ -72,6 +72,7 @@ import com.jpexs.decompiler.flash.tags.RemoveObject2Tag; import com.jpexs.decompiler.flash.tags.RemoveObjectTag; 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.SoundStreamHead2Tag; import com.jpexs.decompiler.flash.tags.SoundStreamHeadTag; import com.jpexs.decompiler.flash.tags.Tag; @@ -183,6 +184,7 @@ public class DumpTree extends JTree { case DefineSoundTag.NAME: case SoundStreamHeadTag.NAME: case SoundStreamHead2Tag.NAME: + case SoundStreamBlockTag.NAME: nodeType = TreeNodeType.SOUND; break; case DefineBinaryDataTag.NAME: diff --git a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties index e81123a0a..aa535c164 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties @@ -859,4 +859,6 @@ work.generating_swd = Generating SWD file button.replaceRefs = Replace references with other character ID -contextmenu.cloneTag = Clone tag \ No newline at end of file +contextmenu.cloneTag = Clone tag + +contextmenu.setTagPosition = Set tag position \ No newline at end of file diff --git a/src/com/jpexs/decompiler/flash/gui/locales/SelectTagPositionDialog.properties b/src/com/jpexs/decompiler/flash/gui/locales/SelectTagPositionDialog.properties new file mode 100644 index 000000000..d515cda56 --- /dev/null +++ b/src/com/jpexs/decompiler/flash/gui/locales/SelectTagPositionDialog.properties @@ -0,0 +1,19 @@ +# 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 . + +dialog.title = Select tag position +button.ok = OK +button.cancel = Cancel +timeline.end = end of timeline \ 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 c07e983ec..b81977455 100644 --- a/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeContextMenu.java +++ b/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeContextMenu.java @@ -27,6 +27,7 @@ import com.jpexs.decompiler.flash.action.Action; import com.jpexs.decompiler.flash.action.parser.ActionParseException; import com.jpexs.decompiler.flash.action.parser.script.ActionScript2Parser; import com.jpexs.decompiler.flash.gui.AppDialog; +import com.jpexs.decompiler.flash.gui.SelectTagPositionDialog; import com.jpexs.decompiler.flash.gui.Main; import com.jpexs.decompiler.flash.gui.MainPanel; import com.jpexs.decompiler.flash.gui.ReplaceCharacterDialog; @@ -157,6 +158,8 @@ public class TagTreeContextMenu extends JPopupMenu { private JMenuItem addAs3ClassMenuItem; private JMenuItem textSearchMenuItem; + + private JMenuItem setTagPositionMenuItem; public TagTreeContextMenu(final TagTree tagTree, MainPanel mainPanel) { this.mainPanel = mainPanel; @@ -226,6 +229,10 @@ public class TagTreeContextMenu extends JPopupMenu { importSwfXmlMenuItem.addActionListener(mainPanel::importSwfXmlActionPerformed); add(importSwfXmlMenuItem); + setTagPositionMenuItem = new JMenuItem(mainPanel.translate("contextmenu.setTagPosition")); + setTagPositionMenuItem.addActionListener(this::setTagPositionActionPerformed); + add(setTagPositionMenuItem); + addTagMenu = new JMenu(mainPanel.translate("contextmenu.addTag")); add(addTagMenu); @@ -233,7 +240,7 @@ public class TagTreeContextMenu extends JPopupMenu { add(moveTagMenu); copyTagMenu = new JMenu(mainPanel.translate("contextmenu.copyTag")); - add(copyTagMenu); + add(copyTagMenu); copyTagWithDependenciesMenu = new JMenu(mainPanel.translate("contextmenu.copyTagWithDependencies")); add(copyTagWithDependenciesMenu); @@ -485,6 +492,7 @@ public class TagTreeContextMenu extends JPopupMenu { addAs12ScriptMenuItem.setVisible(false); addAs3ClassMenuItem.setVisible(false); textSearchMenuItem.setVisible(hasScripts || hasTexts); + setTagPositionMenuItem.setVisible(items.size() == 1 && (items.get(0) instanceof Tag)); if (allSelectedIsTag) { boolean canUndo = false; @@ -688,7 +696,7 @@ public class TagTreeContextMenu extends JPopupMenu { } } - private int chechUniqueCharacterId(Tag tag) { + private int checkUniqueCharacterId(Tag tag) { if (tag instanceof CharacterTag) { CharacterTag characterTag = (CharacterTag) tag; int characterId = characterTag.getCharacterId(); @@ -713,7 +721,7 @@ public class TagTreeContextMenu extends JPopupMenu { sourceSwf.removeTag(tag); tag.setSwf(targetSwf, true); targetSwf.addTag(tag); - chechUniqueCharacterId(tag); + checkUniqueCharacterId(tag); targetSwf.updateCharacters(); tag.setModified(true); } @@ -738,7 +746,7 @@ public class TagTreeContextMenu extends JPopupMenu { Tag copyTag = tag.cloneTag(); copyTag.setSwf(targetSwf, true); targetSwf.addTag(copyTag); - chechUniqueCharacterId(copyTag); + checkUniqueCharacterId(copyTag); targetSwf.updateCharacters(); copyTag.setModified(true); } @@ -780,7 +788,7 @@ public class TagTreeContextMenu extends JPopupMenu { copyTag.setSwf(targetSwf, true); targetSwf.addTag(copyTag); int oldCharacterId = neededTag.getCharacterId(); - int newCharacterId = chechUniqueCharacterId(copyTag); + int newCharacterId = checkUniqueCharacterId(copyTag); changedCharacterIds.put(oldCharacterId, newCharacterId); targetSwf.updateCharacters(); @@ -797,7 +805,7 @@ public class TagTreeContextMenu extends JPopupMenu { if (tag instanceof CharacterTag) { CharacterTag characterTag = (CharacterTag) copyTag; int oldCharacterId = characterTag.getCharacterId(); - int newCharacterId = chechUniqueCharacterId(copyTag); + int newCharacterId = checkUniqueCharacterId(copyTag); changedCharacterIds.put(oldCharacterId, newCharacterId); } @@ -1764,7 +1772,7 @@ public class TagTreeContextMenu extends JPopupMenu { timelined.addTag(idx + 1, copyTag); copyTag.setTimelined(timelined); - chechUniqueCharacterId(copyTag); + checkUniqueCharacterId(copyTag); copyTag.setModified(true); timelined.resetTimeline(); @@ -1792,7 +1800,7 @@ public class TagTreeContextMenu extends JPopupMenu { timelined.addTag(++i, copyTag); copyTag.setTimelined(timelined); - chechUniqueCharacterId(copyTag); + checkUniqueCharacterId(copyTag); copyTag.setModified(true); } @@ -1818,4 +1826,40 @@ public class TagTreeContextMenu extends JPopupMenu { logger.log(Level.SEVERE, null, ex); } } + + private void setTagPositionActionPerformed(ActionEvent evt) { + List items = tagTree.getSelected(); + Tag t = (Tag)items.get(0); + TreePath path = tagTree.getSelectionPath(); + Timelined timelined = null; + for (int i = path.getPathCount()-1; i >= 0; i--) { + if ((path.getPathComponent(i) instanceof DefineSpriteTag)||(path.getPathComponent(i) instanceof SWF)) { + timelined = (Timelined)path.getPathComponent(i); + } + } + if (timelined == null) { //should not happen + return; + } + + SelectTagPositionDialog dialog = new SelectTagPositionDialog(Main.getDefaultDialogsOwner(), t.getSwf(), t, timelined); + if (dialog.showDialog() == AppDialog.OK_OPTION){ + Tag selectedTag = dialog.getSelectedTag(); + Timelined selectedTimelined = dialog.getSelectedTimelined(); + + if (selectedTag == t && selectedTimelined == timelined) { + return; + } + timelined.removeTag(t); + if (selectedTag == null) { + selectedTimelined.addTag(t); + } else { + selectedTimelined.addTag(selectedTimelined.indexOfTag(selectedTag), t); + } + timelined.resetTimeline(); + if (timelined != selectedTimelined) { + selectedTimelined.resetTimeline(); + } + mainPanel.refreshTree(t.getSwf()); + } + } }