diff --git a/src/com/jpexs/decompiler/flash/easygui/DoableOperation.java b/src/com/jpexs/decompiler/flash/easygui/DoableOperation.java
new file mode 100644
index 000000000..dbefa8b96
--- /dev/null
+++ b/src/com/jpexs/decompiler/flash/easygui/DoableOperation.java
@@ -0,0 +1,29 @@
+/*
+ * 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;
+
+/**
+ *
+ * @author JPEXS
+ */
+public interface DoableOperation {
+ public void doOperation();
+
+ public void undoOperation();
+
+ public String getDescription();
+}
diff --git a/src/com/jpexs/decompiler/flash/easygui/MainFrame.java b/src/com/jpexs/decompiler/flash/easygui/MainFrame.java
index a1d056367..75e8b5611 100644
--- a/src/com/jpexs/decompiler/flash/easygui/MainFrame.java
+++ b/src/com/jpexs/decompiler/flash/easygui/MainFrame.java
@@ -17,19 +17,29 @@
package com.jpexs.decompiler.flash.easygui;
import com.jpexs.decompiler.flash.SWF;
+import com.jpexs.decompiler.flash.exporters.commonshape.Matrix;
+import com.jpexs.decompiler.flash.gui.BoundsChangeListener;
import com.jpexs.decompiler.flash.gui.ImagePanel;
+import com.jpexs.decompiler.flash.gui.RegistrationPointPosition;
import com.jpexs.decompiler.flash.gui.TimelinedMaker;
import com.jpexs.decompiler.flash.tags.Tag;
import com.jpexs.decompiler.flash.tags.base.PlaceObjectTypeTag;
+import com.jpexs.decompiler.flash.timeline.DepthState;
+import com.jpexs.decompiler.flash.types.MATRIX;
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import javax.swing.BorderFactory;
+import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
@@ -52,7 +62,10 @@ public class MainFrame extends JFrame {
private JSplitPane horizontalSplitPane;
private ImagePanel libraryPreviewPanel;
private ImagePanel stagePanel;
- private TimelinePanel timelinePanel;
+ private TimelinePanel timelinePanel;
+ private JButton undoButton;
+ private JButton redoButton;
+ private UndoManager undoManager;
public MainFrame() {
setTitle("JPEXS FFDec Easy GUI");
@@ -75,8 +88,101 @@ public class MainFrame extends JFrame {
}
}
});
+
+ stagePanel.addTransformChangeListener(new Runnable() {
+ @Override
+ public void run() {
+ final int depth = stagePanel.getSelectedDepth();
+ final int frame = stagePanel.getFrame();
+ MATRIX m = stagePanel.getNewMatrix().toMATRIX();
+ undoManager.doOperation(new DoableOperation() {
+
+ private MATRIX previousMatrix;
+ private MATRIX newMatrix = m;
+
+ @Override
+ public void doOperation() {
+ timelinePanel.setFrame(frame, depth);
+ DepthState ds = stagePanel.getTimelined().getTimeline().getFrame(frame).layers.get(depth);
+ PlaceObjectTypeTag pl = ds.placeObjectTag;
+ previousMatrix = ds.matrix;
+ ds.setMATRIX(newMatrix);
+ stagePanel.repaint();
+ }
+
+ @Override
+ public void undoOperation() {
+ timelinePanel.setFrame(frame, depth);
+ DepthState ds = stagePanel.getTimelined().getTimeline().getFrame(frame).layers.get(depth);
+ ds.setMATRIX(previousMatrix);
+ stagePanel.repaint();
+ }
+
+ @Override
+ public String getDescription() {
+ return "Move";
+ }
+ });
+
+ }
+
+ });
+
+ undoManager = new UndoManager();
+
+ JPanel topPanel = new JPanel(new BorderLayout());
+
+ JPanel toolbarPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 0, 0));
+
+ undoButton = new JButton(View.getIcon("rotateanticlockwise16"));
+ undoButton.setToolTipText("Undo");
+ undoButton.setMargin(new Insets(5, 5, 5, 5));
+ undoButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ undoManager.undo();
+ }
+ });
+
+ redoButton = new JButton(View.getIcon("rotateclockwise16"));
+ redoButton.setToolTipText("Redo");
+ redoButton.setMargin(new Insets(5, 5, 5, 5));
+ redoButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ undoManager.redo();
+ }
+ });
+
+ Runnable undoChangeListener = new Runnable() {
+ @Override
+ public void run() {
+ undoButton.setEnabled(undoManager.canUndo());
+ redoButton.setEnabled(undoManager.canRedo());
+ if (undoManager.canUndo()) {
+ undoButton.setToolTipText("Undo " + undoManager.getUndoName());
+ } else {
+ undoButton.setToolTipText("Cannot undo");
+ }
+ if (undoManager.canRedo()) {
+ redoButton.setToolTipText("Redo " + undoManager.getRedoName());
+ } else {
+ redoButton.setToolTipText("Cannot redo");
+ }
+ }
+ };
+
+ undoManager.addChangeListener(undoChangeListener);
+ undoChangeListener.run();
+
+ toolbarPanel.add(undoButton);
+ toolbarPanel.add(redoButton);
+
+ topPanel.add(toolbarPanel, BorderLayout.NORTH);
+ topPanel.add(stagePanel, BorderLayout.CENTER);
+
timelinePanel = new TimelinePanel();
- verticalSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, stagePanel, timelinePanel);
+ verticalSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, topPanel, timelinePanel);
libraryTreeTable = new LibraryTreeTable();
JScrollPane libraryScrollPane = new JScrollPane(libraryTreeTable);
@@ -146,7 +252,5 @@ public class MainFrame extends JFrame {
super.setVisible(b);
verticalSplitPane.setDividerLocation(0.7);
horizontalSplitPane.setDividerLocation(0.7);
- }
-
-
+ }
}
diff --git a/src/com/jpexs/decompiler/flash/easygui/TimelinePanel.java b/src/com/jpexs/decompiler/flash/easygui/TimelinePanel.java
index f300ab3d7..1b2d56890 100644
--- a/src/com/jpexs/decompiler/flash/easygui/TimelinePanel.java
+++ b/src/com/jpexs/decompiler/flash/easygui/TimelinePanel.java
@@ -69,6 +69,10 @@ public class TimelinePanel extends JPanel {
timelineBodyPanel.depthSelect(depth);
}
+ public void setFrame(int frame, int depth) {
+ timelineBodyPanel.frameSelect(frame, depth);
+ }
+
public void setTimelined(Timelined timelined) {
this.removeAll();
if (timelined == null) {
diff --git a/src/com/jpexs/decompiler/flash/easygui/UndoManager.java b/src/com/jpexs/decompiler/flash/easygui/UndoManager.java
new file mode 100644
index 000000000..b28729b08
--- /dev/null
+++ b/src/com/jpexs/decompiler/flash/easygui/UndoManager.java
@@ -0,0 +1,97 @@
+/*
+ * 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 java.util.ArrayList;
+import java.util.List;
+
+/**
+ *
+ * @author JPEXS
+ */
+public class UndoManager {
+ private int historyPos = 0;
+ private final List history = new ArrayList<>();
+
+ private final List changeListeners = new ArrayList<>();
+
+ public void addChangeListener(Runnable listener) {
+ changeListeners.add(listener);
+ }
+
+ public void removeChangeListener(Runnable listener) {
+ changeListeners.remove(listener);
+ }
+
+ private void fireChange() {
+ for (Runnable listener : changeListeners) {
+ listener.run();
+ }
+ }
+
+ public void doOperation(DoableOperation doableOperation) {
+ //drop redos
+ while(history.size() > historyPos) {
+ history.remove(historyPos);
+ }
+
+ history.add(doableOperation);
+ historyPos++;
+ doableOperation.doOperation();
+ fireChange();
+ }
+
+ public boolean canUndo() {
+ return historyPos > 0;
+ }
+
+ public String getUndoName() {
+ if (!canUndo()) {
+ return null;
+ }
+ return history.get(historyPos - 1).getDescription();
+ }
+
+ public void undo() {
+ if (historyPos == 0) {
+ return;
+ }
+ historyPos--;
+ history.get(historyPos).undoOperation();
+ fireChange();
+ }
+
+ public void redo() {
+ if (!canRedo()) {
+ return;
+ }
+ history.get(historyPos).doOperation();
+ historyPos++;
+ fireChange();
+ }
+
+ public String getRedoName() {
+ if (!canRedo()) {
+ return null;
+ }
+ return history.get(historyPos).getDescription();
+ }
+
+ public boolean canRedo() {
+ return history.size() > historyPos;
+ }
+}
diff --git a/src/com/jpexs/decompiler/flash/gui/ImagePanel.java b/src/com/jpexs/decompiler/flash/gui/ImagePanel.java
index ff96a726f..59bb8b3d2 100644
--- a/src/com/jpexs/decompiler/flash/gui/ImagePanel.java
+++ b/src/com/jpexs/decompiler/flash/gui/ImagePanel.java
@@ -356,7 +356,17 @@ public final class ImagePanel extends JPanel implements MediaDisplay {
private List boundsChangeListeners = new ArrayList<>();
private List pointUpdateListeners = new ArrayList<>();
+
+ private List transformChangeListeners = new ArrayList<>();
+ public void addTransformChangeListener(Runnable listener) {
+ transformChangeListeners.add(listener);
+ }
+
+ public void removeTransformChangeListener(Runnable listener) {
+ transformChangeListeners.remove(listener);
+ }
+
public void addPointUpdateListener(PointUpdateListener listener) {
pointUpdateListeners.add(listener);
}
@@ -370,6 +380,12 @@ public final class ImagePanel extends JPanel implements MediaDisplay {
listener.pointsUpdated(points);
}
}
+
+ private void fireTransformChanged() {
+ for (Runnable listener : transformChangeListeners) {
+ listener.run();
+ }
+ }
private void fireStatusChanged(String status) {
for (MediaDisplayListener listener : listeners) {
@@ -578,30 +594,7 @@ public final class ImagePanel extends JPanel implements MediaDisplay {
}
public Matrix getNewMatrix() {
- synchronized (lock) {
- DepthState ds = null;
- Timeline timeline = timelined.getTimeline();
- if (freeTransformDepth > -1 && timeline.getFrameCount() > frame) {
- ds = timeline.getFrame(frame).layers.get(freeTransformDepth);
- }
- if (freeTransformDepth == -1 && selectionMode && selectedDepth != -1 && timeline.getFrameCount() > frame) {
- ds = timeline.getFrame(frame).layers.get(selectedDepth);
- }
-
- if (ds != null) {
- CharacterTag cht = ds.getCharacter();
- if (cht != null) {
- if (cht instanceof DrawableTag) {
- double zoomDouble = zoom.fit ? getZoomToFit() : zoom.value;
- if (lowQuality) {
- zoomDouble /= LQ_FACTOR;
- }
- return transform;
- }
- }
- }
- return null;
- }
+ return transform;
}
public synchronized void selectDepth(int depth) {
@@ -617,6 +610,14 @@ public final class ImagePanel extends JPanel implements MediaDisplay {
redraw();
}
+ public int getSelectedDepth() {
+ return selectedDepth;
+ }
+
+ public synchronized int getFrame() {
+ return frame;
+ }
+
private void calculateFreeOrSelectionTransform() {
DepthState ds = null;
Timeline timeline = timelined.getTimeline();
@@ -1246,15 +1247,9 @@ public final class ImagePanel extends JPanel implements MediaDisplay {
}
}
- if (selectionMode) {
- DepthState ds = timelined.getTimeline().getFrame(frame).layers.get(selectedDepth);
- PlaceObjectTypeTag pl = ds.placeObjectTag;
- MATRIX m = transform.toMATRIX();
- ds.setMATRIX(m);
- }
-
calcRect(); //do not put this inside synchronized block, it cause deadlock
fireBoundsChange(getTransformBounds(), registrationPoint, registrationPointPosition);
+ fireTransformChanged();
repaint();
}
if (selectionMode) {