From c43769410c85ca72e7c97c00137d34a9086f97cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jindra=20Pet=C5=99=C3=ADk?= Date: Sun, 6 Oct 2024 15:26:54 +0200 Subject: [PATCH] Add new (key)frame. Button add to stage fix. getCharacters sync fix --- .../src/com/jpexs/decompiler/flash/SWF.java | 12 +- .../decompiler/flash/tags/base/ButtonTag.java | 2 +- .../flash/timeline/TweenDetector.java | 5 +- ....java => CharacterTagTransferHandler.java} | 5 +- ...ble.java => CharacterTagTransferable.java} | 17 +- .../flash/easygui/LibraryTreeTable.java | 2 +- .../decompiler/flash/easygui/MainFrame.java | 61 ++- .../flash/easygui/TimelineBodyPanel.java | 437 ++++++++++++++++-- .../flash/easygui/TimelineDepthPanel.java | 10 +- .../flash/easygui/TimelinePanel.java | 97 ++-- .../TimelinedTagListDoableOperation.java | 69 +++ 11 files changed, 604 insertions(+), 113 deletions(-) rename src/com/jpexs/decompiler/flash/easygui/{TagTransferHandler.java => CharacterTagTransferHandler.java} (91%) rename src/com/jpexs/decompiler/flash/easygui/{TagTransferable.java => CharacterTagTransferable.java} (72%) create mode 100644 src/com/jpexs/decompiler/flash/easygui/TimelinedTagListDoableOperation.java 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 baefdb122..c406be8bc 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWF.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWF.java @@ -952,8 +952,10 @@ public final class SWF implements SWFContainerItem, Timelined, Openable { * @return Character id to character map */ public Map getCharacters(boolean withImported) { + Map newCharacters = characters; + Map newCharactersWithImported = charactersWithImported; synchronized (charactersLock) { - if (characters == null || charactersWithImported == null) { + if (newCharacters == null || newCharactersWithImported == null) { if (destroyed) { return new HashMap<>(); } @@ -988,14 +990,16 @@ public final class SWF implements SWFContainerItem, Timelined, Openable { } } - characters = Collections.unmodifiableMap(chars); - charactersWithImported = Collections.unmodifiableMap(charsWithImported); + newCharacters = Collections.unmodifiableMap(chars); + newCharactersWithImported = Collections.unmodifiableMap(charsWithImported); + characters = newCharacters; + charactersWithImported = newCharactersWithImported; characterToId = Collections.unmodifiableMap(charToId); characterIdTags = Collections.unmodifiableMap(charIdtags); externalImages2 = Collections.unmodifiableMap(eimages); } - return withImported ? charactersWithImported : characters; + return withImported ? newCharactersWithImported : newCharacters; } } 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 cfbef24f2..622441ec7 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 @@ -64,7 +64,7 @@ public abstract class ButtonTag extends DrawableTag implements Timelined { */ public static int FRAME_HITTEST = 3; - private Timeline timeline; + private transient Timeline timeline; private boolean isSingleFrameInitialized; diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/timeline/TweenDetector.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/timeline/TweenDetector.java index 18b7d6215..a56ec0cd3 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/timeline/TweenDetector.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/timeline/TweenDetector.java @@ -28,12 +28,13 @@ import java.util.List; public class TweenDetector { public static List detectRanges(List depthStates) { + return new ArrayList<>(); //TODO: make this working :-( - if (depthStates.size() < 2 || depthStates.get(0).placeObjectTag == depthStates.get(1).placeObjectTag) { + /*if (depthStates.size() < 2 || depthStates.get(0).placeObjectTag == depthStates.get(1).placeObjectTag) { return new ArrayList<>(); } - return new ArrayList<>(Arrays.asList(new TweenRange(0, depthStates.size() - 1))); + return new ArrayList<>(Arrays.asList(new TweenRange(0, depthStates.size() - 1)));*/ /* List ret = new ArrayList<>(); diff --git a/src/com/jpexs/decompiler/flash/easygui/TagTransferHandler.java b/src/com/jpexs/decompiler/flash/easygui/CharacterTagTransferHandler.java similarity index 91% rename from src/com/jpexs/decompiler/flash/easygui/TagTransferHandler.java rename to src/com/jpexs/decompiler/flash/easygui/CharacterTagTransferHandler.java index e5200af02..e0029111a 100644 --- a/src/com/jpexs/decompiler/flash/easygui/TagTransferHandler.java +++ b/src/com/jpexs/decompiler/flash/easygui/CharacterTagTransferHandler.java @@ -19,6 +19,7 @@ package com.jpexs.decompiler.flash.easygui; import com.jpexs.decompiler.flash.tags.DefineSpriteTag; import com.jpexs.decompiler.flash.tags.Tag; import com.jpexs.decompiler.flash.tags.base.ButtonTag; +import com.jpexs.decompiler.flash.tags.base.CharacterTag; import com.jpexs.decompiler.flash.tags.base.ShapeTag; import com.jpexs.decompiler.flash.tags.base.TextTag; import de.javagl.treetable.JTreeTable; @@ -31,7 +32,7 @@ import javax.swing.tree.DefaultMutableTreeNode; * * @author JPEXS */ - class TagTransferHandler extends TransferHandler { + class CharacterTagTransferHandler extends TransferHandler { @Override protected Transferable createTransferable(JComponent c) { @@ -46,7 +47,7 @@ import javax.swing.tree.DefaultMutableTreeNode; || (o instanceof TextTag) || (o instanceof ButtonTag) ) { - return new TagTransferable((Tag) o); + return new CharacterTagTransferable((CharacterTag) o); } } return null; diff --git a/src/com/jpexs/decompiler/flash/easygui/TagTransferable.java b/src/com/jpexs/decompiler/flash/easygui/CharacterTagTransferable.java similarity index 72% rename from src/com/jpexs/decompiler/flash/easygui/TagTransferable.java rename to src/com/jpexs/decompiler/flash/easygui/CharacterTagTransferable.java index eb7d9003f..55ea51f44 100644 --- a/src/com/jpexs/decompiler/flash/easygui/TagTransferable.java +++ b/src/com/jpexs/decompiler/flash/easygui/CharacterTagTransferable.java @@ -17,6 +17,7 @@ package com.jpexs.decompiler.flash.easygui; import com.jpexs.decompiler.flash.tags.Tag; +import com.jpexs.decompiler.flash.tags.base.CharacterTag; import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.Transferable; import java.awt.datatransfer.UnsupportedFlavorException; @@ -26,30 +27,30 @@ import java.io.IOException; * * @author JPEXS */ -public class TagTransferable implements Transferable { +public class CharacterTagTransferable implements Transferable { - public static final DataFlavor TAG_FLAVOR = new DataFlavor(Tag.class, "Tag"); + public static final DataFlavor CHARACTERTAG_FLAVOR = new DataFlavor(Integer.class, "Tag"); - private Tag tag; + private CharacterTag tag; - public TagTransferable(Tag tag) { + public CharacterTagTransferable(CharacterTag tag) { this.tag = tag; } @Override public DataFlavor[] getTransferDataFlavors() { - return new DataFlavor[]{TAG_FLAVOR}; + return new DataFlavor[]{CHARACTERTAG_FLAVOR}; } @Override public boolean isDataFlavorSupported(DataFlavor flavor) { - return TAG_FLAVOR.equals(flavor); + return CHARACTERTAG_FLAVOR.equals(flavor); } @Override public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException { - if (TAG_FLAVOR.equals(flavor)) { - return tag; + if (CHARACTERTAG_FLAVOR.equals(flavor)) { + return tag.getCharacterId(); } return null; } diff --git a/src/com/jpexs/decompiler/flash/easygui/LibraryTreeTable.java b/src/com/jpexs/decompiler/flash/easygui/LibraryTreeTable.java index df88618bc..14b987beb 100644 --- a/src/com/jpexs/decompiler/flash/easygui/LibraryTreeTable.java +++ b/src/com/jpexs/decompiler/flash/easygui/LibraryTreeTable.java @@ -95,7 +95,7 @@ public class LibraryTreeTable extends JTreeTable { } } }); - setTransferHandler(new TagTransferHandler()); + setTransferHandler(new CharacterTagTransferHandler()); setDragEnabled(true); setSelectionMode(ListSelectionModel.SINGLE_SELECTION); } diff --git a/src/com/jpexs/decompiler/flash/easygui/MainFrame.java b/src/com/jpexs/decompiler/flash/easygui/MainFrame.java index a77f78d61..5e60b7eda 100644 --- a/src/com/jpexs/decompiler/flash/easygui/MainFrame.java +++ b/src/com/jpexs/decompiler/flash/easygui/MainFrame.java @@ -164,7 +164,7 @@ public class MainFrame extends JFrame { stagePanel.setTransferHandler(new TransferHandler() { @Override public boolean canImport(TransferHandler.TransferSupport support) { - return support.isDataFlavorSupported(TagTransferable.TAG_FLAVOR); + return support.isDataFlavorSupported(CharacterTagTransferable.CHARACTERTAG_FLAVOR); } @Override @@ -174,8 +174,8 @@ public class MainFrame extends JFrame { } try { Transferable transferable = support.getTransferable(); - Tag tag = (Tag) transferable.getTransferData(TagTransferable.TAG_FLAVOR); - + Integer characterId = (Integer) transferable.getTransferData(CharacterTagTransferable.CHARACTERTAG_FLAVOR); + CharacterTag tag = stagePanel.getTimelined().getSwf().getCharacter(characterId); if ((tag instanceof DefineSpriteTag) || (tag instanceof ShapeTag) || (tag instanceof TextTag) @@ -197,6 +197,9 @@ public class MainFrame extends JFrame { int maxDepth = stagePanel.getTimelined().getTimeline().getMaxDepth(); int newDepth = maxDepth + 1; Timelined timelined = stagePanel.getTimelined(); + + tags = timelined.getSwf().getTags().toArrayList(); + ShowFrameTag showFrameTag = timelined.getTimeline().getFrame(frame).showFrameTag; place = new PlaceObject2Tag(timelined.getSwf()); place.depth = newDepth; @@ -215,7 +218,7 @@ public class MainFrame extends JFrame { timelined.addTag(timelined.indexOfTag(showFrameTag) + 1, remove); } - tags = timelined.getSwf().getTags().toArrayList(); + DefineBeforeUsageFixer fixer = new DefineBeforeUsageFixer(); boolean tagOrderChanged = fixer.fixDefineBeforeUsage(timelined.getSwf()); if (!tagOrderChanged) { @@ -330,7 +333,34 @@ public class MainFrame extends JFrame { topPanel.add(toolbarPanel, BorderLayout.NORTH); topPanel.add(stagePanel, BorderLayout.CENTER); - timelinePanel = new TimelinePanel(); + timelinePanel = new TimelinePanel(undoManager); + + timelinePanel.addChangeListener(new Runnable() { + @Override + public void run() { + stagePanel.repaint(); + } + }); + timelinePanel.addFrameSelectionListener(new FrameSelectionListener() { + @Override + public void frameSelected(int frame, int depth) { + stagePanel.pause(); + stagePanel.gotoFrame(frame + 1); + stagePanel.selectDepth(depth); + if (transformEnabled()) { + stagePanel.freeTransformDepth(depth); + } + + if (depth != -1) { + DepthState ds = stagePanel.getTimelined().getTimeline().getFrame(stagePanel.getFrame()).layers.get(depth); + if (ds == null) { + depth = -1; + } + } + transformPanel.setVisible(depth != -1); + } + }); + verticalSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, topPanel, timelinePanel); libraryTreeTable = new LibraryTreeTable(); @@ -414,26 +444,7 @@ public class MainFrame extends JFrame { stagePanel.setTimelined(swf, swf, 0, true, true, true, true, true, false, true); stagePanel.pause(); stagePanel.gotoFrame(0); - timelinePanel.setTimelined(swf); - timelinePanel.addFrameSelectionListener(new FrameSelectionListener() { - @Override - public void frameSelected(int frame, int depth) { - stagePanel.pause(); - stagePanel.gotoFrame(frame + 1); - stagePanel.selectDepth(depth); - if (transformEnabled()) { - stagePanel.freeTransformDepth(depth); - } - - if (depth != -1) { - DepthState ds = stagePanel.getTimelined().getTimeline().getFrame(stagePanel.getFrame()).layers.get(depth); - if (ds == null) { - depth = -1; - } - } - transformPanel.setVisible(depth != -1); - } - }); + timelinePanel.setTimelined(swf); } @Override diff --git a/src/com/jpexs/decompiler/flash/easygui/TimelineBodyPanel.java b/src/com/jpexs/decompiler/flash/easygui/TimelineBodyPanel.java index de652a2c2..61aeb05de 100644 --- a/src/com/jpexs/decompiler/flash/easygui/TimelineBodyPanel.java +++ b/src/com/jpexs/decompiler/flash/easygui/TimelineBodyPanel.java @@ -16,10 +16,17 @@ */ package com.jpexs.decompiler.flash.easygui; +import com.jpexs.decompiler.flash.ReadOnlyTagList; +import com.jpexs.decompiler.flash.tags.RemoveObject2Tag; +import com.jpexs.decompiler.flash.tags.ShowFrameTag; +import com.jpexs.decompiler.flash.tags.Tag; import com.jpexs.decompiler.flash.tags.base.CharacterTag; 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.timeline.DepthState; import com.jpexs.decompiler.flash.timeline.Timeline; +import com.jpexs.decompiler.flash.timeline.Timelined; import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics; @@ -28,15 +35,22 @@ import java.awt.Point; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.SystemColor; +import java.awt.event.ActionEvent; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; +import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.swing.JMenuItem; import javax.swing.JPanel; +import javax.swing.JPopupMenu; +import javax.swing.SwingUtilities; import org.pushingpixels.substance.internal.utils.SubstanceColorUtilities; /** @@ -44,8 +58,8 @@ import org.pushingpixels.substance.internal.utils.SubstanceColorUtilities; */ public class TimelineBodyPanel extends JPanel implements MouseListener, KeyListener { - private final Timeline timeline; - + private Timeline timeline; + public static final int FRAME_WIDTH = 8; public static final int FRAME_HEIGHT = 18; @@ -72,19 +86,23 @@ public class TimelineBodyPanel extends JPanel implements MouseListener, KeyListe public static final Color SELECTED_COLOR = new Color(0xff, 0x99, 0x99); public static final Color SELECTED_BORDER_COLOR = new Color(0xcc, 0, 0); - + //public static final Color SELECTED_COLOR = new Color(113, 174, 235); public static final int BORDER_LINES_LENGTH = 2; public static final float FONT_SIZE = 10.0f; - private final List listeners = new ArrayList<>(); + private final List selectionListeners = new ArrayList<>(); + + private final List changeListeners = new ArrayList<>(); public Point cursor = null; - - + private int frame = 0; + private int depth = 0; + private final UndoManager undoManager; + private enum BlockType { EMPTY, NORMAL, MOTION_TWEEN, SHAPE_TWEEN @@ -101,7 +119,7 @@ public class TimelineBodyPanel extends JPanel implements MouseListener, KeyListe public static Color getBackgroundColor() { return SystemColor.control; } - + public static Color getSelectedColor() { return SystemColor.textHighlight; } @@ -115,26 +133,38 @@ public class TimelineBodyPanel extends JPanel implements MouseListener, KeyListe } public void addFrameSelectionListener(FrameSelectionListener l) { - listeners.add(l); + selectionListeners.add(l); } public void removeFrameSelectionListener(FrameSelectionListener l) { - listeners.remove(l); + selectionListeners.remove(l); } - public TimelineBodyPanel(Timeline timeline) { + public void addChangeListener(Runnable l) { + changeListeners.add(l); + } - this.timeline = timeline; - Dimension dim = new Dimension(FRAME_WIDTH * timeline.getFrameCount() + 1, FRAME_HEIGHT * (timeline.getMaxDepth() + 1)); - setSize(dim); - setPreferredSize(dim); + public void removeChangeListener(Runnable l) { + changeListeners.remove(l); + } + + private void fireChanged() { + for (Runnable l : changeListeners) { + l.run(); + } + } + + public TimelineBodyPanel(UndoManager undoManager) { + refresh(); addMouseListener(this); addKeyListener(this); setFocusable(true); + this.undoManager = undoManager; } @Override protected void paintComponent(Graphics g1) { + Graphics2D g = (Graphics2D) g1; g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); @@ -142,6 +172,9 @@ public class TimelineBodyPanel extends JPanel implements MouseListener, KeyListe g.setColor(getBackgroundColor()); g.fillRect(0, 0, getWidth(), getHeight()); + if (timeline == null) { + return; + } Rectangle clip = g.getClipBounds(); int frameWidth = FRAME_WIDTH; int frameHeight = FRAME_HEIGHT; @@ -180,22 +213,22 @@ public class TimelineBodyPanel extends JPanel implements MouseListener, KeyListe } int awidth = g.getFontMetrics().stringWidth("a"); - boolean firstAction = true; + boolean firstAction = true; for (int f = start_f; f <= end_f || (firstAction && f <= max_f); f++) { if (!timeline.getFrame(f).actions.isEmpty()) { if (firstAction) { drawBlock(g, getEmptyFrameColor(), 0, 0, f, BlockType.EMPTY); - } - + } + int f2 = f + 1; - while(f2 <= max_f && timeline.getFrame(f2).actions.isEmpty()) { + while (f2 <= max_f && timeline.getFrame(f2).actions.isEmpty()) { f2++; } drawBlock(g, getEmptyFrameColor(), 0, f, f2 - f, BlockType.EMPTY); g.setColor(A_COLOR); - g.setFont(getFont().deriveFont(FONT_SIZE)); + g.setFont(getFont().deriveFont(FONT_SIZE)); g.drawString("a", f * frameWidth + frameWidth / 2 - awidth / 2, frameHeight / 2); - firstAction = false; + firstAction = false; } } @@ -337,7 +370,7 @@ public class TimelineBodyPanel extends JPanel implements MouseListener, KeyListe public void depthSelect(int depth) { frameSelect(frame, depth); } - + public void frameSelect(int frame, int depth) { if (cursor != null && cursor.x == frame && (cursor.y == depth || depth == -1)) { return; @@ -346,14 +379,19 @@ public class TimelineBodyPanel extends JPanel implements MouseListener, KeyListe depth = cursor.y; } cursor = new Point(frame, depth); - for (FrameSelectionListener l : listeners) { - l.frameSelected(frame, depth); - } + fireFrameSelected(frame, depth); repaint(); this.frame = frame; + this.depth = depth; scrollRectToVisible(getFrameBounds(frame, depth)); } + private void fireFrameSelected(int frame, int depth) { + for (FrameSelectionListener l : selectionListeners) { + l.frameSelected(frame, depth); + } + } + @Override public void mousePressed(MouseEvent e) { Point p = e.getPoint(); @@ -368,6 +406,342 @@ public class TimelineBodyPanel extends JPanel implements MouseListener, KeyListe } frameSelect(p.x, p.y); requestFocusInWindow(); + if (SwingUtilities.isRightMouseButton(e)) { + + JPopupMenu popupMenu = new JPopupMenu(); + + DepthState ds = timeline.getFrame(frame).layers.get(depth); + boolean thisEmpty = ds == null || ds.getCharacter() == null; + boolean previousEmpty = true; + boolean emptyDepth = true; + boolean somethingBefore = false; + boolean somethingAfter = false; + if (frame > 0) { + ds = timeline.getFrame(frame - 1).layers.get(depth); + previousEmpty = ds == null || ds.getCharacter() == null; + } + for (int f = frame - 1; f >= 0; f--) { + ds = timeline.getFrame(f).layers.get(depth); + boolean empty = ds == null || ds.getCharacter() == null; + if (!empty) { + somethingBefore = true; + break; + } + } + for (int f = frame + 1; f < timeline.getFrameCount(); f++) { + ds = timeline.getFrame(f).layers.get(depth); + boolean empty = ds == null || ds.getCharacter() == null; + if (!empty) { + somethingAfter = true; + break; + } + } + emptyDepth = thisEmpty && !somethingBefore && !somethingAfter; + + JMenuItem addKeyFrameMenuItem = new JMenuItem("Add keyframe"); + addKeyFrameMenuItem.addActionListener(this::addKeyFrame); + JMenuItem addKeyFrameEmptyBeforeMenuItem = new JMenuItem("Add keyframe with blank frames before"); + addKeyFrameEmptyBeforeMenuItem.addActionListener(this::addKeyFrameEmptyBefore); + JMenuItem addFrameMenuItem = new JMenuItem("Add frame"); + addFrameMenuItem.addActionListener(this::addFrame); + + if (!thisEmpty) { + popupMenu.add(addKeyFrameMenuItem); + } + + if (thisEmpty && previousEmpty && somethingBefore && !somethingAfter) { + popupMenu.add(addKeyFrameEmptyBeforeMenuItem); + } + if (!emptyDepth) { + popupMenu.add(addFrameMenuItem); + } + + if (popupMenu.getComponentCount() > 0) { + popupMenu.show(this, e.getX(), e.getY()); + } + } + } + + private void addKeyFrame(ActionEvent e) { + if (timeline.getFrame(frame).layers.get(depth).key) { + return; + } + undoManager.doOperation(new TimelinedTagListDoableOperation(timeline.timelined) { + + @Override + public void doOperation() { + super.doOperation(); + Timelined timelined = timeline.timelined; + DepthState ds = timeline.getFrame(frame).layers.get(depth); + /*RemoveTag rm = new RemoveObject2Tag(timelined.getSwf()); + rm.setDepth(depth); + rm.setTimelined(timelined);*/ + PlaceObjectTypeTag place; + try { + place = (PlaceObjectTypeTag) ds.placeObjectTag.cloneTag(); + } catch (InterruptedException | IOException ex) { + //should not happen + return; + } + place.setTimelined(timelined); + place.setPlaceFlagMove(true); + ShowFrameTag sf = timeline.getFrame(frame).showFrameTag; + int pos; + if (sf != null) { + pos = timelined.indexOfTag(sf); + } else { + pos = timelined.getTags().size(); + } + //timelined.addTag(pos++, rm); + timelined.addTag(pos++, place); + + timelined.resetTimeline(); + timeline = timelined.getTimeline(); + + refresh(); + fireChanged(); + repaint(); + } + + @Override + public void undoOperation() { + super.undoOperation(); + timeline = timeline.timelined.getTimeline(); + refresh(); + fireChanged(); + repaint(); + } + + @Override + public String getDescription() { + return "Add key frame"; + } + }); + } + + private void addKeyFrameEmptyBefore(ActionEvent e) { + undoManager.doOperation(new TimelinedTagListDoableOperation(timeline.timelined) { + + @Override + public void doOperation() { + super.doOperation(); + Timelined timelined = timeline.timelined; + DepthState ds = null; + for (int f = frame - 1; f >= 0; f--) { + ds = timeline.getFrame(f).layers.get(depth); + if (ds != null && ds.getCharacter() != null) { + break; + } + } + + PlaceObjectTypeTag place; + try { + place = (PlaceObjectTypeTag) ds.placeObjectTag.cloneTag(); + } catch (InterruptedException | IOException ex) { + //should not happen + return; + } + place.setTimelined(timelined); + place.setPlaceFlagMove(false); + ShowFrameTag sf = timeline.getFrame(frame).showFrameTag; + int pos; + if (sf != null) { + pos = timelined.indexOfTag(sf); + + if (frame < timelined.getFrameCount() - 1) { + RemoveTag rm = new RemoveObject2Tag(timelined.getSwf()); + rm.setTimelined(timelined); + rm.setDepth(depth); + timelined.addTag(pos + 1, rm); + } + } else { + pos = timelined.getTags().size(); + } + timelined.addTag(pos++, place); + + + timelined.resetTimeline(); + timeline = timelined.getTimeline(); + + refresh(); + fireChanged(); + repaint(); + } + + @Override + public void undoOperation() { + super.undoOperation(); + timeline = timeline.timelined.getTimeline(); + refresh(); + fireChanged(); + repaint(); + } + + @Override + public String getDescription() { + return "Add key frame"; + } + }); + } + + private void addFrame(ActionEvent e) { + final int fframe = frame; + final int fdepth = depth; + + undoManager.doOperation(new TimelinedTagListDoableOperation(timeline.timelined) { + @Override + public void doOperation() { + super.doOperation(); + DepthState ds; + + boolean somethingAfter = false; + for (int f = fframe + 1; f < timeline.getFrameCount(); f++) { + ds = timeline.getFrame(f).layers.get(fdepth); + boolean empty = ds == null || ds.getCharacter() == null; + if (!empty) { + somethingAfter = true; + break; + } + } + + Timelined timelined = timeline.timelined; + for (int f = fframe; f >= 0; f--) { + ds = timeline.getFrame(f).layers.get(fdepth); + boolean empty = ds == null || ds.getCharacter() == null; + if (!empty || somethingAfter) { + int moveFrameCount = fframe - f; + if (moveFrameCount == 0) { + moveFrameCount = 1; + } + boolean frameAdded = false; + for (int mf = 0; mf < moveFrameCount; mf++) { + int pos = timelined.indexOfTag(timeline.getFrame(f).showFrameTag); + ReadOnlyTagList tags = timelined.getTags(); + List lastFrameDepthTags = new ArrayList<>(); + int f2 = f + 1; + boolean endsWithRemove = false; + for (int i = pos + 1; i < tags.size(); i++) { + Tag t = tags.get(i); + if (t instanceof PlaceObjectTypeTag) { + PlaceObjectTypeTag po = (PlaceObjectTypeTag) t; + if (po.getDepth() == fdepth) { + lastFrameDepthTags.add(po); + timelined.removeTag(po); + endsWithRemove = false; + i--; + } + } + if (t instanceof RemoveTag) { + RemoveTag ro = (RemoveTag) t; + if (ro.getDepth() == fdepth) { + lastFrameDepthTags.add(ro); + timelined.removeTag(ro); + endsWithRemove = true; + i--; + } + } + if ((t instanceof ShowFrameTag) || (i == tags.size() - 1)) { + if (!(t instanceof ShowFrameTag) && !lastFrameDepthTags.isEmpty()) { + ShowFrameTag sf = new ShowFrameTag(timelined.getSwf()); + sf.setTimelined(timelined); + timelined.addTag(sf); + i++; + } + + boolean removeOnly = true; + for (Tag lt : lastFrameDepthTags) { + if (!(lt instanceof RemoveTag)) { + removeOnly = false; + break; + } + } + + boolean onLastFrame = f2 == timelined.getFrameCount() - 1; + + if (!(removeOnly && onLastFrame)) { + for (Tag lt : lastFrameDepthTags) { + i++; + timelined.addTag(i, lt); + } + } + + //Add beyond current total frameCount + if (onLastFrame) { + if ((!removeOnly && !lastFrameDepthTags.isEmpty()) || !endsWithRemove) { + //Add removeTag to other layers + for (int d = 1; d <= timeline.maxDepth; d++) { + if (d == fdepth) { + continue; + } + ds = timeline.getFrame(f2).layers.get(d); + if (ds != null && ds.getCharacter() != null) { + RemoveTag rt = new RemoveObject2Tag(timelined.getSwf()); + rt.setTimelined(timelined); + rt.setDepth(d); + timelined.addTag(i, rt); + i++; + } + } + + frameAdded = true; + ShowFrameTag sf = new ShowFrameTag(timelined.getSwf()); + sf.setTimelined(timelined); + timelined.addTag(sf); + i++; + timelined.setFrameCount(timelined.getFrameCount() + 1); + } + } + lastFrameDepthTags.clear(); + f2++; + } + } + } + if (!frameAdded && fframe == timelined.getFrameCount() - 1) { + //Add removeTag to other layers + for (int d = 1; d <= timeline.maxDepth; d++) { + if (d == fdepth) { + continue; + } + ds = timeline.getFrame(fframe).layers.get(d); + if (ds != null && ds.getCharacter() != null) { + RemoveTag rt = new RemoveObject2Tag(timelined.getSwf()); + rt.setTimelined(timelined); + rt.setDepth(d); + timelined.addTag(rt); + } + } + for (int mf = 0; mf < moveFrameCount; mf++) { + ShowFrameTag sf = new ShowFrameTag(timelined.getSwf()); + sf.setTimelined(timelined); + timelined.addTag(sf); + } + } + break; + } + } + + timelined.resetTimeline(); + timelined.setFrameCount(timelined.getTimeline().getFrameCount()); + timeline = timelined.getTimeline(); + refresh(); + fireChanged(); + repaint(); + } + + @Override + public void undoOperation() { + super.undoOperation(); + timeline = timeline.timelined.getTimeline(); + refresh(); + fireChanged(); + repaint(); + } + + @Override + public String getDescription() { + return "Add frame"; + } + }); } @Override @@ -418,10 +792,11 @@ public class TimelineBodyPanel extends JPanel implements MouseListener, KeyListe @Override public void keyReleased(KeyEvent e) { } - + public Rectangle getDepthBounds(int depth) { return getFrameBounds(frame, depth); } + public Rectangle getFrameBounds(int frame, int depth) { Rectangle rect = new Rectangle(); rect.width = FRAME_WIDTH; @@ -430,11 +805,17 @@ public class TimelineBodyPanel extends JPanel implements MouseListener, KeyListe rect.y = depth * FRAME_HEIGHT; return rect; } - + public void refresh() { - Dimension dim = new Dimension(FRAME_WIDTH * timeline.getFrameCount() + 1, FRAME_HEIGHT * (timeline.getMaxDepth() + 1)); + int frameCount = timeline == null ? 0 : timeline.getFrameCount(); + int maxDepth = timeline == null ? 0 : timeline.getMaxDepth(); + Dimension dim = new Dimension(FRAME_WIDTH * frameCount + 1, FRAME_HEIGHT * (maxDepth + 1)); setSize(dim); setPreferredSize(dim); } -} + public void setTimeline(Timeline timeline) { + this.timeline = timeline; + refresh(); + } +} diff --git a/src/com/jpexs/decompiler/flash/easygui/TimelineDepthPanel.java b/src/com/jpexs/decompiler/flash/easygui/TimelineDepthPanel.java index 2dca55bca..93da2593e 100644 --- a/src/com/jpexs/decompiler/flash/easygui/TimelineDepthPanel.java +++ b/src/com/jpexs/decompiler/flash/easygui/TimelineDepthPanel.java @@ -28,7 +28,7 @@ import javax.swing.JPanel; */ public class TimelineDepthPanel extends JPanel { - private final int maxDepth; + private int maxDepth; public static final int PADDING = 5; @@ -40,8 +40,12 @@ public class TimelineDepthPanel extends JPanel { public static final Color FONT_COLOR = Color.black; - public TimelineDepthPanel(Timeline timeline) { - maxDepth = timeline.getMaxDepth(); + public TimelineDepthPanel() { + setTimeline(null); + } + + public void setTimeline(Timeline timeline) { + maxDepth = timeline == null ? 0 : timeline.getMaxDepth(); String maxDepthStr = Integer.toString(maxDepth); setFont(getFont().deriveFont(FONT_SIZE)); int maxDepthW = getFontMetrics(getFont()).stringWidth(maxDepthStr); diff --git a/src/com/jpexs/decompiler/flash/easygui/TimelinePanel.java b/src/com/jpexs/decompiler/flash/easygui/TimelinePanel.java index c0d1a32e1..14745ab2a 100644 --- a/src/com/jpexs/decompiler/flash/easygui/TimelinePanel.java +++ b/src/com/jpexs/decompiler/flash/easygui/TimelinePanel.java @@ -41,55 +41,30 @@ public class TimelinePanel extends JPanel { private Timeline timeline; + private Timelined timelined; + public static final int FRAME_WIDTH = 8; public static final int FRAME_HEIGHT = 18; private JScrollPane timelineBodyScrollPane; - - //public static final Color backgroundColor = new Color(0xd9, 0xe7, 0xfa); - public static Color getBackgroundColor() { - return SystemColor.control; - } - public Timeline getTimeline() { - return timeline; - } - - public void addFrameSelectionListener(FrameSelectionListener l) { - timelineBodyPanel.addFrameSelectionListener(l); - } - - public void removeFrameSelectionListener(FrameSelectionListener l) { - timelineBodyPanel.removeFrameSelectionListener(l); - } - - public void setDepth(int depth) { - timelineBodyPanel.depthSelect(depth); - } - - public void setFrame(int frame, int depth) { - timelineBodyPanel.frameSelect(frame, depth); - } - - public void refresh() { - timelineBodyPanel.refresh(); - } - - public void setTimelined(Timelined timelined) { - this.removeAll(); - if (timelined == null) { - this.revalidate(); - return; - } - timeline = timelined.getTimeline(); - timelineBodyPanel = new TimelineBodyPanel(timeline); + public TimelinePanel(UndoManager undoManager) { + timelineBodyPanel = new TimelineBodyPanel(undoManager); setLayout(new BorderLayout()); timelineBodyScrollPane = new FasterScrollPane(timelineBodyPanel); - depthPanel = new TimelineDepthPanel(timeline); + depthPanel = new TimelineDepthPanel(); + + timelineBodyPanel.addChangeListener(new Runnable() { + @Override + public void run() { + timeline = timelined.getTimeline(); + depthPanel.setTimeline(timeline); + } + }); timePanel = new TimelineTimePanel(); @@ -139,6 +114,50 @@ public class TimelinePanel extends JPanel { ftimeline.frameSelect(frame, depth); } }); - this.revalidate(); + } + + + + //public static final Color backgroundColor = new Color(0xd9, 0xe7, 0xfa); + public static Color getBackgroundColor() { + return SystemColor.control; + } + + public Timeline getTimeline() { + return timeline; + } + + public void addFrameSelectionListener(FrameSelectionListener l) { + timelineBodyPanel.addFrameSelectionListener(l); + } + + public void removeFrameSelectionListener(FrameSelectionListener l) { + timelineBodyPanel.removeFrameSelectionListener(l); + } + + public void setDepth(int depth) { + timelineBodyPanel.depthSelect(depth); + } + + public void setFrame(int frame, int depth) { + timelineBodyPanel.frameSelect(frame, depth); + } + + public void refresh() { + timelineBodyPanel.refresh(); + } + + public void addChangeListener(Runnable l) { + timelineBodyPanel.addChangeListener(l); + } + + public void removeChangeListener(Runnable l) { + timelineBodyPanel.removeChangeListener(l); + } + + public void setTimelined(Timelined timelined) { + this.timelined = timelined; + timelineBodyPanel.setTimeline(timelined.getTimeline()); + depthPanel.setTimeline(timelined.getTimeline()); } } diff --git a/src/com/jpexs/decompiler/flash/easygui/TimelinedTagListDoableOperation.java b/src/com/jpexs/decompiler/flash/easygui/TimelinedTagListDoableOperation.java new file mode 100644 index 000000000..fe70e7b2d --- /dev/null +++ b/src/com/jpexs/decompiler/flash/easygui/TimelinedTagListDoableOperation.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2024 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.easygui; + +import com.jpexs.decompiler.flash.ReadOnlyTagList; +import com.jpexs.decompiler.flash.tags.Tag; +import com.jpexs.decompiler.flash.timeline.Timelined; +import java.util.List; + +/** + * + * @author JPEXS + */ +public abstract class TimelinedTagListDoableOperation implements DoableOperation { + + private final Timelined timelined; + protected List tags; + + public TimelinedTagListDoableOperation(Timelined timelined) { + this.timelined = timelined; + } + + @Override + public void doOperation() { + saveTagList(); + } + + protected void saveTagList() { + tags = timelined.getTags().toArrayList(); + } + + protected void restoreTagList() { + if (tags != null) { + ReadOnlyTagList newTags = timelined.getTags(); + int size = newTags.size(); + for (int i = 0; i < size; i++) { + timelined.removeTag(0); + } + for (int i = 0; i < tags.size(); i++) { + timelined.addTag(tags.get(i)); + } + timelined.resetTimeline(); + timelined.setFrameCount(timelined.getTimeline().getFrameCount()); + } + } + + @Override + public void undoOperation() { + restoreTagList(); + } + + @Override + public abstract String getDescription(); + +}