diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/PlaceObject2Tag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/PlaceObject2Tag.java
index 99ffa5ecd..879e7ad80 100644
--- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/PlaceObject2Tag.java
+++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/PlaceObject2Tag.java
@@ -489,4 +489,10 @@ public class PlaceObject2Tag extends PlaceObjectTypeTag implements ASMSourceCont
public boolean hasImage() {
return false;
}
+
+ @Override
+ public void setColorTransform(ColorTransform colorTransform) {
+ this.colorTransform = new CXFORMWITHALPHA(colorTransform);
+ placeFlagHasColorTransform = true;
+ }
}
diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/PlaceObject3Tag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/PlaceObject3Tag.java
index 9e664b34a..19d804dd5 100644
--- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/PlaceObject3Tag.java
+++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/PlaceObject3Tag.java
@@ -698,5 +698,10 @@ public class PlaceObject3Tag extends PlaceObjectTypeTag implements ASMSourceCont
public boolean hasImage() {
return placeFlagHasImage;
}
-
+
+ @Override
+ public void setColorTransform(ColorTransform colorTransform) {
+ this.colorTransform = new CXFORMWITHALPHA(colorTransform);
+ placeFlagHasColorTransform = true;
+ }
}
diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/PlaceObject4Tag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/PlaceObject4Tag.java
index 06a8349a2..58ac123b4 100644
--- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/PlaceObject4Tag.java
+++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/PlaceObject4Tag.java
@@ -719,4 +719,10 @@ public class PlaceObject4Tag extends PlaceObjectTypeTag implements ASMSourceCont
public boolean hasImage() {
return placeFlagHasImage;
}
+
+ @Override
+ public void setColorTransform(ColorTransform colorTransform) {
+ this.colorTransform = new CXFORMWITHALPHA(colorTransform);
+ placeFlagHasColorTransform = true;
+ }
}
diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/PlaceObjectTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/PlaceObjectTag.java
index dba208e2e..716ba9064 100644
--- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/PlaceObjectTag.java
+++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/PlaceObjectTag.java
@@ -301,4 +301,9 @@ public class PlaceObjectTag extends PlaceObjectTypeTag {
public boolean hasImage() {
return false;
}
+
+ @Override
+ public void setColorTransform(ColorTransform colorTransform) {
+ this.colorTransform = new CXFORM(colorTransform);
+ }
}
diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/PlaceObjectTypeTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/PlaceObjectTypeTag.java
index b93443a3a..75d959b9b 100644
--- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/PlaceObjectTypeTag.java
+++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/PlaceObjectTypeTag.java
@@ -208,6 +208,12 @@ public abstract class PlaceObjectTypeTag extends Tag implements CharacterIdTag,
* @param placeFlagMove Place flag move
*/
public abstract void setPlaceFlagMove(boolean placeFlagMove);
+
+ /**
+ * Sets color transform.
+ * @param colorTransform Color transform
+ */
+ public abstract void setColorTransform(ColorTransform colorTransform);
/**
* Checks if place equals.
diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/CXFORMWITHALPHA.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/CXFORMWITHALPHA.java
index 578ca847b..cb1c8b5e0 100644
--- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/CXFORMWITHALPHA.java
+++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/CXFORMWITHALPHA.java
@@ -146,6 +146,10 @@ public class CXFORMWITHALPHA extends ColorTransform implements Serializable {
* Constructor.
*/
public CXFORMWITHALPHA() {
+ alphaMultTerm = 256;
+ redMultTerm = 256;
+ greenMultTerm = 256;
+ blueMultTerm = 256;
}
/**
@@ -160,7 +164,7 @@ public class CXFORMWITHALPHA extends ColorTransform implements Serializable {
greenAddTerm = cxform.greenAddTerm;
blueAddTerm = cxform.blueAddTerm;
alphaAddTerm = 0;
- alphaMultTerm = 255;
+ alphaMultTerm = 256;
}
/**
diff --git a/src/com/jpexs/decompiler/flash/easygui/ChangeDoableOperation.java b/src/com/jpexs/decompiler/flash/easygui/ChangeDoableOperation.java
new file mode 100644
index 000000000..53a251738
--- /dev/null
+++ b/src/com/jpexs/decompiler/flash/easygui/ChangeDoableOperation.java
@@ -0,0 +1,36 @@
+/*
+ * 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 abstract class ChangeDoableOperation implements DoableOperation {
+
+ private final String itemIdentifier;
+
+ public ChangeDoableOperation(String itemIdentifier) {
+ this.itemIdentifier = itemIdentifier;
+ }
+
+
+ @Override
+ public String getDescription() {
+ return EasyStrings.translate("action.change").replace("%item%", EasyStrings.translate("action.change." + itemIdentifier));
+ }
+}
diff --git a/src/com/jpexs/decompiler/flash/easygui/EasySwfPanel.java b/src/com/jpexs/decompiler/flash/easygui/EasySwfPanel.java
index 20a084261..1aea3747d 100644
--- a/src/com/jpexs/decompiler/flash/easygui/EasySwfPanel.java
+++ b/src/com/jpexs/decompiler/flash/easygui/EasySwfPanel.java
@@ -16,10 +16,14 @@
*/
package com.jpexs.decompiler.flash.easygui;
+import com.jpexs.decompiler.flash.easygui.properties.IntegerPropertyField;
+import com.jpexs.decompiler.flash.easygui.properties.FloatPropertyField;
import com.jpexs.decompiler.flash.DefineBeforeUsageFixer;
import com.jpexs.decompiler.flash.ReadOnlyTagList;
import com.jpexs.decompiler.flash.SWF;
import com.jpexs.decompiler.flash.configuration.Configuration;
+import com.jpexs.decompiler.flash.easygui.properties.panels.DocumentPropertiesPanel;
+import com.jpexs.decompiler.flash.easygui.properties.panels.GeneralPropertiesPanel;
import com.jpexs.decompiler.flash.gui.FasterScrollPane;
import com.jpexs.decompiler.flash.gui.ImagePanel;
import com.jpexs.decompiler.flash.gui.Main;
@@ -45,6 +49,7 @@ import com.jpexs.decompiler.flash.timeline.Timelined;
import com.jpexs.decompiler.flash.treeitems.Openable;
import com.jpexs.decompiler.flash.types.MATRIX;
import java.awt.BorderLayout;
+import java.awt.CardLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
@@ -91,6 +96,12 @@ public class EasySwfPanel extends JPanel {
private Timelined timelined;
private JLabel timelineLabel;
private JButton closeTimelineButton;
+ private JPanel propertiesPanel;
+
+ private static final String PROPERTIES_DOCUMENT = "Document";
+ private static final String PROPERTIES_GENERAL = "General";
+ private DocumentPropertiesPanel documentPropertiesPanel;
+ private GeneralPropertiesPanel generalPropertiesPanel;
public EasySwfPanel() {
setLayout(new BorderLayout());
@@ -107,6 +118,7 @@ public class EasySwfPanel extends JPanel {
timelinePanel.setDepth(pl.getDepth());
}
transformPanel.setVisible(pl != null);
+ updatePropertiesPanel();
}
});
@@ -384,6 +396,7 @@ public class EasySwfPanel extends JPanel {
}
}
transformPanel.setVisible(depth != -1);
+ updatePropertiesPanel();
}
});
@@ -403,6 +416,19 @@ public class EasySwfPanel extends JPanel {
libraryPreviewPanel.setPreferredSize(new Dimension(200, 200));
rightTabbedPane = new JTabbedPane();
+
+
+ propertiesPanel = new JPanel();
+ documentPropertiesPanel = new DocumentPropertiesPanel(undoManager);
+ propertiesPanel.setLayout(new CardLayout());
+
+ generalPropertiesPanel = new GeneralPropertiesPanel(this, undoManager);
+ propertiesPanel.add(documentPropertiesPanel, PROPERTIES_DOCUMENT);
+ propertiesPanel.add(generalPropertiesPanel, PROPERTIES_GENERAL);
+
+
+ rightTabbedPane.addTab(EasyStrings.translate("properties"), propertiesPanel);
+
rightTabbedPane.addTab(EasyStrings.translate("library"), libraryPanel);
JPanel transformTab = new JPanel(new BorderLayout());
@@ -473,8 +499,19 @@ public class EasySwfPanel extends JPanel {
});
}
+ private void updatePropertiesPanel() {
+ CardLayout cl = (CardLayout) propertiesPanel.getLayout();
+ PlaceObjectTypeTag place = getSelectedPlaceTag();
+ if (place == null) {
+ cl.show(propertiesPanel, PROPERTIES_DOCUMENT);
+ return;
+ }
+ generalPropertiesPanel.update();
+ cl.show(propertiesPanel, PROPERTIES_GENERAL);
+ }
+
private boolean transformEnabled() {
- return rightTabbedPane.getSelectedIndex() == 1;
+ return rightTabbedPane.getSelectedIndex() == 2;
}
public void setTimelined(Timelined timelined) {
@@ -486,8 +523,11 @@ public class EasySwfPanel extends JPanel {
libraryPreviewPanel.clearAll();
closeTimelineButton.setVisible(false);
timelineLabel.setText("");
+ documentPropertiesPanel.setSwf(null);
+ generalPropertiesPanel.update();
} else {
SWF swf = timelined.getSwf();
+ documentPropertiesPanel.setSwf(swf);
libraryTreeTable.setSwf(swf);
libraryPreviewPanel.clearAll();
stagePanel.setTimelined(timelined, swf, 0, true, true, true, true, true, false, true);
@@ -502,6 +542,7 @@ public class EasySwfPanel extends JPanel {
timelineLabel.setText(EasyStrings.translate("timeline.item").replace("%item%", nameResolver.getTagName((Tag) timelined)));
closeTimelineButton.setVisible(true);
}
+ generalPropertiesPanel.update();
}
updateUndos();
}
@@ -537,4 +578,37 @@ public class EasySwfPanel extends JPanel {
setTimelined(null);
undoManager.clear();
}
+
+ public int getDepth() {
+ return stagePanel.getSelectedDepth();
+ }
+
+ public int getFrame() {
+ return stagePanel.getFrame();
+ }
+
+ public ImagePanel getStagePanel() {
+ return stagePanel;
+ }
+
+ public SWF getSwf() {
+ return timelined == null ? null : timelined.getSwf();
+ }
+
+ public DepthState getSelectedDepthState() {
+ if (timelined == null) {
+ return null;
+ }
+ int frame = stagePanel.getFrame();
+ int depth = stagePanel.getSelectedDepth();
+ DepthState ds = timelined.getTimeline().getDepthState(frame, depth);
+ return ds;
+ }
+ public PlaceObjectTypeTag getSelectedPlaceTag() {
+ DepthState ds = getSelectedDepthState();
+ if (ds == null) {
+ return null;
+ }
+ return ds.placeObjectTag;
+ }
}
diff --git a/src/com/jpexs/decompiler/flash/easygui/properties/AbstractPropertyField.java b/src/com/jpexs/decompiler/flash/easygui/properties/AbstractPropertyField.java
new file mode 100644
index 000000000..199402d6b
--- /dev/null
+++ b/src/com/jpexs/decompiler/flash/easygui/properties/AbstractPropertyField.java
@@ -0,0 +1,227 @@
+/*
+ * 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.properties;
+
+import java.awt.AWTEvent;
+import java.awt.BasicStroke;
+import java.awt.CardLayout;
+import java.awt.Cursor;
+import java.awt.Font;
+import java.awt.FontMetrics;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Stroke;
+import java.awt.Toolkit;
+import java.awt.event.AWTEventListener;
+import java.awt.event.FocusAdapter;
+import java.awt.event.FocusEvent;
+import java.awt.event.KeyAdapter;
+import java.awt.event.KeyEvent;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.geom.Line2D;
+import java.util.ArrayList;
+import java.util.List;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JTextField;
+import javax.swing.SwingUtilities;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+
+/**
+ *
+ * @author JPEXS
+ */
+public abstract class AbstractPropertyField extends JPanel {
+ private static final String CARD_READ = "Read";
+ private static final String CARD_WRITE = "Write";
+
+ protected JLabel readLabel;
+ protected JTextField writeField;
+
+ private final List> validations = new ArrayList<>();
+ private final List changeListeners = new ArrayList<>();
+
+ private AWTEventListener aeListener;
+
+ public void addValidation(PropertyValidationInteface validation) {
+ validations.add(validation);
+ }
+
+ public void removeValidation(PropertyValidationInteface validation) {
+ validations.remove(validation);
+ }
+
+ public void addChangeListener(ChangeListener changeListener) {
+ changeListeners.add(changeListener);
+ }
+
+ public void removeChangeListener(ChangeListener changeListener) {
+ changeListeners.remove(changeListener);
+ }
+
+ private void fireChange() {
+ for (ChangeListener listener : changeListeners) {
+ listener.stateChanged(new ChangeEvent(this));
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ public AbstractPropertyField(String text) {
+ setLayout(new CardLayout());
+ readLabel = new DottedUnderlineLabel(text);
+
+ readLabel.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
+ writeField = new JTextField(text);
+ add(readLabel, CARD_READ);
+ add(writeField, CARD_WRITE);
+
+ readLabel.addMouseListener(new MouseAdapter() {
+ @Override
+ public void mouseClicked(MouseEvent e) {
+ if (e.getClickCount() == 1) {
+ ((CardLayout)AbstractPropertyField.this.getLayout()).show(AbstractPropertyField.this, CARD_WRITE);
+ writeField.requestFocus();
+ writeField.selectAll();
+
+ Toolkit.getDefaultToolkit().addAWTEventListener(aeListener, AWTEvent.MOUSE_EVENT_MASK);
+ }
+ }
+ });
+
+ writeField.addFocusListener(new FocusAdapter() {
+ @Override
+ public void focusLost(FocusEvent e) {
+ finishEdit();
+ }
+ });
+ writeField.addKeyListener(new KeyAdapter() {
+ @Override
+ public void keyPressed(KeyEvent e) {
+ if (e.getKeyCode() == KeyEvent.VK_ENTER) {
+ e.consume();
+ finishEdit();
+ }
+ if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
+ e.consume();
+ cancelEdit();
+ }
+ }
+ });
+
+ aeListener = new AWTEventListener() {
+ @Override
+ public void eventDispatched(AWTEvent event) {
+ if (event instanceof MouseEvent) {
+ MouseEvent me = (MouseEvent) event;
+ if (!SwingUtilities.isDescendingFrom(me.getComponent(), writeField)) {
+ if (me.getClickCount() > 0) {
+ finishEdit();
+ }
+ }
+ }
+ }
+ };
+
+
+ }
+
+ protected abstract E textToValue(String text);
+ protected abstract String valueToText(E value);
+
+ private void finishEdit() {
+
+ String textBefore = readLabel.getText();
+ String textAfter = writeField.getText();
+
+ if (textBefore.equals(textAfter)) {
+ cancelEdit();
+ return;
+ }
+
+ Toolkit.getDefaultToolkit().removeAWTEventListener(aeListener);
+
+ boolean ok = true;
+ E value = textToValue(textAfter);
+ if (value == null) {
+ ok = false;
+ } else {
+ for (PropertyValidationInteface validation : validations) {
+ if (!validation.validate(value)) {
+ ok = false;
+ break;
+ }
+ }
+ }
+
+ if (!ok) {
+ cancelEdit();
+ return;
+ }
+ readLabel.setText(valueToText(value));
+ ((CardLayout)AbstractPropertyField.this.getLayout()).show(AbstractPropertyField.this, CARD_READ);
+ fireChange();
+ }
+
+ private void cancelEdit() {
+ Toolkit.getDefaultToolkit().removeAWTEventListener(aeListener);
+ writeField.setText(readLabel.getText());
+ ((CardLayout)AbstractPropertyField.this.getLayout()).show(AbstractPropertyField.this, CARD_READ);
+ }
+
+ public E getValue() {
+ return textToValue(writeField.getText());
+ }
+
+ public void setValue(E value) {
+ String text = valueToText(value);
+ readLabel.setText(text);
+ writeField.setText(text);
+ fireChange();
+ }
+}
+
+class DottedUnderlineLabel extends JLabel {
+
+ public DottedUnderlineLabel(String text) {
+ super(text);
+ }
+
+ @Override
+ protected void paintComponent(Graphics g) {
+ super.paintComponent(g);
+ Graphics2D g2d = (Graphics2D) g;
+ Font font = getFont();
+ FontMetrics metrics = getFontMetrics(font);
+ String text = getText();
+
+ int x = 0;
+ int y = metrics.getAscent();
+
+ g2d.setPaint(getForeground());
+
+ int textWidth = metrics.stringWidth(text);
+ int underlineY = y + 6;
+
+ float[] dash = {1f, 1f};
+ Stroke dotted = new BasicStroke(1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 1f, dash, 0f);
+ g2d.setStroke(dotted);
+
+ g2d.draw(new Line2D.Float(x, underlineY, x + textWidth, underlineY));
+ }
+}
\ No newline at end of file
diff --git a/src/com/jpexs/decompiler/flash/easygui/properties/FloatPropertyField.java b/src/com/jpexs/decompiler/flash/easygui/properties/FloatPropertyField.java
new file mode 100644
index 000000000..f012af5f5
--- /dev/null
+++ b/src/com/jpexs/decompiler/flash/easygui/properties/FloatPropertyField.java
@@ -0,0 +1,77 @@
+/*
+ * 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.properties;
+
+/**
+ *
+ * @author JPEXS
+ */
+public class FloatPropertyField extends AbstractPropertyField {
+
+ private PropertyValidationInteface minValidation = null;
+ private PropertyValidationInteface maxValidation = null;
+
+ public FloatPropertyField(float value, float min, float max) {
+ super("" + value);
+ setMin(min);
+ setMax(max);
+ }
+
+ public FloatPropertyField(float value) {
+ super("" + value);
+ }
+
+ public void setMax(float max) {
+ if (maxValidation != null) {
+ removeValidation(maxValidation);
+ }
+ maxValidation = new PropertyValidationInteface() {
+ @Override
+ public boolean validate(Float value) {
+ return value <= max;
+ }
+ };
+ addValidation(maxValidation);
+ }
+
+ public void setMin(float min) {
+ if (minValidation != null) {
+ removeValidation(minValidation);
+ }
+ minValidation = new PropertyValidationInteface() {
+ @Override
+ public boolean validate(Float value) {
+ return value >= min;
+ }
+ };
+ addValidation(minValidation);
+ }
+
+ @Override
+ protected Float textToValue(String text) {
+ try{
+ return Float.parseFloat(text);
+ }catch(NumberFormatException nfe){
+ return null;
+ }
+ }
+
+ @Override
+ protected String valueToText(Float value) {
+ return "" + value;
+ }
+}
diff --git a/src/com/jpexs/decompiler/flash/easygui/properties/IntegerPropertyField.java b/src/com/jpexs/decompiler/flash/easygui/properties/IntegerPropertyField.java
new file mode 100644
index 000000000..c568f2162
--- /dev/null
+++ b/src/com/jpexs/decompiler/flash/easygui/properties/IntegerPropertyField.java
@@ -0,0 +1,78 @@
+/*
+ * 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.properties;
+
+/**
+ *
+ * @author JPEXS
+ */
+public class IntegerPropertyField extends AbstractPropertyField {
+
+ private PropertyValidationInteface minValidation = null;
+ private PropertyValidationInteface maxValidation = null;
+
+ public IntegerPropertyField(int value, int min, int max) {
+ super("" + value);
+ setMin(min);
+ setMax(max);
+ writeField.setColumns(("" + max).length());
+ }
+
+ public IntegerPropertyField(int value) {
+ super("" + value);
+ }
+
+ public void setMax(int max) {
+ if (maxValidation != null) {
+ removeValidation(maxValidation);
+ }
+ maxValidation = new PropertyValidationInteface() {
+ @Override
+ public boolean validate(Integer value) {
+ return value <= max;
+ }
+ };
+ addValidation(maxValidation);
+ }
+
+ public void setMin(int min) {
+ if (minValidation != null) {
+ removeValidation(minValidation);
+ }
+ minValidation = new PropertyValidationInteface() {
+ @Override
+ public boolean validate(Integer value) {
+ return value >= min;
+ }
+ };
+ addValidation(minValidation);
+ }
+
+ @Override
+ protected Integer textToValue(String text) {
+ try {
+ return Integer.valueOf(text);
+ } catch (NumberFormatException nfe) {
+ return null;
+ }
+ }
+
+ @Override
+ protected String valueToText(Integer value) {
+ return "" + value;
+ }
+}
diff --git a/src/com/jpexs/decompiler/flash/easygui/properties/PropertyValidationInteface.java b/src/com/jpexs/decompiler/flash/easygui/properties/PropertyValidationInteface.java
new file mode 100644
index 000000000..f64d7959f
--- /dev/null
+++ b/src/com/jpexs/decompiler/flash/easygui/properties/PropertyValidationInteface.java
@@ -0,0 +1,26 @@
+/*
+ * 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.properties;
+
+/**
+ *
+ * @author JPEXS
+ * @param
+ */
+public interface PropertyValidationInteface {
+ public boolean validate(E value);
+}
diff --git a/src/com/jpexs/decompiler/flash/easygui/properties/TextPropertyField.java b/src/com/jpexs/decompiler/flash/easygui/properties/TextPropertyField.java
new file mode 100644
index 000000000..b069e5af2
--- /dev/null
+++ b/src/com/jpexs/decompiler/flash/easygui/properties/TextPropertyField.java
@@ -0,0 +1,48 @@
+/*
+ * 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.properties;
+
+/**
+ *
+ * @author JPEXS
+ */
+public class TextPropertyField extends AbstractPropertyField {
+
+ public TextPropertyField(String text, int maxLength) {
+ super(text);
+
+ writeField.setColumns(maxLength);
+
+ addValidation(new PropertyValidationInteface(){
+ @Override
+ public boolean validate(String value) {
+ return value.length() <= maxLength;
+ }
+ });
+ }
+
+ @Override
+ protected String textToValue(String text) {
+ return text;
+ }
+
+ @Override
+ protected String valueToText(String value) {
+ return value;
+ }
+
+}
diff --git a/src/com/jpexs/decompiler/flash/easygui/properties/panels/AbstractPropertiesPanel.java b/src/com/jpexs/decompiler/flash/easygui/properties/panels/AbstractPropertiesPanel.java
new file mode 100644
index 000000000..973e2d65d
--- /dev/null
+++ b/src/com/jpexs/decompiler/flash/easygui/properties/panels/AbstractPropertiesPanel.java
@@ -0,0 +1,148 @@
+/*
+ * 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.properties.panels;
+
+import com.jpexs.decompiler.flash.easygui.EasyStrings;
+import com.jpexs.decompiler.flash.gui.AppStrings;
+import com.jpexs.decompiler.flash.gui.View;
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import javax.swing.BorderFactory;
+import javax.swing.BoxLayout;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.SwingUtilities;
+import javax.swing.border.BevelBorder;
+
+/**
+ *
+ * @author JPEXS
+ */
+public abstract class AbstractPropertiesPanel extends JPanel {
+
+ protected String titleIdentifier;
+
+ private final Map cardContents = new LinkedHashMap<>();
+ private final Map cardPlusMinusLabels = new LinkedHashMap<>();
+
+ public AbstractPropertiesPanel(String titleIdentifier) {
+ setLayout(new BorderLayout());
+
+ JLabel documentLabel = new JLabel(EasyStrings.translate("properties." + titleIdentifier));
+ add(documentLabel, BorderLayout.NORTH);
+ this.titleIdentifier = titleIdentifier;
+ }
+
+ protected String formatPropertyName(String id) {
+ String item = EasyStrings.translate("property."+titleIdentifier+"." + id);
+ return EasyStrings.translate("property.label").replace("%item%", item);
+ }
+
+ protected JPanel makeCard(String id, String icon, JPanel contents) {
+ JPanel cardPanel = new JPanel();
+
+ JPanel headerPanel = new JPanel(new BorderLayout());
+ JLabel label = new JLabel(EasyStrings.translate("properties." + titleIdentifier + ".header." + id));
+ if (icon != null) {
+ label.setIcon(View.getIcon(icon));
+ }
+ label.setHorizontalAlignment(JLabel.CENTER);
+ headerPanel.add(label, BorderLayout.CENTER);
+ JLabel plusMinusLabel = new JLabel("+");
+ plusMinusLabel.setFont(plusMinusLabel.getFont().deriveFont(plusMinusLabel.getFont().getSize2D() * 1.8f));
+ plusMinusLabel.setHorizontalAlignment(JLabel.CENTER);
+ plusMinusLabel.setPreferredSize(new Dimension(30, 30));
+ headerPanel.add(plusMinusLabel, BorderLayout.WEST);
+ headerPanel.setBorder(BorderFactory.createBevelBorder(BevelBorder.RAISED));
+ headerPanel.setMaximumSize(new Dimension(Integer.MAX_VALUE, 40));
+ headerPanel.setMinimumSize(new Dimension(0, 40));
+ headerPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
+
+ cardPanel.setLayout(new BoxLayout(cardPanel, BoxLayout.Y_AXIS));
+ headerPanel.addMouseListener(new MouseAdapter() {
+ @Override
+ public void mousePressed(MouseEvent e) {
+ if (SwingUtilities.isLeftMouseButton(e)) {
+ setCardOpened(id, !isCardOpened(id));
+ cardPanel.revalidate();
+ cardPanel.repaint();
+ }
+ }
+ });
+ contents.setAlignmentX(Component.LEFT_ALIGNMENT);
+ contents.setVisible(false);
+ cardPanel.add(headerPanel);
+ cardPanel.add(contents);
+ contents.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
+ //contents.setMaximumSize(new Dimension(getPreferredSize().width, contents.getPreferredSize().height + 10));
+ cardPanel.setAlignmentX(Component.CENTER_ALIGNMENT);
+ cardPanel.setAlignmentY(Component.TOP_ALIGNMENT);
+ cardContents.put(id, contents);
+ cardPlusMinusLabels.put(id, plusMinusLabel);
+ return cardPanel;
+ }
+
+ private boolean isCardOpened(String id) {
+ return cardContents.get(id).isVisible();
+ }
+
+ private void setCardOpened(String id, boolean opened) {
+ JPanel contents = cardContents.get(id);
+ contents.setVisible(opened);
+ contents.setMaximumSize(new Dimension(Integer.MAX_VALUE, contents.getPreferredSize().height));
+ JLabel plusMinusLabel = cardPlusMinusLabels.get(id);
+ if (opened) {
+ plusMinusLabel.setText("-");
+ } else {
+ plusMinusLabel.setText("+");
+ }
+ }
+
+
+
+ protected void addToGrid(GridBagLayout layout, Container parent, Component component, int x, int y) {
+ addToGrid(layout, parent, component, x, y, 1, 1);
+ }
+
+ protected void addToGrid(GridBagLayout layout, Container parent, Component component, int x, int y, int w, int h) {
+ GridBagConstraints gbc = new GridBagConstraints();
+ gbc.anchor = GridBagConstraints.NORTHWEST;
+ gbc.gridx = x;
+ gbc.gridy = y;
+
+ gbc.gridwidth = w;
+ gbc.gridheight = h;
+ gbc.fill = GridBagConstraints.NONE;
+ gbc.insets = new Insets(3, 3, 3, 3);
+ gbc.weightx = 0;
+
+ if (x == 0 && w == 1) {
+ gbc.anchor = GridBagConstraints.EAST;
+ }
+
+ parent.add(component, gbc);
+ }
+}
diff --git a/src/com/jpexs/decompiler/flash/easygui/properties/panels/DocumentPropertiesPanel.java b/src/com/jpexs/decompiler/flash/easygui/properties/panels/DocumentPropertiesPanel.java
new file mode 100644
index 000000000..72f2f73de
--- /dev/null
+++ b/src/com/jpexs/decompiler/flash/easygui/properties/panels/DocumentPropertiesPanel.java
@@ -0,0 +1,448 @@
+/*
+ * 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.properties.panels;
+
+import com.jpexs.decompiler.flash.SWF;
+import com.jpexs.decompiler.flash.SWFCompression;
+import static com.jpexs.decompiler.flash.SWFCompression.LZMA;
+import static com.jpexs.decompiler.flash.SWFCompression.NONE;
+import static com.jpexs.decompiler.flash.SWFCompression.ZLIB;
+import com.jpexs.decompiler.flash.easygui.ChangeDoableOperation;
+import com.jpexs.decompiler.flash.easygui.DoableOperation;
+import com.jpexs.decompiler.flash.easygui.EasyStrings;
+import com.jpexs.decompiler.flash.easygui.UndoManager;
+import com.jpexs.decompiler.flash.easygui.properties.FloatPropertyField;
+import com.jpexs.decompiler.flash.easygui.properties.IntegerPropertyField;
+import com.jpexs.decompiler.flash.gui.AppStrings;
+import com.jpexs.decompiler.flash.gui.ComboBoxItem;
+import com.jpexs.decompiler.flash.gui.View;
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.util.ArrayList;
+import java.util.List;
+import javax.swing.JCheckBox;
+import javax.swing.JComboBox;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.SwingConstants;
+import javax.swing.event.ChangeEvent;
+
+/**
+ *
+ * @author JPEXS
+ */
+public class DocumentPropertiesPanel extends AbstractPropertiesPanel {
+
+ private final JLabel displayRectPixelsLabel = new JLabel();
+
+ private final JPanel compressionEditorPanel = new JPanel();
+
+ private final JComboBox> compressionComboBox = new JComboBox<>();
+
+ private final JPanel versionEditorPanel = new JPanel();
+
+ private final IntegerPropertyField versionEditor = new IntegerPropertyField(SWF.DEFAULT_VERSION, 1, 255);
+
+ private final JCheckBox gfxCheckBox = new JCheckBox();
+
+ private final JCheckBox encryptedCheckBox = new JCheckBox();
+
+ private final JPanel frameRateEditorPanel = new JPanel();
+
+ private final FloatPropertyField frameRateEditor = new FloatPropertyField(24f, 0.01f, 120f);
+
+ private final JPanel propertiesPanel = new JPanel();
+
+ private final JPanel displayRectEditorPanel = new JPanel();
+
+ private final IntegerPropertyField widthEditor = new IntegerPropertyField(550, 1, 8192);
+
+ private final IntegerPropertyField heightEditor = new IntegerPropertyField(400, 1, 8192);
+
+ private final JPanel warningPanel = new JPanel();
+
+ private final JLabel warningLabel = new JLabel();
+
+ private SWF swf;
+
+ private boolean modifying = false;
+
+ public DocumentPropertiesPanel(UndoManager undoManager) {
+ super("document");
+
+ FlowLayout layout = new FlowLayout(SwingConstants.WEST);
+ layout.setHgap(0);
+ layout.setVgap(0);
+
+ compressionEditorPanel.setLayout(layout);
+ compressionComboBox.addItem(new ComboBoxItem<>(AppStrings.translate("header.uncompressed"), SWFCompression.NONE));
+ compressionComboBox.addItem(new ComboBoxItem<>("Zlib", SWFCompression.ZLIB));
+ compressionComboBox.addItem(new ComboBoxItem<>("LZMA", SWFCompression.LZMA));
+ compressionComboBox.addActionListener((ActionEvent e) -> {
+ validateHeader();
+ });
+ compressionEditorPanel.add(compressionComboBox);
+
+ versionEditorPanel.setLayout(layout);
+ versionEditor.setPreferredSize(new Dimension(80, versionEditor.getPreferredSize().height));
+ versionEditor.addChangeListener((ChangeEvent e) -> {
+ validateHeader();
+ });
+ versionEditorPanel.add(versionEditor);
+
+ encryptedCheckBox.addChangeListener((ChangeEvent e) -> {
+ validateHeader();
+ });
+
+ gfxCheckBox.addChangeListener((ChangeEvent e) -> {
+ validateHeader();
+ });
+
+ frameRateEditorPanel.setLayout(layout);
+ frameRateEditor.setPreferredSize(new Dimension(80, frameRateEditor.getPreferredSize().height));
+ frameRateEditorPanel.add(frameRateEditor);
+
+ displayRectEditorPanel.setLayout(layout);
+ //displayRectEditorPanel.setMinimumSize(new Dimension(10, displayRectEditorPanel.getMinimumSize().height));
+ //widthEditor.setPreferredSize(new Dimension(80, widthEditor.getPreferredSize().height));
+ //heightEditor.setPreferredSize(new Dimension(80, heightEditor.getPreferredSize().height));
+ displayRectEditorPanel.add(widthEditor);
+ displayRectEditorPanel.add(new JLabel("\u00D7 "));
+ displayRectEditorPanel.add(heightEditor);
+ displayRectEditorPanel.add(new JLabel(" px"));
+
+ warningLabel.setIcon(View.getIcon("warning16"));
+ warningPanel.setLayout(layout);
+ warningPanel.setBackground(new Color(255, 213, 29));
+ warningPanel.add(warningLabel);
+
+ GridBagLayout gridBag = new GridBagLayout();
+ propertiesPanel.setLayout(gridBag);
+
+ addToGrid(gridBag, propertiesPanel, new JLabel(AppStrings.translate("header.compression")), 0, 1);
+ addToGrid(gridBag, propertiesPanel, compressionEditorPanel, 1, 1);
+ addToGrid(gridBag, propertiesPanel, new JLabel(AppStrings.translate("header.version")), 0, 2);
+ addToGrid(gridBag, propertiesPanel, versionEditorPanel, 1, 2);
+ addToGrid(gridBag, propertiesPanel, new JLabel(AppStrings.translate("header.encrypted")), 0, 3);
+ addToGrid(gridBag, propertiesPanel, encryptedCheckBox, 1, 3);
+ addToGrid(gridBag, propertiesPanel, new JLabel(AppStrings.translate("header.gfx")), 0, 4);
+ addToGrid(gridBag, propertiesPanel, gfxCheckBox, 1, 4);
+ addToGrid(gridBag, propertiesPanel, new JLabel(AppStrings.translate("header.framerate")), 0, 6);
+ addToGrid(gridBag, propertiesPanel, frameRateEditorPanel, 1, 6);
+ addToGrid(gridBag, propertiesPanel, new JLabel(AppStrings.translate("header.displayrect")), 0, 7);
+ addToGrid(gridBag, propertiesPanel, displayRectEditorPanel, 1, 7);
+ addToGrid(gridBag, propertiesPanel, new JLabel(""), 0, 8);
+ addToGrid(gridBag, propertiesPanel, displayRectPixelsLabel, 1, 8);
+ addToGrid(gridBag, propertiesPanel, warningPanel, 0, 9, 2, 1);
+
+ GridBagConstraints gbc = new GridBagConstraints();
+ gbc.gridx = 0;
+ gbc.gridy = 10;
+ gbc.gridwidth = 3;
+ gbc.weighty = 1;
+ gbc.fill = GridBagConstraints.BOTH;
+ propertiesPanel.add(new JPanel(), gbc);
+
+ gbc = new GridBagConstraints();
+ gbc.gridx = 2;
+ gbc.gridy = 0;
+ gbc.gridheight = 10;
+ gbc.weightx = 1;
+ gbc.fill = GridBagConstraints.HORIZONTAL;
+ propertiesPanel.add(new JPanel(), gbc);
+
+ add(propertiesPanel, BorderLayout.CENTER);
+
+ warningPanel.setVisible(false);
+
+ compressionComboBox.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ if (modifying) {
+ return;
+ }
+
+ undoManager.doOperation(new HeaderChangeDoableOperation("compression") {
+ SWFCompression itemBefore = swf.compression;
+ SWFCompression itemAfter = getCompression();
+
+ @Override
+ public void doHeaderOperation() {
+ swf.compression = itemAfter;
+ }
+
+ @Override
+ public void undoHeaderOperation() {
+ swf.compression = itemBefore;
+ }
+
+ }, swf);
+ }
+ });
+ versionEditor.addChangeListener((ChangeEvent e) -> {
+ if (modifying) {
+ return;
+ }
+ undoManager.doOperation(new HeaderChangeDoableOperation("swfVersion") {
+ int itemBefore = swf.version;
+ int itemAfter = versionEditor.getValue();
+
+ @Override
+ public void doHeaderOperation() {
+ swf.version = itemAfter;
+ }
+
+ @Override
+ public void undoHeaderOperation() {
+ swf.version = itemBefore;
+ }
+ }, swf);
+ });
+ encryptedCheckBox.addChangeListener((ChangeEvent e) -> {
+ if (modifying) {
+ return;
+ }
+ if (encryptedCheckBox.isSelected() == swf.encrypted) {
+ return;
+ }
+ undoManager.doOperation(new HeaderChangeDoableOperation("encrypted") {
+ boolean itemBefore = swf.encrypted;
+ boolean itemAfter = encryptedCheckBox.isSelected();
+ @Override
+ public void doHeaderOperation() {
+ swf.encrypted = itemAfter;
+ }
+
+ @Override
+ public void undoHeaderOperation() {
+ swf.encrypted = itemBefore;
+ }
+ }, swf);
+ });
+ gfxCheckBox.addChangeListener((ChangeEvent e) -> {
+ if (modifying) {
+ return;
+ }
+ if (gfxCheckBox.isSelected() == swf.gfx) {
+ return;
+ }
+
+ undoManager.doOperation(new HeaderChangeDoableOperation("gfx") {
+ boolean itemBefore = swf.gfx;
+ boolean itemAfter = gfxCheckBox.isSelected();
+
+ @Override
+ public void doHeaderOperation() {
+ swf.gfx = itemAfter;
+ }
+
+ @Override
+ public void undoHeaderOperation() {
+ swf.gfx = itemBefore;
+ }
+ }, swf);
+ });
+ frameRateEditor.addChangeListener((ChangeEvent e) -> {
+ if (modifying) {
+ return;
+ }
+ undoManager.doOperation(new HeaderChangeDoableOperation("frameRate") {
+ float itemBefore = swf.frameRate;
+ float itemAfter = frameRateEditor.getValue();
+
+ @Override
+ public void doHeaderOperation() {
+ swf.frameRate = itemAfter;
+ }
+
+ @Override
+ public void undoHeaderOperation() {
+ swf.frameRate = itemBefore;
+ }
+ }, swf);
+ });
+ widthEditor.addChangeListener((ChangeEvent e) -> {
+ if (modifying) {
+ return;
+ }
+ undoManager.doOperation(new HeaderChangeDoableOperation("width") {
+ int xMinBefore = swf.displayRect.Xmin;
+ int xMaxBefore = swf.displayRect.Xmax;
+ int xMaxAfter = widthEditor.getValue() * 20;
+
+ @Override
+ public void doHeaderOperation() {
+ swf.displayRect.Xmin = 0;
+ swf.displayRect.Xmax = xMaxAfter;
+ }
+
+ @Override
+ public void undoHeaderOperation() {
+ swf.displayRect.Xmin = xMinBefore;
+ swf.displayRect.Xmax = xMaxBefore;
+ }
+ }, swf);
+ });
+ heightEditor.addChangeListener((ChangeEvent e) -> {
+ if (modifying) {
+ return;
+ }
+ undoManager.doOperation(new HeaderChangeDoableOperation("height") {
+ int yMinBefore = swf.displayRect.Ymin;
+ int yMaxBefore = swf.displayRect.Ymax;
+ int yMaxAfter = heightEditor.getValue() * 20;
+
+ @Override
+ public void doHeaderOperation() {
+ swf.displayRect.Ymin = 0;
+ swf.displayRect.Ymax = yMaxAfter;
+ }
+
+ @Override
+ public void undoHeaderOperation() {
+ swf.displayRect.Ymin = yMinBefore;
+ swf.displayRect.Ymax = yMaxBefore;
+ }
+ }, swf);
+ });
+ }
+
+
+
+ private boolean validateHeader() {
+ int version = getVersionNumber();
+ boolean gfx = gfxCheckBox.isSelected();
+ boolean encrypted = encryptedCheckBox.isSelected();
+ SWFCompression compression = getCompression();
+ List results = new ArrayList<>();
+ if (gfx && !(compression == SWFCompression.NONE || compression == SWFCompression.ZLIB)) {
+ results.add(AppStrings.translate("header.warning.unsupportedGfxCompression"));
+ }
+
+ if (gfx && encrypted) {
+ results.add(AppStrings.translate("header.warning.unsupportedGfxEncryption"));
+ }
+
+ if (compression == SWFCompression.ZLIB && version < 6) {
+ results.add(AppStrings.translate("header.warning.minimumZlibVersion"));
+ }
+
+ if (compression == SWFCompression.LZMA && version < 13) {
+ results.add(AppStrings.translate("header.warning.minimumLzmaVersion"));
+ }
+
+ warningPanel.setVisible(!results.isEmpty());
+ if (!results.isEmpty()) {
+ warningLabel.setText("" + String.join("
", results) + "");
+ }
+
+ return results.isEmpty();
+ }
+
+ private int getVersionNumber() {
+ return versionEditor.getValue();
+ }
+
+ private SWFCompression getCompression() {
+ @SuppressWarnings("unchecked")
+ ComboBoxItem item = (ComboBoxItem) compressionComboBox.getSelectedItem();
+ return item.getValue();
+ }
+
+ private void refresh() {
+ modifying = true;
+ propertiesPanel.setVisible(swf != null);
+ if (swf == null) {
+ modifying = false;
+ return;
+ }
+ switch (swf.compression) {
+ case LZMA:
+ compressionComboBox.setSelectedIndex(2);
+ break;
+ case ZLIB:
+ compressionComboBox.setSelectedIndex(1);
+ break;
+ case NONE:
+ compressionComboBox.setSelectedIndex(0);
+ break;
+ }
+
+ versionEditor.setValue(swf.version);
+
+ encryptedCheckBox.setSelected(swf.encrypted);
+
+ gfxCheckBox.setSelected(swf.gfx);
+
+ frameRateEditor.setValue(swf.frameRate);
+
+ widthEditor.setValue(swf.displayRect.getWidth() / 20);
+ heightEditor.setValue(swf.displayRect.getHeight() / 20);
+
+ modifying = false;
+ }
+
+ public void setSwf(SWF swf) {
+
+ this.swf = swf;
+ refresh();
+ }
+
+ abstract class HeaderChangeDoableOperation extends ChangeDoableOperation {
+
+ private boolean modifiedBefore = swf.isHeaderModified();
+
+ public HeaderChangeDoableOperation(String itemIdentifier) {
+ super(itemIdentifier);
+ }
+
+ @Override
+ public final void doOperation() {
+ modifying = true;
+ doHeaderOperation();
+ swf.setHeaderModified(true);
+ refresh();
+ validateHeader();
+ modifying = false;
+ }
+
+ @Override
+ public final void undoOperation() {
+ modifying = true;
+ undoHeaderOperation();
+ swf.setHeaderModified(modifiedBefore);
+ refresh();
+ validateHeader();
+ modifying = false;
+ }
+
+ public abstract void doHeaderOperation();
+
+ public abstract void undoHeaderOperation();
+ }
+}
diff --git a/src/com/jpexs/decompiler/flash/easygui/properties/panels/GeneralPropertiesPanel.java b/src/com/jpexs/decompiler/flash/easygui/properties/panels/GeneralPropertiesPanel.java
new file mode 100644
index 000000000..fcf6a09d8
--- /dev/null
+++ b/src/com/jpexs/decompiler/flash/easygui/properties/panels/GeneralPropertiesPanel.java
@@ -0,0 +1,370 @@
+/*
+ * 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.properties.panels;
+
+import com.jpexs.decompiler.flash.easygui.ChangeDoableOperation;
+import com.jpexs.decompiler.flash.easygui.EasyStrings;
+import com.jpexs.decompiler.flash.easygui.EasySwfPanel;
+import com.jpexs.decompiler.flash.easygui.UndoManager;
+import com.jpexs.decompiler.flash.easygui.properties.IntegerPropertyField;
+import com.jpexs.decompiler.flash.gui.AppStrings;
+import com.jpexs.decompiler.flash.gui.ImagePanel;
+import com.jpexs.decompiler.flash.tags.base.PlaceObjectTypeTag;
+import com.jpexs.decompiler.flash.tags.converters.PlaceObjectTypeConverter;
+import com.jpexs.decompiler.flash.timeline.DepthState;
+import com.jpexs.decompiler.flash.timeline.Timelined;
+import com.jpexs.decompiler.flash.types.CXFORMWITHALPHA;
+import com.jpexs.decompiler.flash.types.ColorTransform;
+import java.awt.BorderLayout;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import javax.swing.BoxLayout;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+
+/**
+ *
+ * @author JPEXS
+ */
+public class GeneralPropertiesPanel extends AbstractPropertiesPanel {
+
+ private final IntegerPropertyField alphaPercentPropertyField = new IntegerPropertyField(100, -100, 100);
+ private final IntegerPropertyField redPercentPropertyField = new IntegerPropertyField(100, -100, 100);
+ private final IntegerPropertyField greenPercentPropertyField = new IntegerPropertyField(100, -100, 100);
+ private final IntegerPropertyField bluePercentPropertyField = new IntegerPropertyField(100, -100, 100);
+ private final IntegerPropertyField alphaAddPropertyField = new IntegerPropertyField(0, -255, 255);
+ private final IntegerPropertyField redAddPropertyField = new IntegerPropertyField(0, -255, 255);
+ private final IntegerPropertyField greenAddPropertyField = new IntegerPropertyField(0, -255, 255);
+ private final IntegerPropertyField blueAddPropertyField = new IntegerPropertyField(0, -255, 255);
+ private final EasySwfPanel swfPanel;
+
+ private final JPanel propertiesPanel;
+
+ private boolean updating = false;
+
+ public GeneralPropertiesPanel(EasySwfPanel swfPanel, UndoManager undoManager) {
+ super("general");
+
+ JPanel colorEffectPanel = new JPanel();
+
+ GridBagLayout gridBag = new GridBagLayout();
+ colorEffectPanel.setLayout(gridBag);
+
+ GridBagConstraints gbc = new GridBagConstraints();
+ gbc.weightx = 0;
+ gbc.insets = new Insets(3, 3, 3, 3);
+ gbc.gridx = 0;
+ gbc.gridy = 0;
+ gbc.anchor = GridBagConstraints.EAST;
+ colorEffectPanel.add(new JLabel(formatPropertyName("colorEffect.alpha")), gbc);
+ gbc.anchor = GridBagConstraints.WEST;
+ gbc.gridx++;
+ colorEffectPanel.add(alphaPercentPropertyField, gbc);
+ gbc.gridx++;
+ colorEffectPanel.add(new JLabel("% \u00D7 A + "), gbc);
+ gbc.gridx++;
+ colorEffectPanel.add(alphaAddPropertyField, gbc);
+
+ gbc.gridx = 0;
+ gbc.gridy++;
+ gbc.anchor = GridBagConstraints.EAST;
+ colorEffectPanel.add(new JLabel(formatPropertyName("colorEffect.red")), gbc);
+ gbc.anchor = GridBagConstraints.WEST;
+ gbc.gridx++;
+ colorEffectPanel.add(redPercentPropertyField, gbc);
+ gbc.gridx++;
+ colorEffectPanel.add(new JLabel("% \u00D7 R + "), gbc);
+ gbc.gridx++;
+ colorEffectPanel.add(redAddPropertyField, gbc);
+
+ gbc.gridx = 0;
+ gbc.gridy++;
+ gbc.anchor = GridBagConstraints.EAST;
+ colorEffectPanel.add(new JLabel(formatPropertyName("colorEffect.green")), gbc);
+ gbc.anchor = GridBagConstraints.WEST;
+ gbc.gridx++;
+ colorEffectPanel.add(greenPercentPropertyField, gbc);
+ gbc.gridx++;
+ colorEffectPanel.add(new JLabel("% \u00D7 G + "), gbc);
+ gbc.gridx++;
+ colorEffectPanel.add(greenAddPropertyField, gbc);
+
+ gbc.gridx = 0;
+ gbc.gridy++;
+ gbc.anchor = GridBagConstraints.EAST;
+ colorEffectPanel.add(new JLabel(formatPropertyName("colorEffect.blue")), gbc);
+ gbc.anchor = GridBagConstraints.WEST;
+ gbc.gridx++;
+ colorEffectPanel.add(bluePercentPropertyField, gbc);
+ gbc.gridx++;
+ colorEffectPanel.add(new JLabel("% \u00D7 B + "), gbc);
+ gbc.gridx++;
+ colorEffectPanel.add(blueAddPropertyField, gbc);
+
+ gbc.gridx++;
+ gbc.gridy = 0;
+ gbc.gridheight = 4;
+ gbc.weightx = 1;
+ gbc.fill = GridBagConstraints.HORIZONTAL;
+ colorEffectPanel.add(new JPanel(), gbc);
+
+
+ propertiesPanel = new JPanel();
+ propertiesPanel.setLayout(new BoxLayout(propertiesPanel, BoxLayout.Y_AXIS));
+
+ propertiesPanel.add(makeCard("colorEffect", null, colorEffectPanel));
+ this.swfPanel = swfPanel;
+
+ alphaPercentPropertyField.addChangeListener(new ChangeListener() {
+ @Override
+ public void stateChanged(ChangeEvent e) {
+ if (updating) {
+ return;
+ }
+ undoManager.doOperation(new ColorEffectChangeDoableOperation() {
+ int value = alphaPercentPropertyField.getValue();
+ @Override
+ public void doColorEffectOperation(CXFORMWITHALPHA colorTransform) {
+ colorTransform.alphaMultTerm = value * 256 / 100;
+ colorTransform.hasMultTerms = true;
+ }
+ }, swfPanel.getSwf());
+ }
+ });
+ alphaAddPropertyField.addChangeListener(new ChangeListener() {
+ @Override
+ public void stateChanged(ChangeEvent e) {
+ if (updating) {
+ return;
+ }
+ undoManager.doOperation(new ColorEffectChangeDoableOperation() {
+ int value = alphaAddPropertyField.getValue();
+ @Override
+ public void doColorEffectOperation(CXFORMWITHALPHA colorTransform) {
+ colorTransform.alphaAddTerm = value;
+ colorTransform.hasAddTerms = true;
+ }
+ }, swfPanel.getSwf());
+ }
+ });
+
+ redPercentPropertyField.addChangeListener(new ChangeListener() {
+ @Override
+ public void stateChanged(ChangeEvent e) {
+ if (updating) {
+ return;
+ }
+ undoManager.doOperation(new ColorEffectChangeDoableOperation() {
+ int value = redPercentPropertyField.getValue();
+ @Override
+ public void doColorEffectOperation(CXFORMWITHALPHA colorTransform) {
+ colorTransform.redMultTerm = value * 256 / 100;
+ colorTransform.hasMultTerms = true;
+ }
+ }, swfPanel.getSwf());
+ }
+ });
+ redAddPropertyField.addChangeListener(new ChangeListener() {
+ @Override
+ public void stateChanged(ChangeEvent e) {
+ if (updating) {
+ return;
+ }
+ undoManager.doOperation(new ColorEffectChangeDoableOperation() {
+ int value = redAddPropertyField.getValue();
+ @Override
+ public void doColorEffectOperation(CXFORMWITHALPHA colorTransform) {
+ colorTransform.redAddTerm = value;
+ colorTransform.hasAddTerms = true;
+ }
+ }, swfPanel.getSwf());
+ }
+ });
+
+ greenPercentPropertyField.addChangeListener(new ChangeListener() {
+ @Override
+ public void stateChanged(ChangeEvent e) {
+ if (updating) {
+ return;
+ }
+ undoManager.doOperation(new ColorEffectChangeDoableOperation() {
+ int value = greenPercentPropertyField.getValue();
+ @Override
+ public void doColorEffectOperation(CXFORMWITHALPHA colorTransform) {
+ colorTransform.greenMultTerm = value * 256 / 100;
+ colorTransform.hasMultTerms = true;
+ }
+ }, swfPanel.getSwf());
+ }
+ });
+ greenAddPropertyField.addChangeListener(new ChangeListener() {
+ @Override
+ public void stateChanged(ChangeEvent e) {
+ if (updating) {
+ return;
+ }
+ undoManager.doOperation(new ColorEffectChangeDoableOperation() {
+ int value = greenAddPropertyField.getValue();
+ @Override
+ public void doColorEffectOperation(CXFORMWITHALPHA colorTransform) {
+ colorTransform.greenAddTerm = value;
+ colorTransform.hasAddTerms = true;
+ }
+ }, swfPanel.getSwf());
+ }
+ });
+
+ bluePercentPropertyField.addChangeListener(new ChangeListener() {
+ @Override
+ public void stateChanged(ChangeEvent e) {
+ if (updating) {
+ return;
+ }
+ undoManager.doOperation(new ColorEffectChangeDoableOperation() {
+ int value = bluePercentPropertyField.getValue();
+ @Override
+ public void doColorEffectOperation(CXFORMWITHALPHA colorTransform) {
+ colorTransform.blueMultTerm = value * 256 / 100;
+ colorTransform.hasMultTerms = true;
+ }
+ }, swfPanel.getSwf());
+ }
+ });
+ blueAddPropertyField.addChangeListener(new ChangeListener() {
+ @Override
+ public void stateChanged(ChangeEvent e) {
+ if (updating) {
+ return;
+ }
+ undoManager.doOperation(new ColorEffectChangeDoableOperation() {
+ int value = blueAddPropertyField.getValue();
+ @Override
+ public void doColorEffectOperation(CXFORMWITHALPHA colorTransform) {
+ colorTransform.blueAddTerm = value;
+ colorTransform.hasAddTerms = true;
+ }
+ }, swfPanel.getSwf());
+ }
+ });
+
+ add(propertiesPanel, BorderLayout.CENTER);;
+
+ }
+
+ public void update() {
+ updating = true;
+ DepthState ds = swfPanel.getSelectedDepthState();
+ if (ds == null || ds.placeObjectTag == null) {
+ propertiesPanel.setVisible(false);
+ return;
+ }
+ propertiesPanel.setVisible(true);
+ ColorTransform colorTransform = ds.colorTransForm;
+ if (colorTransform == null) {
+ alphaPercentPropertyField.setValue(100);
+ alphaAddPropertyField.setValue(0);
+ redPercentPropertyField.setValue(100);
+ redAddPropertyField.setValue(0);
+ greenPercentPropertyField.setValue(100);
+ greenAddPropertyField.setValue(0);
+ bluePercentPropertyField.setValue(100);
+ blueAddPropertyField.setValue(0);
+ } else {
+ alphaPercentPropertyField.setValue(colorTransform.getAlphaMulti() * 100 / 256);
+ alphaAddPropertyField.setValue(colorTransform.getAlphaAdd());
+ redPercentPropertyField.setValue(colorTransform.getRedMulti() * 100 / 256);
+ redAddPropertyField.setValue(colorTransform.getRedAdd());
+ greenPercentPropertyField.setValue(colorTransform.getGreenMulti() * 100 / 256);
+ greenAddPropertyField.setValue(colorTransform.getGreenAdd());
+ bluePercentPropertyField.setValue(colorTransform.getBlueMulti() * 100 / 256);
+ blueAddPropertyField.setValue(colorTransform.getBlueAdd());
+ }
+ updating = false;
+ }
+
+ abstract class PlaceChangeDoableOperation extends ChangeDoableOperation {
+
+ int fdepth = swfPanel.getDepth();
+ int fframe = swfPanel.getFrame();
+
+ DepthState depthStateBefore = swfPanel.getSelectedDepthState();
+ PlaceObjectTypeTag placeObjectBefore = depthStateBefore.placeObjectTag;
+ PlaceObjectTypeTag placeObjectAfter = null;
+ private final int minPlace;
+
+ private final boolean timelinedModifiedBefore = placeObjectBefore.getTimelined().isModified();
+
+ public PlaceChangeDoableOperation(String itemIdentifier, int minPlace) {
+ super(itemIdentifier);
+ this.minPlace = minPlace;
+ }
+
+ @Override
+ public final void doOperation() {
+ swfPanel.getStagePanel().gotoFrame(fframe + 1);
+ swfPanel.getStagePanel().selectDepth(fdepth);
+
+ int convNum = placeObjectBefore.getPlaceObjectNum() < minPlace ? minPlace : placeObjectBefore.getPlaceObjectNum();
+ PlaceObjectTypeConverter conv = new PlaceObjectTypeConverter();
+ placeObjectAfter = conv.convertTagType(placeObjectBefore, convNum);
+
+ doPlaceOperation(placeObjectAfter, depthStateBefore);
+ placeObjectAfter.getTimelined().resetTimeline();
+ update();
+ }
+
+ @Override
+ public final void undoOperation() {
+ swfPanel.getStagePanel().gotoFrame(fframe + 1);
+ swfPanel.getStagePanel().selectDepth(fdepth);
+ int index = placeObjectAfter.getTimelined().indexOfTag(placeObjectAfter);
+ Timelined timelined = placeObjectAfter.getTimelined();
+ timelined.removeTag(index);
+ timelined.addTag(index, placeObjectBefore);
+ if (!timelinedModifiedBefore) {
+ timelined.setModified(false);
+ }
+ timelined.resetTimeline();
+
+ update();
+ }
+
+ public abstract void doPlaceOperation(PlaceObjectTypeTag placeObject, DepthState depthState);
+ }
+
+ abstract class ColorEffectChangeDoableOperation extends PlaceChangeDoableOperation {
+
+ CXFORMWITHALPHA colorTransformAfter;
+
+ public ColorEffectChangeDoableOperation() {
+ super("colorEffect", 2);
+ }
+
+ @Override
+ public final void doPlaceOperation(PlaceObjectTypeTag placeObject, DepthState depthState) {
+ colorTransformAfter = depthState.colorTransForm == null ? new CXFORMWITHALPHA() : new CXFORMWITHALPHA(depthState.colorTransForm);
+ doColorEffectOperation(colorTransformAfter);
+ placeObject.setColorTransform(colorTransformAfter);
+ }
+
+ public abstract void doColorEffectOperation(CXFORMWITHALPHA colorTransform);
+ }
+
+}
diff --git a/src/com/jpexs/decompiler/flash/gui/locales/EasyPanel.properties b/src/com/jpexs/decompiler/flash/gui/locales/EasyPanel.properties
index 858dfef55..b852b409e 100644
--- a/src/com/jpexs/decompiler/flash/gui/locales/EasyPanel.properties
+++ b/src/com/jpexs/decompiler/flash/gui/locales/EasyPanel.properties
@@ -53,7 +53,36 @@ action.removeFrame = Remove frame
action.transform = Transform
action.move = Move
action.addToStage = Add to stage
+action.change = Change %item%
+
+action.change.compression = Compression
+action.change.swfVersion = SWF version
+action.change.encrypted = Encrypted
+action.change.swfVersion = SWF version
+action.change.gfx = GFX
+action.change.frameRate = Frame rate
+action.change.width = Width
+action.change.height = Height
+action.change.colorEffect = Color effect
+
+
timeline.main = Main timeline
timeline.item = Timeline of %item%
-timeline.item.cancel = Cancel - Click to go to main timeline
\ No newline at end of file
+timeline.item.cancel = Cancel - Click to go to main timeline
+
+properties = Properties
+properties.document = Document
+properties.general = General
+
+properties.general.header.colorEffect = Color effect
+
+property.label = %item%:
+
+property.general.item = Item
+property.general.colorEffect.alpha = Alpha
+property.general.colorEffect.red = Red
+property.general.colorEffect.green = Green
+property.general.colorEffect.blue = Blue
+
+