diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/configuration/Configuration.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/configuration/Configuration.java index 608a85163..145ec03cb 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/configuration/Configuration.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/configuration/Configuration.java @@ -839,6 +839,11 @@ public final class Configuration { @ConfigurationCategory("script") public static ConfigurationItem deobfuscateAs12RemoveInvalidNamesAssignments = null; + @ConfigurationDefaultDouble(0.6) + @ConfigurationName("gui.splitPanePlace.dividerLocationPercent") + @ConfigurationInternal + public static ConfigurationItem guiSplitPanePlaceDividerLocationPercent = null; + private enum OSId { WINDOWS, OSX, UNIX } diff --git a/src/com/jpexs/decompiler/flash/gui/BoundsChangeListener.java b/src/com/jpexs/decompiler/flash/gui/BoundsChangeListener.java new file mode 100644 index 000000000..4022a192b --- /dev/null +++ b/src/com/jpexs/decompiler/flash/gui/BoundsChangeListener.java @@ -0,0 +1,27 @@ +/* + * 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 java.awt.geom.Rectangle2D; + +/** + * + * @author JPEXS + */ +public interface BoundsChangeListener { + public void boundsChanged(Rectangle2D newBounds); +} diff --git a/src/com/jpexs/decompiler/flash/gui/ImagePanel.java b/src/com/jpexs/decompiler/flash/gui/ImagePanel.java index 5dbb46e8e..2f2048963 100644 --- a/src/com/jpexs/decompiler/flash/gui/ImagePanel.java +++ b/src/com/jpexs/decompiler/flash/gui/ImagePanel.java @@ -253,7 +253,30 @@ public final class ImagePanel extends JPanel implements MediaDisplay { } private SerializableImage imgPlay = null; + + private List boundsChangeListeners = new ArrayList<>(); + + public void addBoundsChangeListener(BoundsChangeListener listener) { + boundsChangeListeners.add(listener); + } + + public void removeBoundsChangeListener(BoundsChangeListener listener) { + boundsChangeListeners.remove(listener); + } + + private void fireBoundsChange(Rectangle2D bounds) { + for (BoundsChangeListener listener:boundsChangeListeners) { + listener.boundsChanged(bounds); + } + } + + public Rectangle2D getTransformBounds() { + return bounds; + } + + + private SerializableImage getImagePlay() { if (imgPlay != null) { return imgPlay; @@ -407,7 +430,8 @@ public final class ImagePanel extends JPanel implements MediaDisplay { } registrationPoint = null; calculateFreeTransform(); - hideMouseSelection(); + hideMouseSelection(); + redraw(); } public void fireMediaDisplayStateChanged() { @@ -534,7 +558,7 @@ public final class ImagePanel extends JPanel implements MediaDisplay { int y = rect.y < 0 ? 0 : rect.y; if (freeTransformDepth > -1) { - RECT timRect = timelined.getRectWithStrokes(); + RECT timRect = timelined.getRect(); double zoomDouble = zoom.fit ? getZoomToFit() : zoom.value; int offsetX = (int) (SWF.unitDivisor * iconPanel.getWidth() / zoomDouble / 2 - timRect.getWidth() / 2); int offsetY = (int) (SWF.unitDivisor * iconPanel.getHeight() / zoomDouble / 2 - timRect.getHeight() / 2); @@ -561,7 +585,7 @@ public final class ImagePanel extends JPanel implements MediaDisplay { int axisX = 0; int axisY = 0; double zoomDouble = zoom.fit ? getZoomToFit() : zoom.value; - RECT timRect = timelined.getRectWithStrokes(); + RECT timRect = timelined.getRect(); axisX = rect.x - (int) (timRect.Xmin * zoomDouble / SWF.unitDivisor); axisY = rect.y - (int) (timRect.Ymin * zoomDouble / SWF.unitDivisor); g2.setComposite(BlendComposite.Invert); @@ -1360,8 +1384,8 @@ public final class ImagePanel extends JPanel implements MediaDisplay { w1 = (int) (_img.getWidth() * (lowQuality ? LQ_FACTOR : 1)); h1 = (int) (_img.getHeight() * (lowQuality ? LQ_FACTOR : 1)); } else { - w1 = (int) (timelined.getRectWithStrokes().getWidth() * zoomDouble / SWF.unitDivisor); - h1 = (int) (timelined.getRectWithStrokes().getHeight() * zoomDouble / SWF.unitDivisor); + w1 = (int) (timelined.getRect().getWidth() * zoomDouble / SWF.unitDivisor); + h1 = (int) (timelined.getRect().getHeight() * zoomDouble / SWF.unitDivisor); } //HERE @@ -1718,7 +1742,7 @@ public final class ImagePanel extends JPanel implements MediaDisplay { @Override public synchronized double getZoomToFit() { if (timelined != null) { - RECT bounds = timelined.getRectWithStrokes(); + RECT bounds = timelined.getRect(); double w1 = bounds.getWidth() / SWF.unitDivisor; double h1 = bounds.getHeight() / SWF.unitDivisor; @@ -1812,6 +1836,7 @@ public final class ImagePanel extends JPanel implements MediaDisplay { frame = ButtonTag.FRAME_UP; } + bounds = null; displayObjectCache.clear(); this.timelined = drawable; this.swf = swf; @@ -2266,7 +2291,7 @@ public final class ImagePanel extends JPanel implements MediaDisplay { aRect = new ExportRectangle(_rect); ExportRectangle viewRect = new ExportRectangle(new RECT()); - RECT timRect = timelined.getRectWithStrokes(); + RECT timRect = timelined.getRect(); double w = timRect.getWidth() * zoomDouble / SWF.unitDivisor; @@ -2417,7 +2442,7 @@ public final class ImagePanel extends JPanel implements MediaDisplay { synchronized (lock) { Reference boundsRef = new Reference<>(null); - RECT rect = timelined.getRectWithStrokes(); + RECT rect = timelined.getRect(); _viewRect = getViewRect(); @@ -2425,7 +2450,7 @@ public final class ImagePanel extends JPanel implements MediaDisplay { AffineTransform tempTrans2 = transformUpdated == null ? null : new AffineTransform(transformUpdated); //HERE - RECT timRect = timelined.getRectWithStrokes(); + RECT timRect = timelined.getRect(); int offsetX = (int) (SWF.unitDivisor * iconPanel.getWidth() / zoomDouble / 2 - timRect.getWidth() / 2); int offsetY = (int) (SWF.unitDivisor * iconPanel.getHeight() / zoomDouble / 2 - timRect.getHeight() / 2); offsetX *= zoomDouble; @@ -2506,6 +2531,10 @@ public final class ImagePanel extends JPanel implements MediaDisplay { } if (newBounds != null) { bounds = newBounds; + + RECT timRectNoStrokes = timelined.getRect(); + Rectangle2D bounds1 = new Rectangle2D.Double((bounds.getX() - _rect.x)/zoomDouble + timRectNoStrokes.Xmin/SWF.unitDivisor, (bounds.getY() - _rect.y)/zoomDouble + timRectNoStrokes.Ymin/SWF.unitDivisor ,bounds.getWidth() / zoomDouble, bounds.getHeight() / zoomDouble); + fireBoundsChange(bounds1); } } } diff --git a/src/com/jpexs/decompiler/flash/gui/PreviewPanel.java b/src/com/jpexs/decompiler/flash/gui/PreviewPanel.java index eb18a3d0c..8fd40c44b 100644 --- a/src/com/jpexs/decompiler/flash/gui/PreviewPanel.java +++ b/src/com/jpexs/decompiler/flash/gui/PreviewPanel.java @@ -191,7 +191,7 @@ public class PreviewPanel extends JPersistentSplitPane implements TagEditorPanel private JButton genericCancelButton; - private JButton placeFreeTransformButton; + private JButton placeTransformButton; private JButton placeEditButton; @@ -221,7 +221,7 @@ public class PreviewPanel extends JPersistentSplitPane implements TagEditorPanel private HexView unknownHexView; - private final int PLACE_EDIT_FREETRANSFORM = 1; + private final int PLACE_EDIT_TRANSFORM = 1; private final int PLACE_EDIT_RAW = 2; private int placeEditMode = 0; @@ -234,11 +234,15 @@ public class PreviewPanel extends JPersistentSplitPane implements TagEditorPanel private JLabel buildValueLabel = new JLabel(); private JLabel compileDateValueLabel = new JLabel(); - private JButton imageFreeTransformButton; + private JButton imageTransformButton; - private JButton imageFreeTransformSaveButton; - private JButton imageFreeTransformCancelButton; + private JButton imageTransformSaveButton; + private JButton imageTransformCancelButton; + private TransformPanel imageTransformPanel; + + private TransformPanel placeTransformPanel; + public void setReadOnly(boolean readOnly) { this.readOnly = readOnly; setDividerSize(this.readOnly ? 0 : dividerSize); @@ -495,28 +499,32 @@ public class PreviewPanel extends JPersistentSplitPane implements TagEditorPanel JPanel previewCnt = new JPanel(new BorderLayout()); imagePanel = new ImagePanel(); imagePanel.setLoop(Configuration.loopMedia.get()); + + imageTransformPanel = new TransformPanel(imagePanel); + previewCnt.add(imageTransformPanel, BorderLayout.EAST); + previewCnt.add(imagePanel, BorderLayout.CENTER); JPanel buttonsPanel = new JPanel(new FlowLayout()); - imageFreeTransformButton = new JButton(mainPanel.translate("button.freetransform"), View.getIcon("freetransform16")); - imageFreeTransformButton.setMargin(new Insets(3, 3, 3, 10)); - imageFreeTransformButton.addActionListener(this::freeTransformImageButtonActionPerformed); + imageTransformButton = new JButton(mainPanel.translate("button.transform"), View.getIcon("freetransform16")); + imageTransformButton.setMargin(new Insets(3, 3, 3, 10)); + imageTransformButton.addActionListener(this::transformImageButtonActionPerformed); - imageFreeTransformSaveButton = new JButton(mainPanel.translate("button.save"), View.getIcon("save16")); - imageFreeTransformSaveButton.setMargin(new Insets(3, 3, 3, 10)); - imageFreeTransformSaveButton.addActionListener(this::saveImageFreeTransformButtonActionPerformed); + imageTransformSaveButton = new JButton(mainPanel.translate("button.save"), View.getIcon("save16")); + imageTransformSaveButton.setMargin(new Insets(3, 3, 3, 10)); + imageTransformSaveButton.addActionListener(this::saveImageTransformButtonActionPerformed); - imageFreeTransformCancelButton = new JButton(mainPanel.translate("button.cancel"), View.getIcon("cancel16")); - imageFreeTransformCancelButton.setMargin(new Insets(3, 3, 3, 10)); - imageFreeTransformCancelButton.addActionListener(this::cancelImageFreeTransformButtonActionPerformed); + imageTransformCancelButton = new JButton(mainPanel.translate("button.cancel"), View.getIcon("cancel16")); + imageTransformCancelButton.setMargin(new Insets(3, 3, 3, 10)); + imageTransformCancelButton.addActionListener(this::cancelImageTransformButtonActionPerformed); - buttonsPanel.add(imageFreeTransformButton); - buttonsPanel.add(imageFreeTransformSaveButton); - buttonsPanel.add(imageFreeTransformCancelButton); + buttonsPanel.add(imageTransformButton); + buttonsPanel.add(imageTransformSaveButton); + buttonsPanel.add(imageTransformCancelButton); - imageFreeTransformSaveButton.setVisible(false); - imageFreeTransformCancelButton.setVisible(false); + imageTransformSaveButton.setVisible(false); + imageTransformCancelButton.setVisible(false); previewCnt.add(imagePlayControls = new PlayerControls(mainPanel, imagePanel, buttonsPanel), BorderLayout.SOUTH); imagePlayControls.setMedia(imagePanel); @@ -658,10 +666,13 @@ public class PreviewPanel extends JPersistentSplitPane implements TagEditorPanel JPanel previewCnt = new JPanel(new BorderLayout()); placeImagePanel = new ImagePanel(); + + placeTransformPanel = new TransformPanel(placeImagePanel); //imagePanel.setLoop(Configuration.loopMedia.get()); previewCnt.add(placeImagePanel, BorderLayout.CENTER); PlayerControls placeImagePlayControls = new PlayerControls(mainPanel, placeImagePanel, null); previewCnt.add(placeImagePlayControls, BorderLayout.SOUTH); + previewCnt.add(placeTransformPanel, BorderLayout.EAST); placeImagePlayControls.setMedia(placeImagePanel); previewPanel.add(previewCnt, BorderLayout.CENTER); JLabel prevIntLabel = new HeaderLabel(mainPanel.translate("swfpreview.internal")); @@ -669,7 +680,7 @@ public class PreviewPanel extends JPersistentSplitPane implements TagEditorPanel previewPanel.add(prevIntLabel, BorderLayout.NORTH); placeGenericPanel = new GenericTagTreePanel(mainPanel); - placeSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, previewPanel, placeGenericPanel); + placeSplitPane = new JPersistentSplitPane(JSplitPane.HORIZONTAL_SPLIT, previewPanel, placeGenericPanel, Configuration.guiSplitPanePlaceDividerLocationPercent); placeTagCard.add(placeSplitPane, BorderLayout.CENTER); //placeSplitPane.setDividerLocation(800); @@ -679,9 +690,9 @@ public class PreviewPanel extends JPersistentSplitPane implements TagEditorPanel } private JPanel createPlaceTagButtonsPanel() { - placeFreeTransformButton = new JButton(mainPanel.translate("button.freetransform"), View.getIcon("freetransform16")); - placeFreeTransformButton.setMargin(new Insets(3, 3, 3, 10)); - placeFreeTransformButton.addActionListener(this::freeTransformPlaceTagButtonActionPerformed); + placeTransformButton = new JButton(mainPanel.translate("button.transform"), View.getIcon("freetransform16")); + placeTransformButton.setMargin(new Insets(3, 3, 3, 10)); + placeTransformButton.addActionListener(this::transformPlaceTagButtonActionPerformed); placeEditButton = new JButton(mainPanel.translate("button.edit"), View.getIcon("edit16")); placeEditButton.setMargin(new Insets(3, 3, 3, 10)); placeEditButton.addActionListener(this::editPlaceTagButtonActionPerformed); @@ -695,7 +706,7 @@ public class PreviewPanel extends JPersistentSplitPane implements TagEditorPanel placeCancelButton.setVisible(false); ButtonsPanel placeTagButtonsPanel = new ButtonsPanel(); - placeTagButtonsPanel.add(placeFreeTransformButton); + placeTagButtonsPanel.add(placeTransformButton); placeTagButtonsPanel.add(placeEditButton); placeTagButtonsPanel.add(placeSaveButton); placeTagButtonsPanel.add(placeCancelButton); @@ -733,12 +744,12 @@ public class PreviewPanel extends JPersistentSplitPane implements TagEditorPanel showCardLeft(DRAW_PREVIEW_CARD); parametersPanel.setVisible(false); imagePlayControls.setMedia(imagePanel); - imageFreeTransformButton.setVisible(allowFreeTransform); + imageTransformButton.setVisible(allowFreeTransform); if ((timelined instanceof Tag) && ((Tag) timelined).isReadOnly()) { - imageFreeTransformButton.setVisible(false); + imageTransformButton.setVisible(false); } - imageFreeTransformSaveButton.setVisible(false); - imageFreeTransformCancelButton.setVisible(false); + imageTransformSaveButton.setVisible(false); + imageTransformCancelButton.setVisible(false); imagePanel.setTimelined(timelined, swf, frame, showObjectsUnderCursor, autoPlay, frozen, alwaysDisplay, muted, mutable); } @@ -947,7 +958,7 @@ public class PreviewPanel extends JPersistentSplitPane implements TagEditorPanel placeEditButton.setEnabled(true); placeSaveButton.setVisible(false); placeCancelButton.setVisible(false); - placeFreeTransformButton.setVisible(!tag.isReadOnly() && !readOnly); + placeTransformButton.setVisible(!tag.isReadOnly() && !readOnly); } public void setImageReplaceButtonVisible(boolean showImage, boolean showAlpha, boolean showShape, boolean showSound) { @@ -1164,7 +1175,7 @@ public class PreviewPanel extends JPersistentSplitPane implements TagEditorPanel } private void savePlaceTagButtonActionPerformed(ActionEvent evt) { - if (placeEditMode == PLACE_EDIT_FREETRANSFORM) { + if (placeEditMode == PLACE_EDIT_TRANSFORM) { MATRIX matrix = placeImagePanel.getNewMatrix(); placeTag.setPlaceFlagHasMatrix(true); placeTag.setMatrix(matrix); @@ -1172,6 +1183,8 @@ public class PreviewPanel extends JPersistentSplitPane implements TagEditorPanel placeImagePanel.selectDepth(placeTag.getDepth()); placeImagePanel.freeTransformDepth(-1); placeTag.getTimelined().resetTimeline(); + placeTransformPanel.setVisible(false); + placeGenericPanel.setVisible(true); } Tag hilightTag = null; if (placeEditMode == PLACE_EDIT_RAW) { @@ -1184,7 +1197,7 @@ public class PreviewPanel extends JPersistentSplitPane implements TagEditorPanel } placeGenericPanel.setEditMode(false, null); } - placeFreeTransformButton.setVisible(true); + placeTransformButton.setVisible(true); placeEditButton.setVisible(true); placeSaveButton.setVisible(false); placeCancelButton.setVisible(false); @@ -1198,32 +1211,44 @@ public class PreviewPanel extends JPersistentSplitPane implements TagEditorPanel placeEditMode = PLACE_EDIT_RAW; placeGenericPanel.setEditMode(true, placeTag); placeEditButton.setVisible(false); - placeFreeTransformButton.setVisible(false); + placeTransformButton.setVisible(false); placeSaveButton.setVisible(true); placeCancelButton.setVisible(true); } - private void freeTransformPlaceTagButtonActionPerformed(ActionEvent evt) { + private void transformPlaceTagButtonActionPerformed(ActionEvent evt) { TreeItem item = mainPanel.getCurrentTree().getCurrentTreeItem(); if (item == null) { return; } - placeEditMode = PLACE_EDIT_FREETRANSFORM; + placeEditMode = PLACE_EDIT_TRANSFORM; + placeGenericPanel.setVisible(false); placeImagePanel.selectDepth(-1); - placeImagePanel.freeTransformDepth(placeTag.getDepth()); + + placeTransformPanel.setVisible(true); placeEditButton.setVisible(false); - placeFreeTransformButton.setVisible(false); + placeTransformButton.setVisible(false); placeSaveButton.setVisible(true); - placeCancelButton.setVisible(true); + placeCancelButton.setVisible(true); + + Timer t = new Timer(); + t.schedule(new TimerTask() { + @Override + public void run() { + placeImagePanel.freeTransformDepth(placeTag.getDepth()); + placeTransformPanel.load(); + } + }, 10); //add some delay before controls are hidden } - private void saveImageFreeTransformButtonActionPerformed(ActionEvent evt) { + private void saveImageTransformButtonActionPerformed(ActionEvent evt) { Matrix matrix = new Matrix(imagePanel.getNewMatrix()); + imageTransformPanel.setVisible(false); imagePanel.freeTransformDepth(-1); - imageFreeTransformButton.setVisible(true); - imageFreeTransformCancelButton.setVisible(false); - imageFreeTransformSaveButton.setVisible(false); + imageTransformButton.setVisible(true); + imageTransformCancelButton.setVisible(false); + imageTransformSaveButton.setVisible(false); DefineSpriteTag item = (DefineSpriteTag)mainPanel.getCurrentTree().getCurrentTreeItem(); for (Tag t:item.getTags()) { @@ -1241,15 +1266,16 @@ public class PreviewPanel extends JPersistentSplitPane implements TagEditorPanel mainPanel.reload(true); } - private void cancelImageFreeTransformButtonActionPerformed(ActionEvent evt) { + private void cancelImageTransformButtonActionPerformed(ActionEvent evt) { + imageTransformPanel.setVisible(false); imagePanel.freeTransformDepth(-1); - imageFreeTransformButton.setVisible(true); - imageFreeTransformCancelButton.setVisible(false); - imageFreeTransformSaveButton.setVisible(false); + imageTransformButton.setVisible(true); + imageTransformCancelButton.setVisible(false); + imageTransformSaveButton.setVisible(false); mainPanel.reload(true); } - private void freeTransformImageButtonActionPerformed(ActionEvent evt) { + private void transformImageButtonActionPerformed(ActionEvent evt) { TreeItem item = mainPanel.getCurrentTree().getCurrentTreeItem(); if (item == null) { return; @@ -1311,32 +1337,36 @@ public class PreviewPanel extends JPersistentSplitPane implements TagEditorPanel imagePanel.setTimelined(sprite2, fSwf, 0, true, true, true, true, true, false); imagePanel.selectDepth(-1); - imageFreeTransformButton.setVisible(false); - imageFreeTransformSaveButton.setVisible(true); - imageFreeTransformCancelButton.setVisible(true); + imageTransformButton.setVisible(false); + imageTransformSaveButton.setVisible(true); + imageTransformCancelButton.setVisible(true); + imageTransformPanel.setVisible(true); Timer t = new Timer(); t.schedule(new TimerTask() { @Override public void run() { imagePanel.freeTransformDepth(placeTag2.getDepth()); + imageTransformPanel.load(); } }, 10); //add some delay before controls are hidden } private void cancelPlaceTagButtonActionPerformed(ActionEvent evt) { - if (placeEditMode == PLACE_EDIT_FREETRANSFORM) { + if (placeEditMode == PLACE_EDIT_TRANSFORM) { placeImagePanel.selectDepth(placeTag.getDepth()); placeImagePanel.freeTransformDepth(-1); placeTag.setMatrix(oldMatrix); placeTag.getTimelined().resetTimeline(); + placeTransformPanel.setVisible(false); + placeGenericPanel.setVisible(true); } if (placeEditMode == PLACE_EDIT_RAW) { placeGenericPanel.setEditMode(false, null); } placeEditButton.setVisible(true); - placeFreeTransformButton.setVisible(true); + placeTransformButton.setVisible(true); placeSaveButton.setVisible(false); placeCancelButton.setVisible(false); } @@ -1372,7 +1402,7 @@ public class PreviewPanel extends JPersistentSplitPane implements TagEditorPanel || metadataSaveButton.isVisible() || placeSaveButton.isVisible() || fontPanel.isEditing() - || imageFreeTransformSaveButton.isVisible(); + || imageTransformSaveButton.isVisible(); } public void selectImageDepth(int depth) { diff --git a/src/com/jpexs/decompiler/flash/gui/TransformPanel.java b/src/com/jpexs/decompiler/flash/gui/TransformPanel.java new file mode 100644 index 000000000..069c9b64c --- /dev/null +++ b/src/com/jpexs/decompiler/flash/gui/TransformPanel.java @@ -0,0 +1,498 @@ +/* + * 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.helpers.Reference; +import java.awt.Component; +import java.awt.FlowLayout; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.FocusAdapter; +import java.awt.event.FocusEvent; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; +import java.awt.geom.Rectangle2D; +import java.text.DecimalFormat; +import javax.swing.BorderFactory; +import javax.swing.BoxLayout; +import javax.swing.ButtonGroup; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JComboBox; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextField; +import javax.swing.JToggleButton; +import javax.swing.border.BevelBorder; + +/** + * + * @author JPEXS + */ +public class TransformPanel extends JPanel { + + private JTextField moveHorizontalTextField = new JTextField(8); + private JTextField moveVerticalTextField = new JTextField(8); + private JComboBox moveUnitComboBox = new JComboBox<>(); + private JCheckBox moveRelativeCheckBox = new JCheckBox("Relative move"); + + private JTextField scaleWidthTextField = new JTextField(formatDouble(100), 8); + private JTextField scaleHeightTextField = new JTextField(formatDouble(100), 8); + private JComboBox scaleUnitComboBox = new JComboBox<>(); + private JCheckBox scaleProportionallyCheckBox = new JCheckBox("Scale proportionally"); + + private JTextField rotateTextField = new JTextField(formatDouble(0), 8); + private JComboBox rotateUnitComboBox = new JComboBox<>(); + private JToggleButton rotateAntiClockwiseToggleButton = new JToggleButton(View.getIcon("rotateanticlockwise16")); + private JToggleButton rotateClockwiseToggleButton = new JToggleButton(View.getIcon("rotateclockwise16")); + + private JTextField skewHorizontalTextField = new JTextField(formatDouble(0), 8); + private JTextField skewVerticalTextField = new JTextField(formatDouble(0), 8); + private JComboBox skewUnitComboBox = new JComboBox<>(); + + private JTextField matrixATextField = new JTextField(formatDouble(1), 8); + private JTextField matrixBTextField = new JTextField(formatDouble(0), 8); + private JTextField matrixCTextField = new JTextField(formatDouble(0),8); + private JTextField matrixDTextField = new JTextField(formatDouble(1),8); + private JTextField matrixETextField = new JTextField(formatDouble(0), 8); + private JTextField matrixFTextField = new JTextField(formatDouble(0), 8); + private JCheckBox matrixEditCurrentCheckBox = new JCheckBox("Edit current matrix"); + + private ImagePanel imagePanel; + + private Rectangle2D bounds = new Rectangle2D.Double(0, 0, 1, 1); + + + public static enum UnitKind { + LENGTH, + ANGLE + } + public static enum Unit { + PX("px", 1.0, UnitKind.LENGTH), + TWIP("twip", 20.0, UnitKind.LENGTH), + PERCENT("%", 0.0, UnitKind.LENGTH), + TURN("turn", 1/360.0, UnitKind.ANGLE), + DEG("°", 1, UnitKind.ANGLE), + RAD("rad", Math.PI/180, UnitKind.ANGLE), + GRAD("grad", 1/0.9, UnitKind.ANGLE); + + private Unit(String name, double value, UnitKind kind) { + this.name = name; + this.value = value; + this.kind = kind; + } + + private final String name; + private final double value; + private final UnitKind kind; + + @Override + public String toString() { + return name; + } + + public double getValue() { + return value; + } + + public UnitKind getKind() { + return kind; + } + } + + private class UnitComboItem { + Unit unit; + String title; + } + + public TransformPanel(ImagePanel imagePanel) { + + imagePanel.addBoundsChangeListener(new BoundsChangeListener() { + @Override + public void boundsChanged(Rectangle2D newBounds) { + update(newBounds); + } + }); + setBorder(BorderFactory.createBevelBorder(BevelBorder.LOWERED)); + this.imagePanel = imagePanel; + setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); + + GridBagConstraints c = new GridBagConstraints(); + c.fill = GridBagConstraints.HORIZONTAL; + + add(makeHeader("Move","transformmove16")); + JPanel movePanel = new JPanel(new GridBagLayout()); + addRow(movePanel, 0, new JLabel("Horizontal:"), moveHorizontalTextField, moveUnitComboBox); + addRow(movePanel, 1, new JLabel("Vertical:"), moveVerticalTextField); + addJoinedRow(movePanel, 2, moveRelativeCheckBox, 3); + addJoinedRow(movePanel, 3, makeClearApplyPanel(this::applyMoveActionPerformed, this::clearMoveActionPerformed), 3); + add(movePanel); + + moveUnitComboBox.addItem(Unit.PX); + moveUnitComboBox.addItem(Unit.TWIP); + + moveUnitComboBox.setSelectedItem(Unit.PX); + + addUnitChangeListener(moveUnitComboBox, new UnitChangedListener() { + @Override + public void unitChanged(Unit prevUnit, Unit newUnit) { + try{ + double moveHorizontal = Double.parseDouble(moveHorizontalTextField.getText()); + double moveVertical = Double.parseDouble(moveVerticalTextField.getText()); + moveHorizontalTextField.setText(formatDouble(convertUnit(moveHorizontal, prevUnit, newUnit))); + moveVerticalTextField.setText(formatDouble(convertUnit(moveVertical, prevUnit, newUnit))); + } catch(NumberFormatException nfe) { + return; + } + } + }); + + add(makeHeader("Scale", "transformscale16")); + + JPanel scalePanel = new JPanel(new GridBagLayout()); + addRow(scalePanel, 0, new JLabel("Width:"), scaleWidthTextField, scaleUnitComboBox); + addRow(scalePanel, 1, new JLabel("Height:"), scaleHeightTextField); + addJoinedRow(scalePanel, 2, scaleProportionallyCheckBox, 3); + addJoinedRow(scalePanel, 3, makeClearApplyPanel(this::applyScaleActionPerformed, this::clearScaleActionPerformed), 3); + add(scalePanel); + scaleUnitComboBox.addItem(Unit.PERCENT); + scaleUnitComboBox.addItem(Unit.PX); + scaleUnitComboBox.addItem(Unit.TWIP); + + scaleUnitComboBox.setSelectedItem(Unit.PERCENT); + + addUnitChangeListener(scaleUnitComboBox, new UnitChangedListener() { + @Override + public void unitChanged(Unit prevUnit, Unit newUnit) { + try{ + double scaleWidth = Double.parseDouble(scaleWidthTextField.getText()); + double scaleHeight = Double.parseDouble(scaleHeightTextField.getText()); + + if (prevUnit == Unit.PERCENT) { + scaleWidthTextField.setText(formatDouble(convertUnit(bounds.getWidth() * scaleWidth / 100, Unit.PX, newUnit))); + scaleHeightTextField.setText(formatDouble(convertUnit(bounds.getHeight() * scaleHeight / 100, Unit.PX, newUnit))); + return; + } + if (newUnit == Unit.PERCENT) { + double scaleWidthPx = convertUnit(scaleWidth, prevUnit, Unit.PX); + double scaleHeightPx = convertUnit(scaleHeight, prevUnit, Unit.PX); + scaleWidthTextField.setText(formatDouble((scaleWidthPx * 100 / bounds.getWidth()))); + scaleHeightTextField.setText(formatDouble((scaleHeightPx * 100 / bounds.getHeight()))); + return; + } + + scaleWidthTextField.setText(formatDouble(convertUnit(scaleWidth, prevUnit, newUnit))); + scaleHeightTextField.setText(formatDouble(convertUnit(scaleHeight, prevUnit, newUnit))); + + } catch(NumberFormatException nfe) { + return; + } + } + }); + + + scaleWidthTextField.addFocusListener(new FocusAdapter() { + @Override + public void focusLost(FocusEvent e) { + if (scaleProportionallyCheckBox.isSelected() && scaleUnitComboBox.getSelectedItem() != Unit.PERCENT) { + double ratio = bounds.getHeight() / bounds.getWidth(); + try { + double scaleWidth = Double.parseDouble(scaleWidthTextField.getText()); + double scaleHeight = ratio * scaleWidth; + scaleHeightTextField.setText(formatDouble(scaleHeight)); + }catch(NumberFormatException nfe) { + + } + } + } + }); + + scaleHeightTextField.addFocusListener(new FocusAdapter() { + @Override + public void focusLost(FocusEvent e) { + if (scaleProportionallyCheckBox.isSelected() && scaleUnitComboBox.getSelectedItem() != Unit.PERCENT) { + double ratio = bounds.getWidth() / bounds.getHeight(); + try { + double scaleHeight = Double.parseDouble(scaleHeightTextField.getText()); + double scaleWidth = ratio * scaleHeight; + scaleWidthTextField.setText(formatDouble(scaleWidth)); + }catch(NumberFormatException nfe) { + + } + } + } + }); + + + add(makeHeader("Rotate", "transformrotate16")); + + ButtonGroup clockGroup = new ButtonGroup(); + clockGroup.add(rotateClockwiseToggleButton); + clockGroup.add(rotateAntiClockwiseToggleButton); + JPanel rotatePanel = new JPanel(new GridBagLayout()); + addRow(rotatePanel, 0, new JLabel("Angle:"), rotateTextField, rotateUnitComboBox, rotateAntiClockwiseToggleButton, rotateClockwiseToggleButton); + addJoinedRow(rotatePanel, 1, makeClearApplyPanel(this::applyRotateActionPerformed, this::clearRotateActionPerformed), 5); + + add(rotatePanel); + + rotateUnitComboBox.addItem(Unit.TURN); + rotateUnitComboBox.addItem(Unit.DEG); + rotateUnitComboBox.addItem(Unit.RAD); + rotateUnitComboBox.addItem(Unit.GRAD); + + rotateUnitComboBox.setSelectedItem(Unit.TURN); + + rotateClockwiseToggleButton.setSelected(true); + + addUnitChangeListener(rotateUnitComboBox, new UnitChangedListener() { + @Override + public void unitChanged(Unit prevUnit, Unit newUnit) { + try{ + double rotate = Double.parseDouble(rotateTextField.getText()); + rotateTextField.setText(formatDouble(convertUnit(rotate, prevUnit, newUnit))); + + } catch(NumberFormatException nfe) { + return; + } + } + }); + + add(makeHeader("Skew", "transformskew16")); + + JPanel skewPanel = new JPanel(new GridBagLayout()); + addRow(skewPanel, 0, new JLabel("Horizontal:"), skewHorizontalTextField, skewUnitComboBox); + addRow(skewPanel, 1, new JLabel("Vertical:"), skewVerticalTextField); + addJoinedRow(skewPanel, 2, makeClearApplyPanel(this::applySkewActionPerformed, this::clearSkewActionPerformed), 3); + + add(skewPanel); + + skewUnitComboBox.addItem(Unit.PX); + skewUnitComboBox.addItem(Unit.TWIP); + skewUnitComboBox.addItem(Unit.TURN); + skewUnitComboBox.addItem(Unit.DEG); + skewUnitComboBox.addItem(Unit.RAD); + skewUnitComboBox.addItem(Unit.GRAD); + + skewUnitComboBox.setSelectedItem(Unit.DEG); + + addUnitChangeListener(skewUnitComboBox, new UnitChangedListener() { + @Override + public void unitChanged(Unit prevUnit, Unit newUnit) { + try{ + double skewHorizontal = Double.parseDouble(skewHorizontalTextField.getText()); + double skewVertical = Double.parseDouble(skewVerticalTextField.getText()); + skewHorizontalTextField.setText(formatDouble(convertUnit(skewHorizontal, prevUnit, newUnit))); + skewVerticalTextField.setText(formatDouble(convertUnit(skewVertical, prevUnit, newUnit))); + + } catch(NumberFormatException nfe) { + return; + } + } + }); + + + add(makeHeader("Matrix", null)); + + JPanel matrixPanel = new JPanel(new GridBagLayout()); + addRow(matrixPanel, 0, new JLabel("A"), matrixATextField, new JLabel("C"), matrixCTextField, new JLabel("E"), matrixETextField); + addRow(matrixPanel, 1, new JLabel("B"), matrixBTextField, new JLabel("D"), matrixDTextField, new JLabel("F"), matrixFTextField); + addJoinedRow(matrixPanel,2, matrixEditCurrentCheckBox, 6); + addJoinedRow(matrixPanel, 3, makeClearApplyPanel(this::applyMatrixActionPerformed, this::clearMatrixActionPerformed), 6); + + add(matrixPanel); + + setVisible(false); + } + + public void load() { + clearMoveActionPerformed(null); + clearScaleActionPerformed(null); + clearRotateActionPerformed(null); + clearSkewActionPerformed(null); + clearMatrixActionPerformed(null); + } + + private void update(Rectangle2D bounds) { + this.bounds = bounds; + if (!moveRelativeCheckBox.isSelected()) { + moveHorizontalTextField.setText(formatDouble(convertUnit(bounds.getX(), Unit.PX, (Unit)moveUnitComboBox.getSelectedItem()))); + moveVerticalTextField.setText(formatDouble(convertUnit(bounds.getY(), Unit.PX, (Unit)moveUnitComboBox.getSelectedItem()))); + } + if (scaleProportionallyCheckBox.isSelected() && scaleUnitComboBox.getSelectedItem() != Unit.PERCENT) { + try + { + double ratio = bounds.getHeight() / bounds.getWidth(); + + double scaleWidth = Double.parseDouble(scaleWidthTextField.getText()); + double scaleHeight = ratio * scaleWidth; + scaleHeightTextField.setText(formatDouble(scaleHeight)); + } + catch(NumberFormatException nfe) { + + } + } + } + + + private void clearMoveActionPerformed(ActionEvent e) { + Rectangle2D bounds = imagePanel.getTransformBounds(); + if (bounds != null) { + moveHorizontalTextField.setText(formatDouble(bounds.getX())); + moveVerticalTextField.setText(formatDouble(bounds.getY())); + } + moveRelativeCheckBox.setSelected(false); + } + + private void applyMoveActionPerformed(ActionEvent e) { + //TODO + } + + private void clearScaleActionPerformed(ActionEvent e) { + scaleWidthTextField.setText(formatDouble(100)); + scaleHeightTextField.setText(formatDouble(100)); + scaleUnitComboBox.setSelectedItem(Unit.PERCENT); + scaleProportionallyCheckBox.setSelected(true); + } + + private void applyScaleActionPerformed(ActionEvent e) { + + } + + private void clearRotateActionPerformed(ActionEvent e) { + rotateTextField.setText(formatDouble(0)); + rotateUnitComboBox.setSelectedItem(Unit.TURN); + rotateClockwiseToggleButton.setSelected(true); + } + + private void applyRotateActionPerformed(ActionEvent e) { + + } + + private void clearSkewActionPerformed(ActionEvent e) { + skewHorizontalTextField.setText(formatDouble(0)); + skewVerticalTextField.setText(formatDouble(0)); + skewUnitComboBox.setSelectedItem(Unit.DEG); + } + + private void applySkewActionPerformed(ActionEvent e) { + + } + + private void clearMatrixActionPerformed(ActionEvent e) { + matrixATextField.setText(formatDouble(1)); + matrixBTextField.setText(formatDouble(0)); + matrixCTextField.setText(formatDouble(0)); + matrixDTextField.setText(formatDouble(1)); + matrixETextField.setText(formatDouble(0)); + matrixFTextField.setText(formatDouble(0)); + matrixEditCurrentCheckBox.setSelected(false); + } + + private void applyMatrixActionPerformed(ActionEvent e) { + + } + + private void addJoinedRow(JPanel panel, int rownum, Component comp, int numCols) { + GridBagConstraints c = new GridBagConstraints(); + c.fill = GridBagConstraints.BOTH; + c.insets = new Insets(2, 2, 2, 2); + c.gridx = 0; + c.gridy = rownum; + c.gridwidth = numCols; + panel.add(comp, c); + } + + private void addRow(JPanel panel, int rownum, Component ...comp) { + GridBagConstraints c = new GridBagConstraints(); + c.fill = GridBagConstraints.BOTH; + c.insets = new Insets(2, 2, 2, 2); + for (int i = 0; i < comp.length; i++) { + c.gridx = i; + c.gridy = rownum; + panel.add(comp[i], c); + } + } + + private JPanel makeHeader(String title, String icon) { + JPanel headerPanel = new JPanel(new FlowLayout()); + JLabel label = new JLabel(title); + if (icon != null) { + label.setIcon(View.getIcon(icon)); + } + headerPanel.add(label); + headerPanel.setBorder(BorderFactory.createBevelBorder(BevelBorder.RAISED)); + return headerPanel; + } + + private JPanel makeClearApplyPanel(ActionListener onApply, ActionListener onClear) { + JPanel buttonsPanel = new JPanel(new FlowLayout()); + JButton clearButton = new JButton("Clear"); + clearButton.addActionListener(onClear); + JButton applyButton = new JButton("Apply"); + applyButton.addActionListener(onApply); + buttonsPanel.add(clearButton); + buttonsPanel.add(applyButton); + return buttonsPanel; + } + + private void addUnitChangeListener(JComboBox unitComboBox, UnitChangedListener listener) { + final Reference previousValue = new Reference<>((Unit)unitComboBox.getSelectedItem()); + unitComboBox.addItemListener(new ItemListener() { + @Override + public void itemStateChanged(ItemEvent e) { + if (e.getStateChange() == ItemEvent.SELECTED) { + Unit newValue = (Unit)e.getItem(); + listener.unitChanged(previousValue.getVal(), newValue); + previousValue.setVal(newValue); + } + } + }); + } + + + interface UnitChangedListener { + public void unitChanged(Unit prevUnit, Unit newUnit); + } + + private static double convertUnit(double value, Unit sourceUnit, Unit targetUnit) { + if (sourceUnit == targetUnit) { + return value; + } + if (sourceUnit.kind != targetUnit.kind) { + return value; //no conversion + } + //to px + return (value / sourceUnit.value) * targetUnit.value; + } + + private static String formatDouble(double value) { + DecimalFormat df = new DecimalFormat("0.000"); + return df.format(value); + } + + public static void main(String[] args) { + System.out.println("20 twip to px =" + convertUnit(20, Unit.TWIP, Unit.PX)); + System.out.println("1 turn to deg =" + convertUnit(1, Unit.TURN, Unit.DEG)); + System.out.println("1 deg to rad =" + convertUnit(1, Unit.DEG, Unit.RAD)); + System.out.println("1 deg to grad =" + convertUnit(1, Unit.DEG, Unit.GRAD)); + } +} + + diff --git a/src/com/jpexs/decompiler/flash/gui/graphics/rotateanticlockwise16.png b/src/com/jpexs/decompiler/flash/gui/graphics/rotateanticlockwise16.png new file mode 100644 index 000000000..7da5cfd01 Binary files /dev/null and b/src/com/jpexs/decompiler/flash/gui/graphics/rotateanticlockwise16.png differ diff --git a/src/com/jpexs/decompiler/flash/gui/graphics/rotateclockwise16.png b/src/com/jpexs/decompiler/flash/gui/graphics/rotateclockwise16.png new file mode 100644 index 000000000..11b6dd34e Binary files /dev/null and b/src/com/jpexs/decompiler/flash/gui/graphics/rotateclockwise16.png differ diff --git a/src/com/jpexs/decompiler/flash/gui/graphics/transformmove16.png b/src/com/jpexs/decompiler/flash/gui/graphics/transformmove16.png new file mode 100644 index 000000000..6e13dd3d6 Binary files /dev/null and b/src/com/jpexs/decompiler/flash/gui/graphics/transformmove16.png differ diff --git a/src/com/jpexs/decompiler/flash/gui/graphics/transformrotate16.png b/src/com/jpexs/decompiler/flash/gui/graphics/transformrotate16.png new file mode 100644 index 000000000..9a470f76d Binary files /dev/null and b/src/com/jpexs/decompiler/flash/gui/graphics/transformrotate16.png differ diff --git a/src/com/jpexs/decompiler/flash/gui/graphics/transformscale16.png b/src/com/jpexs/decompiler/flash/gui/graphics/transformscale16.png new file mode 100644 index 000000000..cc12f14b9 Binary files /dev/null and b/src/com/jpexs/decompiler/flash/gui/graphics/transformscale16.png differ diff --git a/src/com/jpexs/decompiler/flash/gui/graphics/transformskew16.png b/src/com/jpexs/decompiler/flash/gui/graphics/transformskew16.png new file mode 100644 index 000000000..c7ee7e2ff Binary files /dev/null and b/src/com/jpexs/decompiler/flash/gui/graphics/transformskew16.png differ diff --git a/src/com/jpexs/decompiler/flash/gui/locales/AdvancedSettingsDialog.properties b/src/com/jpexs/decompiler/flash/gui/locales/AdvancedSettingsDialog.properties index 2faac7bd7..87ce86817 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/AdvancedSettingsDialog.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/AdvancedSettingsDialog.properties @@ -638,3 +638,6 @@ config.description.autoPlaySounds = Automatically play sounds (DefineSound) on t config.name.deobfuscateAs12RemoveInvalidNamesAssignments = AS1/2 deobfuscation: Remove variable declarations with obfuscated names config.description.deobfuscateAs12RemoveInvalidNamesAssignments = During deobfuscation of AS1/2, remove variable declarations which have nonstandard name. WARNING: This could damage SWFs which rely on obfuscated names. + +config.name.gui.splitPanePlace.dividerLocationPercent = (Internal) Splitter place location +config.description.gui.splitPanePlace.dividerLocationPercent = \ No newline at end of file diff --git a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties index 07dcd0ece..da8e97759 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties @@ -1000,4 +1000,6 @@ message.video.installvlc = The file "%file%" contains video tags (DefineVideoStr To properly display them in the decompiler, VLC media player installed is required (minimum version 3.0.0).\r\n\ You can however still export the videos to FLV file format without it. -button.mute = Mute frame sounds \ No newline at end of file +button.mute = Mute frame sounds + +button.transform = Transform \ No newline at end of file