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) {