From e56e7e506a019b13d323a068aa922cb280cd1971 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jindra=20Pet=C5=99=C3=ADk?= Date: Sat, 28 Mar 2026 15:56:00 +0100 Subject: [PATCH] feat: add text tag type conversion (#2383) Closes #2383 --- .../tags/converters/TextTypeConverter.java | 308 ++++++++++++++++++ .../decompiler/flash/xfl/XFLXmlWriter.java | 9 + .../flash/gui/ConvertTextTypeDialog.java | 120 +++++++ .../locales/ConvertTextTypeDialog.properties | 8 + .../ConvertTextTypeDialog_cs.properties | 8 + .../ConvertTextTypeDialog_de.properties | 8 + .../ConvertTextTypeDialog_sk.properties | 8 + .../flash/gui/locales/MainFrame.properties | 4 +- .../flash/gui/locales/MainFrame_cs.properties | 4 +- .../flash/gui/locales/MainFrame_de.properties | 4 +- .../flash/gui/locales/MainFrame_sk.properties | 4 +- .../flash/gui/tagtree/TagTreeContextMenu.java | 63 ++++ 12 files changed, 544 insertions(+), 4 deletions(-) create mode 100644 libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/converters/TextTypeConverter.java create mode 100644 src/com/jpexs/decompiler/flash/gui/ConvertTextTypeDialog.java create mode 100644 src/com/jpexs/decompiler/flash/gui/locales/ConvertTextTypeDialog.properties create mode 100644 src/com/jpexs/decompiler/flash/gui/locales/ConvertTextTypeDialog_cs.properties create mode 100644 src/com/jpexs/decompiler/flash/gui/locales/ConvertTextTypeDialog_de.properties create mode 100644 src/com/jpexs/decompiler/flash/gui/locales/ConvertTextTypeDialog_sk.properties diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/converters/TextTypeConverter.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/converters/TextTypeConverter.java new file mode 100644 index 000000000..ebac5777c --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/converters/TextTypeConverter.java @@ -0,0 +1,308 @@ +package com.jpexs.decompiler.flash.tags.converters; + +import com.jpexs.decompiler.flash.SWF; +import com.jpexs.decompiler.flash.tags.DefineEditTextTag; +import com.jpexs.decompiler.flash.tags.DefineText2Tag; +import com.jpexs.decompiler.flash.tags.DefineTextTag; +import com.jpexs.decompiler.flash.tags.Tag; +import com.jpexs.decompiler.flash.tags.base.CharacterTag; +import com.jpexs.decompiler.flash.tags.base.FontTag; +import com.jpexs.decompiler.flash.tags.base.StaticTextTag; +import com.jpexs.decompiler.flash.tags.base.TextTag; +import com.jpexs.decompiler.flash.timeline.Timelined; +import com.jpexs.decompiler.flash.types.MATRIX; +import com.jpexs.decompiler.flash.types.RECT; +import com.jpexs.decompiler.flash.types.RGB; +import com.jpexs.decompiler.flash.types.RGBA; +import com.jpexs.decompiler.flash.types.TEXTRECORD; +import com.jpexs.decompiler.flash.xfl.XFLXmlWriter; +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.xml.stream.XMLStreamException; + +/** + * Converts between text types (DefineText, DefineText2, DefineEditText) + * + * @author JPEXS + */ +public class TextTypeConverter { + + public static int TEXT_TYPE_DEFINETEXT = 1; + public static int TEXT_TYPE_DEFINETEXT2 = 2; + public static int TEXT_TYPE_DEFINEEXITTEXT = 3; + + /** + * Converts static text version + * + * @param tag DefineText or DefineText2 + * @param defineTextVersion 1 for DefineText, 2 for DefineText2 + * @param targetSWF Target SWF + * @return DefineText or DefineText2 + */ + private StaticTextTag convertStaticText(StaticTextTag tag, int defineTextVersion, SWF targetSWF) { + StaticTextTag ret; + switch (defineTextVersion) { + case 1: + ret = new DefineTextTag(targetSWF); + break; + case 2: + ret = new DefineText2Tag(targetSWF); + break; + default: + throw new IllegalArgumentException("defineTextVersion should be either 1 or 2"); + } + StaticTextTag clonedTag; + try { + clonedTag = (StaticTextTag) tag.cloneTag(); + } catch (InterruptedException | IOException ex) { + return null; + } + ret.textRecords = clonedTag.textRecords; + ret.textBounds = clonedTag.textBounds; + ret.textMatrix = clonedTag.textMatrix; + for (TEXTRECORD rec : ret.textRecords) { + if (defineTextVersion == 1 && rec.textColorA != null) { + rec.textColor = new RGB(rec.textColorA); + rec.textColorA = null; + } + if (defineTextVersion == 2 && rec.textColor != null) { + rec.textColorA = new RGBA(rec.textColor); + rec.textColor = null; + } + } + return ret; + } + + /** + * Converts DefineEditTextTag to static text (DefineText or DefineText2) + * + * @param tag DefineEditTextTag + * @param defineTextVersion 1 for DefineText, 2 for DefineText2 + * @param targetSWF Target SWF + * @return DefineText or DefineText2 + */ + private StaticTextTag editTextToStaticText(DefineEditTextTag tag, int defineTextVersion, SWF targetSWF) { + StaticTextTag ret = null; + switch (defineTextVersion) { + case 1: + ret = new DefineTextTag(targetSWF); + break; + case 2: + ret = new DefineText2Tag(targetSWF); + break; + default: + throw new IllegalArgumentException("defineTextVersion should be either 1 or 2"); + } + List records = tag.getTextRecords(tag.getSwf(), new HashMap<>()); + for (TEXTRECORD rec : records) { + if (defineTextVersion == 1 && rec.textColorA != null) { + rec.textColor = new RGB(rec.textColorA); + rec.textColorA = null; + } + if (defineTextVersion == 2 && rec.textColor != null) { + rec.textColorA = new RGBA(rec.textColor); + rec.textColor = null; + } + } + ret.textRecords = records; + ret.textBounds = tag.getBounds(); + ret.textMatrix = new MATRIX(); + return ret; + } + + /** + * Convers static text (DefineText, DefineText2) to DefineEditText + * + * @param tag DefineText or DefineText2 + * @param targetSWF Target SWF + * @return New DefineEditText + */ + private DefineEditTextTag staticTextToEditText(StaticTextTag tag, SWF targetSWF) { + List textRecords = tag.textRecords; + + DefineEditTextTag det = new DefineEditTextTag(targetSWF); + Map attrs = TextTag.getTextRecordsAttributes(textRecords, tag.getSwf(), new HashMap<>()); + @SuppressWarnings("unchecked") + List leftMargins = (List) attrs.get("allLeftMargins"); + @SuppressWarnings("unchecked") + List letterSpacings = (List) attrs.get("allLetterSpacings"); + + det.bounds = new RECT(tag.getBounds()); + det.wasStatic = true; + det.noSelect = true; + det.useOutlines = true; + det.multiline = true; + + det.indent = (int) attrs.get("indent"); + det.leftMargin = leftMargins.isEmpty() ? 0 : leftMargins.get(0); + det.leading = (int) attrs.get("lineSpacing"); + det.rightMargin = (int) attrs.get("rightMargin"); + + XFLXmlWriter writer = new XFLXmlWriter(); + writer.setMakeNewLines(false); + try { + int fontId; + FontTag font = null; + String fontName = null; + int textHeight = -1; + RGB textColor = null; + RGBA textColorA = null; + boolean newline; + boolean firstRun = true; + boolean isBold = false; + boolean isItalic = false; + for (int r = 0; r < textRecords.size(); r++) { + TEXTRECORD rec = textRecords.get(r); + if (rec.styleFlagsHasColor) { + if (tag instanceof DefineTextTag) { + textColor = rec.textColor; + } else { + textColorA = rec.textColorA; + } + } + if (rec.styleFlagsHasFont) { + fontId = rec.fontId; + fontName = null; + textHeight = rec.textHeight; + font = ((Tag) tag).getSwf().getFont(fontId); + + isBold = false; + isItalic = false; + if (font != null) { + fontName = font.getFontNameIntag(); + isBold = font.isBold(); + isItalic = font.isItalic(); + } + if (fontName == null) { + fontName = FontTag.getDefaultFontName(); + } + } + newline = false; + if (!firstRun && rec.styleFlagsHasYOffset) { + newline = true; + } + firstRun = false; + if (font != null) { + writer.writeStartElement("p"); + writer.writeStartElement("font"); + writer.writeAttribute("face", fontName); + writer.writeAttribute("size", doubleToString(twipToPixel(textHeight))); + if (textColor != null) { + writer.writeAttribute("color", textColor.toHexRGB()); + } else if (textColorA != null) { + writer.writeAttribute("color", textColorA.toHexARGB()); + } else { + writer.writeAttribute("color", "#000000"); + } + writer.writeAttribute("letterSpacing", doubleToString(twipToPixel(letterSpacings.get(r)))); + + if (isBold) { + writer.writeStartElement("b"); + } + if (isItalic) { + writer.writeStartElement("i"); + } + writer.writeCharacters(rec.getText(font)); + if (isItalic) { + writer.writeEndElement(); + } + if (isBold) { + writer.writeEndElement(); //b + } + writer.writeEndElement(); //font + writer.writeEndElement(); //p + } + } + } catch (XMLStreamException ex) { + Logger.getLogger(TextTypeConverter.class.getName()).log(Level.SEVERE, null, ex); + } + det.html = true; + det.hasText = true; + det.initialText = writer.toString(); + + return det; + } + + private static double twipToPixel(double tw) { + return tw / SWF.unitDivisor; + } + + private static String doubleToString(double d) { + String ds = "" + d; + if (ds.endsWith(".0")) { + ds = ds.substring(0, ds.length() - 2); + } + return ds; + } + + /** + * Converts text tag referenced by character id in selected SWF file. + * + * @param swf SWF + * @param characterId Character id + * @param targetTextNum 1 = DefineText, 2 = DefineText2, 3 = DefineEditText + */ + public void convertCharacter(SWF swf, int characterId, int targetTextNum) { + CharacterTag ct = swf.getCharacter(characterId); + if (!(ct instanceof TextTag)) { + throw new IllegalArgumentException("Character " + characterId + " is not a text"); + } + TextTag t = (TextTag) ct; + Timelined tim = t.getTimelined(); + TextTag converted = convertTagType(t, swf, targetTextNum); + converted.setCharacterId(characterId); + swf.replaceTag(ct, converted); + converted.setTimelined(tim); + swf.updateCharacters(); + swf.assignClassesToSymbols(); + swf.assignExportNamesToSymbols(); + tim.resetTimeline(); + } + + /** + * Converts text tag types + * + * @param sourceTextTag Source tag + * @param targetSWF Target swf + * @param targetTextNum 1 = DefineText, 2 = DefineText2, 3 = DefineEditText + * @return Converted DefineShapeX tag + * @throws IllegalArgumentException When conversion is not possible - see + * getForcedMinShapeNum + */ + public TextTag convertTagType(TextTag sourceTextTag, SWF targetSWF, int targetTextNum) { + int currentTextNum; + if (sourceTextTag instanceof DefineTextTag) { + currentTextNum = TextTypeConverter.TEXT_TYPE_DEFINETEXT; + } else if (sourceTextTag instanceof DefineText2Tag) { + currentTextNum = TextTypeConverter.TEXT_TYPE_DEFINETEXT2; + } else if (sourceTextTag instanceof DefineEditTextTag) { + currentTextNum = TextTypeConverter.TEXT_TYPE_DEFINEEXITTEXT; + } else { + throw new IllegalArgumentException("Invalid text"); + } + + if (currentTextNum < TEXT_TYPE_DEFINEEXITTEXT && targetTextNum < TEXT_TYPE_DEFINEEXITTEXT) { + return convertStaticText((StaticTextTag) sourceTextTag, targetTextNum, targetSWF); + } + if (currentTextNum < TEXT_TYPE_DEFINEEXITTEXT && targetTextNum == TEXT_TYPE_DEFINEEXITTEXT) { + return staticTextToEditText((StaticTextTag) sourceTextTag, targetSWF); + } + + if (currentTextNum == TEXT_TYPE_DEFINEEXITTEXT && targetTextNum < TEXT_TYPE_DEFINEEXITTEXT) { + return editTextToStaticText((DefineEditTextTag) sourceTextTag, targetTextNum, targetSWF); + } + + try { + //currentTextNum == TEXT_TYPE_DEFINEEXITTEXT && targetTextNum == TEXT_TYPE_DEFINEEXITTEXT + TextTag ret = (TextTag) sourceTextTag.cloneTag(); + ret.setSwf(targetSWF); + return ret; + } catch (InterruptedException | IOException ex) { + return null; + } + } +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/xfl/XFLXmlWriter.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/xfl/XFLXmlWriter.java index cdb6ac809..aa4d887fa 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/xfl/XFLXmlWriter.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/xfl/XFLXmlWriter.java @@ -47,7 +47,13 @@ public class XFLXmlWriter implements XMLStreamWriter { private final Map namespaces = new HashMap<>(); private final Stack tagsStack = new Stack<>(); + + private boolean makeNewLines = true; + public void setMakeNewLines(boolean makeNewLines) { + this.makeNewLines = makeNewLines; + } + @Override public String toString() { return sb.toString(); @@ -66,6 +72,9 @@ public class XFLXmlWriter implements XMLStreamWriter { } private void makeNewLine() { + if (!makeNewLines) { + return; + } if (!newLine) { sb.append(newLineCharacters); newLine = true; diff --git a/src/com/jpexs/decompiler/flash/gui/ConvertTextTypeDialog.java b/src/com/jpexs/decompiler/flash/gui/ConvertTextTypeDialog.java new file mode 100644 index 000000000..fa682ae12 --- /dev/null +++ b/src/com/jpexs/decompiler/flash/gui/ConvertTextTypeDialog.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2010-2026 JPEXS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jpexs.decompiler.flash.gui; + +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.Container; +import java.awt.FlowLayout; +import java.awt.Window; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.ArrayList; +import java.util.List; +import javax.swing.BoxLayout; +import javax.swing.ButtonGroup; +import javax.swing.JButton; +import javax.swing.JPanel; +import javax.swing.JRadioButton; + +/** + * + * @author JPEXS + */ +public class ConvertTextTypeDialog extends AppDialog { + + private List radios = new ArrayList<>(); + + private int result = 0; + + public ConvertTextTypeDialog(Window owner, int currentTextType) { + super(owner); + + setTitle(translate("dialog.title")); + JPanel radioPanel = new JPanel(); + radioPanel.setLayout(new BoxLayout(radioPanel, BoxLayout.Y_AXIS)); + ButtonGroup radioGroup = new ButtonGroup(); + + JButton okButton = new JButton(translate("button.ok")); + + String[] names = new String[]{"DefineText", "DefineText2", "DefineEditText"}; + + for (int i = 0; i < 3; i++) { + String text = names[i]; + text += " - " + translate("text." + names[i]); + JRadioButton radio = new JRadioButton(text); + radio.setAlignmentX(Component.LEFT_ALIGNMENT); + if (i == currentTextType - 1) { + radio.setSelected(true); + } + final int fi = i; + radio.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + okButton.setEnabled(fi != currentTextType - 1); + } + }); + radioPanel.add(radio); + radioGroup.add(radio); + radios.add(radio); + } + + Container cnt = getContentPane(); + cnt.setLayout(new BorderLayout()); + + cnt.add(radioPanel, BorderLayout.CENTER); + + JPanel buttonsPanel = new JPanel(new FlowLayout()); + + okButton.setEnabled(false); + okButton.addActionListener(this::okButtonActionPerformed); + JButton cancelButton = new JButton(translate("button.cancel")); + cancelButton.addActionListener(this::cancelButtonActionPerformed); + buttonsPanel.add(okButton); + buttonsPanel.add(cancelButton); + + cnt.add(buttonsPanel, BorderLayout.SOUTH); + pack(); + View.centerScreen(this); + View.setWindowIcon(this, "text"); + setModal(true); + } + + private void okButtonActionPerformed(ActionEvent evt) { + result = 0; + for (int i = 0; i < radios.size(); i++) { + if (radios.get(i).isSelected()) { + result = i + 1; + break; + } + } + setVisible(false); + } + + private void cancelButtonActionPerformed(ActionEvent evt) { + setVisible(false); + } + + public int getResult() { + return result; + } + + public int showDialog() { + setVisible(true); + return result; + } +} diff --git a/src/com/jpexs/decompiler/flash/gui/locales/ConvertTextTypeDialog.properties b/src/com/jpexs/decompiler/flash/gui/locales/ConvertTextTypeDialog.properties new file mode 100644 index 000000000..192799346 --- /dev/null +++ b/src/com/jpexs/decompiler/flash/gui/locales/ConvertTextTypeDialog.properties @@ -0,0 +1,8 @@ +dialog.title = Convert text type + +text.DefineText = static text, no alpha channel +text.DefineText2 = static text, with alpha channel +text.DefineEditText = dynamic or input text + +button.ok = OK +button.cancel = Cancel \ No newline at end of file diff --git a/src/com/jpexs/decompiler/flash/gui/locales/ConvertTextTypeDialog_cs.properties b/src/com/jpexs/decompiler/flash/gui/locales/ConvertTextTypeDialog_cs.properties new file mode 100644 index 000000000..7b8d47be1 --- /dev/null +++ b/src/com/jpexs/decompiler/flash/gui/locales/ConvertTextTypeDialog_cs.properties @@ -0,0 +1,8 @@ +dialog.title = Konvertovat typ textu + +text.DefineText = statick\u00fd text, bez alfa kan\u00e1lu +text.DefineText2 = statick\u00fd text, s alfa kan\u00e1lem +text.DefineEditText = dynamick\u00fd nebo vstupn\u00ed text + +button.ok = OK +button.cancel = Storno \ No newline at end of file diff --git a/src/com/jpexs/decompiler/flash/gui/locales/ConvertTextTypeDialog_de.properties b/src/com/jpexs/decompiler/flash/gui/locales/ConvertTextTypeDialog_de.properties new file mode 100644 index 000000000..d471eaadf --- /dev/null +++ b/src/com/jpexs/decompiler/flash/gui/locales/ConvertTextTypeDialog_de.properties @@ -0,0 +1,8 @@ +dialog.title = Texttyp konvertieren + +text.DefineText = statischer Text, ohne Alphakanal +text.DefineText2 = statischer Text, mit Alphakanal +text.DefineEditText = dynamischer oder Eingabetext + +button.ok = OK +button.cancel = Abbrechen \ No newline at end of file diff --git a/src/com/jpexs/decompiler/flash/gui/locales/ConvertTextTypeDialog_sk.properties b/src/com/jpexs/decompiler/flash/gui/locales/ConvertTextTypeDialog_sk.properties new file mode 100644 index 000000000..3de2f9c7f --- /dev/null +++ b/src/com/jpexs/decompiler/flash/gui/locales/ConvertTextTypeDialog_sk.properties @@ -0,0 +1,8 @@ +dialog.title = Konvertova\u0165 typ textu + +text.DefineText = statick\u00fd text, bez alfa kan\u00e1la +text.DefineText2 = statick\u00fd text, s alfa kan\u00e1lom +text.DefineEditText = dynamick\u00fd alebo vstupn\u00fd text + +button.ok = OK +button.cancel = Zru\u0161i\u0165 \ No newline at end of file diff --git a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties index 475c498cf..13104dcb9 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties @@ -1147,4 +1147,6 @@ menu.file.export.xaml = Export XAML work.exporting.xaml = Exporting XAML contextmenu.exportXaml = Export XAML menu.file.import.bulkImport = Bulk import... -menu.file.import.createTagFromFile = Create tag from file... \ No newline at end of file +menu.file.import.createTagFromFile = Create tag from file... + +contextmenu.convertTextType = Convert text type \ No newline at end of file diff --git a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_cs.properties b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_cs.properties index fd3ab5c8e..e73f3025a 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_cs.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_cs.properties @@ -1147,4 +1147,6 @@ menu.file.export.xaml = Exportovat XAML work.exporting.xaml = Exportov\u00e1n\u00ed XAML contextmenu.exportXaml = Exportovat XAML menu.file.import.bulkImport = Hromadn\u00fd import... -menu.file.import.createTagFromFile = Vytvo\u0159it tag ze souboru... \ No newline at end of file +menu.file.import.createTagFromFile = Vytvo\u0159it tag ze souboru... + +contextmenu.convertTextType = Konvertovat typ textu \ No newline at end of file diff --git a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_de.properties b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_de.properties index bbc37277b..6006bae78 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_de.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_de.properties @@ -1008,4 +1008,6 @@ menu.file.export.xaml = Exportiere als XAML work.exporting.xaml = XAML exportieren contextmenu.exportXaml = Exportiere als XAML menu.file.import.bulkImport = Massenimport... -menu.file.import.createTagFromFile = Tag aus Datei erstellen... \ No newline at end of file +menu.file.import.createTagFromFile = Tag aus Datei erstellen... + +contextmenu.convertTextType = Texttyp konvertieren \ No newline at end of file diff --git a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_sk.properties b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_sk.properties index 04d200171..fd3269661 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_sk.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_sk.properties @@ -1147,4 +1147,6 @@ menu.file.export.xaml = Exportova\u0165 XAML work.exporting.xaml = Exportovanie XAML contextmenu.exportXaml = Exportova\u0165 XAML menu.file.import.bulkImport = Hromadn\u00fd import... -menu.file.import.createTagFromFile = Vytvori\u0165 tag zo s\u00faboru... \ No newline at end of file +menu.file.import.createTagFromFile = Vytvori\u0165 tag zo s\u00faboru... + +contextmenu.convertTextType = Konvertova\u0165 typ textu \ No newline at end of file diff --git a/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeContextMenu.java b/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeContextMenu.java index cbda74eec..d295c699a 100644 --- a/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeContextMenu.java +++ b/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeContextMenu.java @@ -46,6 +46,7 @@ import com.jpexs.decompiler.flash.gui.ClipboardType; import com.jpexs.decompiler.flash.gui.CollectDepthAsSpritesDialog; import com.jpexs.decompiler.flash.gui.ConvertPlaceObjectTypeDialog; import com.jpexs.decompiler.flash.gui.ConvertShapeTypeDialog; +import com.jpexs.decompiler.flash.gui.ConvertTextTypeDialog; import com.jpexs.decompiler.flash.gui.Main; import com.jpexs.decompiler.flash.gui.MainPanel; import com.jpexs.decompiler.flash.gui.PathResolvingDialog; @@ -70,11 +71,14 @@ import com.jpexs.decompiler.flash.tags.ABCContainerTag; import com.jpexs.decompiler.flash.tags.DefineBinaryDataTag; import com.jpexs.decompiler.flash.tags.DefineBitsLossless2Tag; import com.jpexs.decompiler.flash.tags.DefineButton2Tag; +import com.jpexs.decompiler.flash.tags.DefineEditTextTag; import com.jpexs.decompiler.flash.tags.DefineFont3Tag; import com.jpexs.decompiler.flash.tags.DefineMorphShape2Tag; import com.jpexs.decompiler.flash.tags.DefineShape4Tag; import com.jpexs.decompiler.flash.tags.DefineSoundTag; import com.jpexs.decompiler.flash.tags.DefineSpriteTag; +import com.jpexs.decompiler.flash.tags.DefineText2Tag; +import com.jpexs.decompiler.flash.tags.DefineTextTag; import com.jpexs.decompiler.flash.tags.DefineVideoStreamTag; import com.jpexs.decompiler.flash.tags.DoABC2Tag; import com.jpexs.decompiler.flash.tags.DoActionTag; @@ -120,6 +124,7 @@ import com.jpexs.decompiler.flash.tags.base.SoundTag; import com.jpexs.decompiler.flash.tags.base.TextTag; import com.jpexs.decompiler.flash.tags.converters.PlaceObjectTypeConverter; import com.jpexs.decompiler.flash.tags.converters.ShapeTypeConverter; +import com.jpexs.decompiler.flash.tags.converters.TextTypeConverter; import com.jpexs.decompiler.flash.tags.gfx.DefineExternalSound; import com.jpexs.decompiler.flash.tags.gfx.DefineExternalStreamSound; import com.jpexs.decompiler.flash.tags.gfx.ExporterInfo; @@ -387,6 +392,8 @@ public class TagTreeContextMenu extends JPopupMenu { private JMenuItem convertPlaceObjectTypeMenuItem; + private JMenuItem convertTextTypeMenuItem; + private JMenuItem normalizeFontsMenuItem; private JMenuItem prepareDebugInject; @@ -673,6 +680,11 @@ public class TagTreeContextMenu extends JPopupMenu { convertPlaceObjectTypeMenuItem.addActionListener(this::convertPlaceObjectTypeActionPerformed); convertPlaceObjectTypeMenuItem.setIcon(View.getIcon("placeobject16")); add(convertPlaceObjectTypeMenuItem); + + convertTextTypeMenuItem = new JMenuItem(mainPanel.translate("contextmenu.convertTextType")); + convertTextTypeMenuItem.addActionListener(this::convertTextTypeActionPerformed); + convertTextTypeMenuItem.setIcon(View.getIcon("text16")); + add(convertTextTypeMenuItem); normalizeFontsMenuItem = new JMenuItem(mainPanel.translate("contextmenu.normalizeFonts")); normalizeFontsMenuItem.addActionListener(this::normalizeFontsActionPerformed); @@ -1249,12 +1261,14 @@ public class TagTreeContextMenu extends JPopupMenu { boolean allSelectedIsShape = true; boolean allSelectedIsPlaceObject = true; + boolean allSelectedIsText = true; boolean allSelectedIsFont = true; if (items.isEmpty()) { allSelectedIsShape = false; allSelectedIsPlaceObject = false; allSelectedIsFont = false; + allSelectedIsText = false; } for (TreeItem item : items) { @@ -1268,6 +1282,10 @@ public class TagTreeContextMenu extends JPopupMenu { if (!(item instanceof FontTag)) { allSelectedIsFont = false; } + + if (!(item instanceof TextTag)) { + allSelectedIsText = false; + } if (item instanceof Tag) { Tag tag = (Tag) item; @@ -1436,6 +1454,7 @@ public class TagTreeContextMenu extends JPopupMenu { replaceRefsWithTagMenuItem.setVisible(false); convertShapeTypeMenuItem.setVisible(false); convertPlaceObjectTypeMenuItem.setVisible(false); + convertTextTypeMenuItem.setVisible(false); normalizeFontsMenuItem.setVisible(false); abcExplorerMenuItem.setVisible(false); cleanAbcMenuItem.setVisible(false); @@ -1936,6 +1955,10 @@ public class TagTreeContextMenu extends JPopupMenu { if (allSelectedIsPlaceObject) { convertPlaceObjectTypeMenuItem.setVisible(true); } + + if (allSelectedIsText) { + convertTextTypeMenuItem.setVisible(true); + } if (allSelectedIsSwf) { normalizeFontsMenuItem.setVisible(true); } @@ -2987,6 +3010,46 @@ public class TagTreeContextMenu extends JPopupMenu { mainPanel.setTagTreeSelectedNode(mainPanel.getCurrentTree(), lastConverted); } } + + private void convertTextTypeActionPerformed(ActionEvent evt) { + List itemr = getSelectedItems(); + if (itemr.isEmpty()) { + return; + } + int currentTextType = 0; + + TextTypeConverter converter = new TextTypeConverter(); + + if (itemr.size() == 1) { + TextTag t = (TextTag) itemr.get(0); + if (t instanceof DefineTextTag) { + currentTextType = TextTypeConverter.TEXT_TYPE_DEFINETEXT; + } else if (t instanceof DefineText2Tag) { + currentTextType = TextTypeConverter.TEXT_TYPE_DEFINETEXT2; + } else if (t instanceof DefineEditTextTag) { + currentTextType = TextTypeConverter.TEXT_TYPE_DEFINEEXITTEXT; + } + } + + ConvertTextTypeDialog dialog = new ConvertTextTypeDialog(Main.getDefaultDialogsOwner(), currentTextType); + + int newTextType = dialog.showDialog(); + + if (newTextType == 0) { + return; + } + + for (TreeItem item : itemr) { + TextTag t = (TextTag) item; + converter.convertCharacter(t.getSwf(), t.getCharacterId(), newTextType); + } + + mainPanel.refreshTree(); + if (itemr.size() == 1) { + TextTag t = (TextTag) itemr.get(0); + mainPanel.setTagTreeSelectedNode(mainPanel.getCurrentTree(), t.getSwf().getCharacter(t.getCharacterId())); + } + } private void normalizeFontsActionPerformed(ActionEvent evt) { for (TreeItem item : getSelectedItems()) {