From 3b055802a7d84d33f52cd9cacbbe4e64e2509361 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jindra=20Pet=C5=99=C3=ADk?= Date: Sat, 12 Oct 2024 23:08:14 +0200 Subject: [PATCH] Properties panel. Document properties. Color effect instance property. --- .../flash/tags/PlaceObject2Tag.java | 6 + .../flash/tags/PlaceObject3Tag.java | 7 +- .../flash/tags/PlaceObject4Tag.java | 6 + .../decompiler/flash/tags/PlaceObjectTag.java | 5 + .../flash/tags/base/PlaceObjectTypeTag.java | 6 + .../flash/types/CXFORMWITHALPHA.java | 6 +- .../flash/easygui/ChangeDoableOperation.java | 36 ++ .../flash/easygui/EasySwfPanel.java | 76 ++- .../properties/AbstractPropertyField.java | 227 +++++++++ .../properties/FloatPropertyField.java | 77 +++ .../properties/IntegerPropertyField.java | 78 +++ .../PropertyValidationInteface.java | 26 + .../easygui/properties/TextPropertyField.java | 48 ++ .../panels/AbstractPropertiesPanel.java | 148 ++++++ .../panels/DocumentPropertiesPanel.java | 448 ++++++++++++++++++ .../panels/GeneralPropertiesPanel.java | 370 +++++++++++++++ .../flash/gui/locales/EasyPanel.properties | 31 +- 17 files changed, 1597 insertions(+), 4 deletions(-) create mode 100644 src/com/jpexs/decompiler/flash/easygui/ChangeDoableOperation.java create mode 100644 src/com/jpexs/decompiler/flash/easygui/properties/AbstractPropertyField.java create mode 100644 src/com/jpexs/decompiler/flash/easygui/properties/FloatPropertyField.java create mode 100644 src/com/jpexs/decompiler/flash/easygui/properties/IntegerPropertyField.java create mode 100644 src/com/jpexs/decompiler/flash/easygui/properties/PropertyValidationInteface.java create mode 100644 src/com/jpexs/decompiler/flash/easygui/properties/TextPropertyField.java create mode 100644 src/com/jpexs/decompiler/flash/easygui/properties/panels/AbstractPropertiesPanel.java create mode 100644 src/com/jpexs/decompiler/flash/easygui/properties/panels/DocumentPropertiesPanel.java create mode 100644 src/com/jpexs/decompiler/flash/easygui/properties/panels/GeneralPropertiesPanel.java 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 + +