diff --git a/CHANGELOG.md b/CHANGELOG.md index db63a11f0..fe3a6a3d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ All notable changes to this project will be documented in this file. - Allow obfuscated DefineEditText variable identifiers - Selectable text (DefineEditTexts with noselect=0), copy to clipboard, select all - AS1/2 P-code curly braces pair highlighting +- [#2478] Simple editor - editing texts (not WYSIWYG, textual as in classic editor) ### Fixed - [#2474] Gotos incorrectly decompiled @@ -3899,6 +3900,7 @@ Major version of SWF to XML export changed to 2. [alpha 8]: https://github.com/jindrapetrik/jpexs-decompiler/compare/alpha7...alpha8 [alpha 7]: https://github.com/jindrapetrik/jpexs-decompiler/releases/tag/alpha7 [#2477]: https://www.free-decompiler.com/flash/issues/2477 +[#2478]: https://www.free-decompiler.com/flash/issues/2478 [#2474]: https://www.free-decompiler.com/flash/issues/2474 [#2480]: https://www.free-decompiler.com/flash/issues/2480 [#2476]: https://www.free-decompiler.com/flash/issues/2476 diff --git a/src/com/jpexs/decompiler/flash/easygui/EasySwfPanel.java b/src/com/jpexs/decompiler/flash/easygui/EasySwfPanel.java index 8b8a39b97..a65868259 100644 --- a/src/com/jpexs/decompiler/flash/easygui/EasySwfPanel.java +++ b/src/com/jpexs/decompiler/flash/easygui/EasySwfPanel.java @@ -104,11 +104,18 @@ public class EasySwfPanel extends JPanel { private static final String PROPERTIES_DOCUMENT = "Document"; private static final String PROPERTIES_INSTANCE = "Instance"; + private static final String PROPERTIES_TEXT = "Text"; private DocumentPropertiesPanel documentPropertiesPanel; private InstancePropertiesPanel instancePropertiesPanel; + private InstancePropertiesPanel textInstancePropertiesPanel; private final MainPanel mainPanel; + /** + * + * @param mainPanel + */ public EasySwfPanel(MainPanel mainPanel) { + this.mainPanel = mainPanel; setLayout(new BorderLayout()); stagePanel = new ImagePanel(); @@ -476,9 +483,11 @@ public class EasySwfPanel extends JPanel { documentPropertiesPanel = new DocumentPropertiesPanel(undoManager); propertiesPanel.setLayout(new CardLayout()); - instancePropertiesPanel = new InstancePropertiesPanel(this, undoManager); + instancePropertiesPanel = new InstancePropertiesPanel(this, undoManager, false); + textInstancePropertiesPanel = new InstancePropertiesPanel(this, undoManager, true); propertiesPanel.add(documentPropertiesPanel, PROPERTIES_DOCUMENT); propertiesPanel.add(instancePropertiesPanel, PROPERTIES_INSTANCE); + propertiesPanel.add(textInstancePropertiesPanel, PROPERTIES_TEXT); rightTabbedPane.addTab(EasyStrings.translate("properties"), propertiesPanel); @@ -553,9 +562,12 @@ public class EasySwfPanel extends JPanel { } } }); - this.mainPanel = mainPanel; } + public MainPanel getMainPanel() { + return mainPanel; + } + private void updatePropertiesPanel() { CardLayout cl = (CardLayout) propertiesPanel.getLayout(); List places = getSelectedPlaceTags(); @@ -563,6 +575,16 @@ public class EasySwfPanel extends JPanel { cl.show(propertiesPanel, PROPERTIES_DOCUMENT); return; } + if (places.size() == 1) { + int chid = places.get(0).getCharacterId(); + if (chid > -1) { + if (places.get(0).getSwf().getCharacter(chid) instanceof TextTag) { + textInstancePropertiesPanel.update(); + cl.show(propertiesPanel, PROPERTIES_TEXT); + return; + } + } + } instancePropertiesPanel.update(); cl.show(propertiesPanel, PROPERTIES_INSTANCE); } @@ -592,6 +614,7 @@ public class EasySwfPanel extends JPanel { timelineLabel.setText(""); documentPropertiesPanel.setSwf(null); instancePropertiesPanel.update(); + textInstancePropertiesPanel.update(); } else { SWF swf = timelined.getSwf(); documentPropertiesPanel.setSwf(swf); @@ -617,6 +640,7 @@ public class EasySwfPanel extends JPanel { closeTimelineButton.setVisible(true); } instancePropertiesPanel.update(); + textInstancePropertiesPanel.update(); } updateUndos(); } @@ -645,7 +669,8 @@ public class EasySwfPanel extends JPanel { if (stagePanel.getTimelined() == null) { return; } - Main.getMainFrame().getPanel().updateUiWithCurrentOpenable(); + updatePropertiesPanel(); + Main.getMainFrame().getPanel().updateUiWithCurrentOpenable(); } public void dispose() { diff --git a/src/com/jpexs/decompiler/flash/easygui/properties/panels/AbstractPropertiesPanel.java b/src/com/jpexs/decompiler/flash/easygui/properties/panels/AbstractPropertiesPanel.java index 56651205d..636e97b10 100644 --- a/src/com/jpexs/decompiler/flash/easygui/properties/panels/AbstractPropertiesPanel.java +++ b/src/com/jpexs/decompiler/flash/easygui/properties/panels/AbstractPropertiesPanel.java @@ -44,6 +44,7 @@ public abstract class AbstractPropertiesPanel extends JPanel { protected String titleIdentifier; + private final Map cardHeaders = new LinkedHashMap<>(); private final Map cardContents = new LinkedHashMap<>(); private final Map cardPlusMinusLabels = new LinkedHashMap<>(); @@ -114,6 +115,7 @@ public abstract class AbstractPropertiesPanel extends JPanel { //contents.setMaximumSize(new Dimension(getPreferredSize().width, contents.getPreferredSize().height + 10)); //cardPanel.setAlignmentX(Component.CENTER_ALIGNMENT); //cardPanel.setAlignmentY(Component.TOP_ALIGNMENT); + cardHeaders.put(id, headerPanel); cardContents.put(id, contents); cardPlusMinusLabels.put(id, plusMinusLabel); @@ -143,6 +145,15 @@ public abstract class AbstractPropertiesPanel extends JPanel { plusMinusLabel.setText("" + PLUS_CHAR); } } + + protected void setCardVisible(String id, boolean visible) { + JPanel contents = cardContents.get(id); + if (!visible) { + contents.setVisible(false); + } + JPanel header = cardHeaders.get(id); + header.setVisible(visible); + } protected void addToGrid(GridBagLayout layout, Container parent, Component component, int x, int y) { addToGrid(layout, parent, component, x, y, 1, 1); diff --git a/src/com/jpexs/decompiler/flash/easygui/properties/panels/InstancePropertiesPanel.java b/src/com/jpexs/decompiler/flash/easygui/properties/panels/InstancePropertiesPanel.java index 9129c456d..39fd23e0d 100644 --- a/src/com/jpexs/decompiler/flash/easygui/properties/panels/InstancePropertiesPanel.java +++ b/src/com/jpexs/decompiler/flash/easygui/properties/panels/InstancePropertiesPanel.java @@ -29,12 +29,16 @@ import com.jpexs.decompiler.flash.easygui.properties.PropertyChangeDoableOperati import com.jpexs.decompiler.flash.easygui.properties.PropertyValidationInterface; import com.jpexs.decompiler.flash.exporters.commonshape.Matrix; import com.jpexs.decompiler.flash.gui.BoundsChangeListener; +import com.jpexs.decompiler.flash.gui.FasterScrollPane; import com.jpexs.decompiler.flash.gui.PopupButton; import com.jpexs.decompiler.flash.gui.RegistrationPointPosition; +import com.jpexs.decompiler.flash.gui.TextPanel; import com.jpexs.decompiler.flash.gui.View; import com.jpexs.decompiler.flash.gui.ViewMessages; import com.jpexs.decompiler.flash.tags.base.ButtonTag; +import com.jpexs.decompiler.flash.tags.base.CharacterTag; import com.jpexs.decompiler.flash.tags.base.PlaceObjectTypeTag; +import com.jpexs.decompiler.flash.tags.base.TextTag; import com.jpexs.decompiler.flash.tags.converters.PlaceObjectTypeConverter; import com.jpexs.decompiler.flash.timeline.DepthState; import com.jpexs.decompiler.flash.timeline.Timelined; @@ -79,6 +83,7 @@ import javax.swing.JMenuItem; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.JScrollPane; +import javax.swing.ScrollPaneConstants; import javax.swing.SwingUtilities; import javax.swing.border.BevelBorder; import javax.swing.event.ChangeEvent; @@ -123,6 +128,8 @@ public class InstancePropertiesPanel extends AbstractPropertiesPanel { private final JComboBox backgroundComboBox = new JComboBox<>(); private final JPanel backgroundColorPanel = new JPanel(); private final JLabel backgroundColorLabel = new JLabel(); + + private final TextPanel textPanel; private final FiltersTreeTable filtersTable; @@ -132,7 +139,7 @@ public class InstancePropertiesPanel extends AbstractPropertiesPanel { - public InstancePropertiesPanel(EasySwfPanel swfPanel, UndoManager undoManager) { + public InstancePropertiesPanel(EasySwfPanel swfPanel, UndoManager undoManager, boolean withText) { super("instance"); setLayout(new BorderLayout()); @@ -245,7 +252,7 @@ public class InstancePropertiesPanel extends AbstractPropertiesPanel { gbc.weightx = 1; gbc.fill = GridBagConstraints.HORIZONTAL; colorEffectPanel.add(new JPanel(), gbc); - + JPanel displayPanel = new JPanel(); gridBag = new GridBagLayout(); displayPanel.setLayout(gridBag); @@ -371,7 +378,9 @@ public class InstancePropertiesPanel extends AbstractPropertiesPanel { gbc.fill = GridBagConstraints.HORIZONTAL; displayPanel.add(new JPanel(), gbc); + textPanel = new TextPanel(swfPanel.getMainPanel(), undoManager); JPanel filtersPanel = new JPanel(new BorderLayout()); + filtersPanel.setPreferredSize(new Dimension(400, 200)); filtersTable = new FiltersTreeTable(); JPanel filtersToolbar = new JPanel(new FlowLayout(FlowLayout.LEFT)); @@ -562,7 +571,10 @@ public class InstancePropertiesPanel extends AbstractPropertiesPanel { addCard(propertiesPanel, "positionSize", null, positionSizePanel, gbc, false); addCard(propertiesPanel, "colorEffect", null, colorEffectPanel, gbc, false); addCard(propertiesPanel, "display", null, displayPanel, gbc, false); - addCard(propertiesPanel, "filters", null, filtersPanel, gbc, true); + addCard(propertiesPanel, "filters", null, filtersPanel, gbc, !withText); + if (withText) { + addCard(propertiesPanel, "text", null, textPanel, gbc, true); + } setCardOpened("positionSize", true); @@ -865,7 +877,10 @@ public class InstancePropertiesPanel extends AbstractPropertiesPanel { } }); - add(propertiesPanel, BorderLayout.CENTER); + JScrollPane sp1 = new FasterScrollPane(propertiesPanel); + sp1.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); + + add(sp1, BorderLayout.CENTER); swfPanel.getStagePanel().addBoundsChangeListener(new BoundsChangeListener() { @Override @@ -925,7 +940,8 @@ public class InstancePropertiesPanel extends AbstractPropertiesPanel { Set> filters = new HashSet<>(); Set ratio = new HashSet<>(); - + TextTag text = null; + for (DepthState ds : dss) { if (ds == null) { continue; @@ -960,8 +976,16 @@ public class InstancePropertiesPanel extends AbstractPropertiesPanel { backgroundColor.add(ds.backGroundColor); filters.add(ds.filters); ratio.add(ds.ratio == -1 ? 0 : ds.ratio); + CharacterTag ch = ds.getCharacter(); + if (ch instanceof TextTag) { + text = (TextTag) ch; + } } + if (dss.size() > 1) { + text = null; + } + if (visible.size() == 0) { return; } @@ -1056,6 +1080,10 @@ public class InstancePropertiesPanel extends AbstractPropertiesPanel { } else { filtersTable.setFilters(null); } + + if (text != null) { + textPanel.setText(text); + } updating = false; revalidate(); } diff --git a/src/com/jpexs/decompiler/flash/gui/FontPanel.java b/src/com/jpexs/decompiler/flash/gui/FontPanel.java index 712a4eec5..414cbe90d 100644 --- a/src/com/jpexs/decompiler/flash/gui/FontPanel.java +++ b/src/com/jpexs/decompiler/flash/gui/FontPanel.java @@ -236,7 +236,7 @@ public class FontPanel extends JPanel implements TagEditorPanel { TextTag textTag = (TextTag) tag; if (textTag.getFontIds().contains(fontId)) { String text = textTag.getFormattedText(true).text; - mainPanel.saveText(textTag, text, null, null); + mainPanel.saveText(textTag, text, null, null, null); } } } diff --git a/src/com/jpexs/decompiler/flash/gui/MainPanel.java b/src/com/jpexs/decompiler/flash/gui/MainPanel.java index 40fb528e3..563c3c833 100644 --- a/src/com/jpexs/decompiler/flash/gui/MainPanel.java +++ b/src/com/jpexs/decompiler/flash/gui/MainPanel.java @@ -46,7 +46,10 @@ import com.jpexs.decompiler.flash.configuration.CustomConfigurationKeys; import com.jpexs.decompiler.flash.configuration.SwfSpecificCustomConfiguration; import com.jpexs.decompiler.flash.dumpview.DumpInfo; import com.jpexs.decompiler.flash.dumpview.DumpInfoSwfNode; +import com.jpexs.decompiler.flash.easygui.DoableOperation; import com.jpexs.decompiler.flash.easygui.EasyPanel; +import com.jpexs.decompiler.flash.easygui.EasyStrings; +import com.jpexs.decompiler.flash.easygui.UndoManager; import com.jpexs.decompiler.flash.exporters.BinaryDataExporter; import com.jpexs.decompiler.flash.exporters.Font4Exporter; import com.jpexs.decompiler.flash.exporters.FontExporter; @@ -5024,7 +5027,42 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se }; } - public boolean saveText(TextTag textTag, String formattedText, String[] texts, LineMarkedEditorPane editor) { + public boolean saveText(TextTag textTag, String formattedText, String[] texts, LineMarkedEditorPane editor, UndoManager undoManager) { + if (undoManager != null) { + String prevText = textTag.getFormattedText(false).text; + if (saveTextInternal(textTag, formattedText, texts, editor)) { + undoManager.doOperation(new DoableOperation() { + + boolean first = true; + + @Override + public void doOperation() { + if (first) { + first = false; + return; + } + saveTextInternal(textTag, formattedText, texts, editor); + } + + @Override + public void undoOperation() { + saveTextInternal(textTag, prevText, texts, editor); + } + + @Override + public String getDescription() { + return EasyStrings.translate("action.change").replace("%item%", EasyStrings.translate("action.change.text")); + } + }, textTag.getSwf()); + return true; + } + return false; + } + return saveTextInternal(textTag, formattedText, texts, editor); + } + + private boolean saveTextInternal(TextTag textTag, String formattedText, String[] texts, LineMarkedEditorPane editor) { + try { if (textTag.setFormattedText(getMissingCharacterHandler(), formattedText, texts)) { return true; diff --git a/src/com/jpexs/decompiler/flash/gui/PreviewPanel.java b/src/com/jpexs/decompiler/flash/gui/PreviewPanel.java index 464022ae0..58d8dda47 100644 --- a/src/com/jpexs/decompiler/flash/gui/PreviewPanel.java +++ b/src/com/jpexs/decompiler/flash/gui/PreviewPanel.java @@ -386,7 +386,7 @@ public class PreviewPanel extends JPersistentSplitPane implements TagEditorPanel private void createParametersPanel() { displayWithPreview = new JPanel(new CardLayout()); - textPanel = new TextPanel(mainPanel); + textPanel = new TextPanel(mainPanel, null); displayWithPreview.add(textPanel, CARDTEXTPANEL); fontPanel = new FontPanel(mainPanel); diff --git a/src/com/jpexs/decompiler/flash/gui/TextPanel.java b/src/com/jpexs/decompiler/flash/gui/TextPanel.java index 1beda6b69..c717d85f1 100644 --- a/src/com/jpexs/decompiler/flash/gui/TextPanel.java +++ b/src/com/jpexs/decompiler/flash/gui/TextPanel.java @@ -18,6 +18,9 @@ package com.jpexs.decompiler.flash.gui; import com.jpexs.decompiler.flash.SWF; import com.jpexs.decompiler.flash.configuration.Configuration; +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.gui.controls.JRepeatButton; import com.jpexs.decompiler.flash.gui.editor.LineMarkedEditorPane; import com.jpexs.decompiler.flash.helpers.HighlightedText; @@ -41,7 +44,7 @@ import java.util.logging.Logger; import javax.swing.BoxLayout; import javax.swing.JButton; import javax.swing.JPanel; -import javax.swing.SwingConstants; +import javax.swing.JScrollPane; import javax.swing.text.BadLocationException; /** @@ -86,9 +89,15 @@ public class TextPanel extends JPanel implements TagEditorPanel { private final JButton undoChangesButton; private TextTag textTag; + + private final UndoManager undoManager; + + private String oldText = null; - public TextPanel(final MainPanel mainPanel) { + public TextPanel(final MainPanel mainPanel, UndoManager undoManager) { super(new BorderLayout()); + + this.undoManager = undoManager; this.mainPanel = mainPanel; textSearchPanel = new SearchPanel<>(new FlowLayout(), mainPanel); @@ -97,14 +106,18 @@ public class TextPanel extends JPanel implements TagEditorPanel { topPanel.setLayout(new BoxLayout(topPanel, BoxLayout.Y_AXIS)); topPanel.add(textSearchPanel); textValue = new LineMarkedEditorPane(); - add(new FasterScrollPane(textValue), BorderLayout.CENTER); + JScrollPane sp = new FasterScrollPane(textValue); + sp.setPreferredSize(new Dimension(200, 200)); + //sp.setMaximumSize(sp.getSize()); + //sp.setPreferredSize(sp.getMaximumSize()); + add(sp, BorderLayout.CENTER); textValue.setFont(Configuration.getSourceFont()); textValue.changeContentType("text/swftext"); textValue.addTextChangedListener(this::textChanged); JPanel textButtonsPanel = new JPanel(); - textButtonsPanel.setLayout(new FlowLayout(SwingConstants.WEST)); - textButtonsPanel.setMinimumSize(new Dimension(10, textButtonsPanel.getMinimumSize().height)); + textButtonsPanel.setLayout(new FlowLayout(FlowLayout.LEFT)); + //textButtonsPanel.setMinimumSize(new Dimension(10, textButtonsPanel.getMinimumSize().height)); selectPreviousTagButton = createButton(null, "arrowup16", "selectPreviousTag", e -> mainPanel.previousTag()); selectNextTagButton = createButton(null, "arrowdown16", "selectNextTag", e -> mainPanel.nextTag()); @@ -126,7 +139,9 @@ public class TextPanel extends JPanel implements TagEditorPanel { textButtonsPanel.add(decreaseTranslateXButton); textButtonsPanel.add(increaseTranslateXButton); textButtonsPanel.add(changeCaseButton); - textButtonsPanel.add(undoChangesButton); + if (undoManager == null) { + textButtonsPanel.add(undoChangesButton); + } textButtonsPanel.setAlignmentX(0); topPanel.add(textButtonsPanel); @@ -148,6 +163,10 @@ public class TextPanel extends JPanel implements TagEditorPanel { add(buttonsPanel, BorderLayout.SOUTH); } + public TextTag getTextTag() { + return textTag; + } + private JButton createButton(String textResource, String iconName, String toolTipResource, ActionListener actionListener) { return createButton(textResource, iconName, toolTipResource, actionListener, false); } @@ -180,6 +199,7 @@ public class TextPanel extends JPanel implements TagEditorPanel { } textValue.setText(formattedText); + oldText = formattedText; } public void setText(TextTag textTag) { @@ -203,6 +223,7 @@ public class TextPanel extends JPanel implements TagEditorPanel { undoChangesButton.setVisible(!readOnly); selectPreviousTagButton.setVisible(mainPanel.getCurrentView() == MainPanel.VIEW_RESOURCES); selectNextTagButton.setVisible(mainPanel.getCurrentView() == MainPanel.VIEW_RESOURCES); + updateButtonsVisibility(); } private boolean isModified() { @@ -281,10 +302,15 @@ public class TextPanel extends JPanel implements TagEditorPanel { } } + boolean editable = textValue.isEditable(); + textValue.setEditable(true); textValue.replaceSelection(selected.toString()); - saveText(true); + textValue.setEditable(editable); + if (!editable) { + saveText(true); - updateButtonsVisibility(); + updateButtonsVisibility(); + } textTag.getSwf().clearImageCache(); mainPanel.repaintTree(); @@ -340,12 +366,13 @@ public class TextPanel extends JPanel implements TagEditorPanel { private void cancelText() { setEditText(false); + refresh(); mainPanel.reload(true); mainPanel.clearEditingStatus(); } private void saveText(boolean refresh) { - if (mainPanel.saveText(textTag, textValue.getText(), null, textValue)) { + if (mainPanel.saveText(textTag, textValue.getText(), null, textValue, undoManager)) { setEditText(false); setModified(false); textTag.getSwf().clearImageCache(); @@ -357,16 +384,74 @@ public class TextPanel extends JPanel implements TagEditorPanel { } private void textAlign(TextAlign textAlign) { + if (undoManager != null) { + String prevText = textTag.getFormattedText(false).text; + undoManager.doOperation(new DoableOperation() { + + @Override + public void doOperation() { + textAlignInternal(textAlign); + } + + @Override + public void undoOperation() { + try { + textTag.setFormattedText(null, prevText, null); + } catch (TextParseException ex) { + //ignore + } + } + + @Override + public String getDescription() { + return EasyStrings.translate("action.change").replace("%item%", EasyStrings.translate("action.change.text")); + } + }, textTag.getSwf()); + return; + } + textAlignInternal(textAlign); + } + + private void textAlignInternal(TextAlign textAlign) { if (textTag.alignText(textAlign)) { updateButtonsVisibility(); + refresh(); textTag.getSwf().clearImageCache(); mainPanel.repaintTree(); } } + private void translateX(int delta, int repeatCount) { + if (undoManager != null) { + undoManager.doOperation(new DoableOperation() { + + @Override + public void doOperation() { + translateXInternal(delta, repeatCount); + } + + @Override + public void undoOperation() { + translateXInternal(-delta, repeatCount); + } + + @Override + public String getDescription() { + return EasyStrings.translate("action.change").replace("%item%", EasyStrings.translate("action.change.text")); + } + }, textTag.getSwf()); + return; + } + translateXInternal(delta, repeatCount); + } + + private void translateXInternal(int delta, int repeatCount) { + + if (textTag.translateText(delta * (repeatCount + 1))) { updateButtonsVisibility(); + refresh(); textTag.getSwf().clearImageCache(); mainPanel.repaintTree(); } diff --git a/src/com/jpexs/decompiler/flash/gui/locales/EasyPanel.properties b/src/com/jpexs/decompiler/flash/gui/locales/EasyPanel.properties index afec10418..4978a4c94 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/EasyPanel.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/EasyPanel.properties @@ -194,4 +194,8 @@ property.instance.filters.innerGlow = inner glow property.instance.filters.menu.enable = Enable or disable filter -property.instance.display.ratio = Ratio \ No newline at end of file +property.instance.display.ratio = Ratio + +#after 24.0.1 +properties.instance.header.text = Text +action.change.text = Text \ No newline at end of file