diff --git a/CHANGELOG.md b/CHANGELOG.md index b1f061151..9fa1ce610 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ All notable changes to this project will be documented in this file. - [PR190] Collect depth as sprites - Updated Dutch translation - [#2259] Optional resampling sound to 44kHz on playback and on export +- Set class to character mapping tool (via SymbolClass) context menu on characters ### Fixed - Debugger - getting children of top level variables @@ -49,6 +50,7 @@ All notable changes to this project will be documented in this file. - [#2239] Default font name detection - [#2239] Exporting TTF font on Linux - [PR193] Quoting JAR file in ffdec.sh +- Refreshing class/exportname association on SymbolClass/ExportAssets deletion ### Changed - [#2185] MochiCrypt no longer offered for auto decrypt, user needs to choose variant from "Use unpacker" menu diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWF.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWF.java index 57777e12c..67fee6e4f 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWF.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWF.java @@ -1856,6 +1856,8 @@ public final class SWF implements SWFContainerItem, Timelined, Openable { } if (exportNames.containsKey(ct.getCharacterId())) { ct.setExportName(exportNames.get(ct.getCharacterId())); + } else { + ct.setExportName(""); } } } @@ -1894,6 +1896,8 @@ public final class SWF implements SWFContainerItem, Timelined, Openable { } if (classes.containsKey(ct.getCharacterId())) { ct.setClassNames(classes.get((Integer) ct.getCharacterId())); + } else { + ct.setClassNames(new LinkedHashSet<>()); } } } diff --git a/src/com/jpexs/decompiler/flash/gui/SelectTagPositionDialog.java b/src/com/jpexs/decompiler/flash/gui/SelectTagPositionDialog.java index 54852f7d8..e741750a4 100644 --- a/src/com/jpexs/decompiler/flash/gui/SelectTagPositionDialog.java +++ b/src/com/jpexs/decompiler/flash/gui/SelectTagPositionDialog.java @@ -192,35 +192,46 @@ public class SelectTagPositionDialog extends AppDialog { } } - private void populateNodes(MyTreeNode root, Timelined tim) { + private void populateNodes(MyTreeNode root, Timelined tim, int minFrame) { int f = 1; MyTreeNode frameNode = new MyTreeNode(); List labels = new ArrayList<>(); - frameNode.setData(new MyFrame(1, labels)); - frameNode.setParent(root); - root.addChild(frameNode); + if (minFrame <= 1) { + frameNode.setData(new MyFrame(1, labels)); + frameNode.setParent(root); + root.addChild(frameNode); + } + boolean wasMinFrame = minFrame <= 1; + for (Tag t : tim.getTags()) { - MyTreeNode node = new MyTreeNode(); - node.setData(t); - frameNode.addChild(node); + + if (wasMinFrame) { + MyTreeNode node = new MyTreeNode(); + node.setData(t); + frameNode.addChild(node); - if (t instanceof DefineSpriteTag) { - if (allowInsideSprites) { - populateNodes(node, (DefineSpriteTag) t); + if (t instanceof DefineSpriteTag) { + if (allowInsideSprites) { + populateNodes(node, (DefineSpriteTag) t, 1); + } + } + if (t instanceof FrameLabelTag) { + labels.add(((FrameLabelTag) t).name); } - } - if (t instanceof FrameLabelTag) { - labels.add(((FrameLabelTag) t).name); } if (t instanceof ShowFrameTag) { f++; - frameNode = new MyTreeNode(); - labels = new ArrayList<>(); - frameNode.setData(new MyFrame(f, labels)); - frameNode.setParent(root); - root.addChild(frameNode); + if (f >= minFrame) { + wasMinFrame = true; + + frameNode = new MyTreeNode(); + labels = new ArrayList<>(); + frameNode.setData(new MyFrame(f, labels)); + frameNode.setParent(root); + root.addChild(frameNode); + } } } if (frameNode.isLeaf()) { @@ -279,7 +290,11 @@ public class SelectTagPositionDialog extends AppDialog { } public SelectTagPositionDialog(Window parent, SWF swf, boolean allowInsideSprites) { - this(parent, swf, null, null, allowInsideSprites, false); + this(parent, swf, allowInsideSprites, null, 1); + } + + public SelectTagPositionDialog(Window parent, SWF swf, boolean allowInsideSprites, String newTypeName, int minFrame) { + this(parent, swf, null, null, allowInsideSprites, false, newTypeName, minFrame); } private static class PositionTreeCellRenderer extends DefaultTreeCellRenderer { @@ -320,14 +335,18 @@ public class SelectTagPositionDialog extends AppDialog { } } - public SelectTagPositionDialog(Window parent, SWF swf, Tag selectedTag, Timelined selectedTimelined, boolean allowInsideSprites, boolean selectNext) { + public SelectTagPositionDialog(Window parent, SWF swf, Tag selectedTag, Timelined selectedTimelined, boolean allowInsideSprites, boolean selectNext, String newTypeName, int minFrame) { super(parent); this.swf = swf; this.selectedTag = selectedTag; this.selectedTimelined = selectedTimelined; this.allowInsideSprites = allowInsideSprites; this.selectNext = selectNext; - setTitle(translate("dialog.title").replace("%filetitle%", swf.getShortPathTitle())); + if (newTypeName != null) { + setTitle(translate("dialog.title.new.typed").replace("%type%", newTypeName).replace("%filetitle%", swf.getShortPathTitle())); + } else { + setTitle(translate("dialog.title").replace("%filetitle%", swf.getShortPathTitle())); + } setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE); Container cnt = getContentPane(); cnt.setLayout(new BorderLayout()); @@ -344,7 +363,7 @@ public class SelectTagPositionDialog extends AppDialog { MyTreeNode root = new MyTreeNode(); root.setData("root"); - populateNodes(root, swf); + populateNodes(root, swf, minFrame); positionTree = new JTree(root) { @Override diff --git a/src/com/jpexs/decompiler/flash/gui/abc/AddClassDialog.java b/src/com/jpexs/decompiler/flash/gui/abc/AddClassDialog.java index 2c8c478d2..0e2f407a2 100644 --- a/src/com/jpexs/decompiler/flash/gui/abc/AddClassDialog.java +++ b/src/com/jpexs/decompiler/flash/gui/abc/AddClassDialog.java @@ -227,8 +227,8 @@ public class AddClassDialog extends AppDialog { selectedAbcContainer = preselectedAbcContainer; } else { if (existingAbcTagRadioButton.isSelected()) { - SelectDoABCDialog selectDoABCDialog = new SelectDoABCDialog(owner, (SWF) openable); - selectedAbcContainer = selectDoABCDialog.showDialog(); + SelectTagOfTypeDialog selectDoABCDialog = new SelectTagOfTypeDialog(owner, (SWF) openable, ABCContainerTag.class, "DoABC", 1); + selectedAbcContainer = (ABCContainerTag) selectDoABCDialog.showDialog(); if (selectedAbcContainer == null) { cancelButtonActionPerformed(evt); return; diff --git a/src/com/jpexs/decompiler/flash/gui/abc/SelectDoABCDialog.java b/src/com/jpexs/decompiler/flash/gui/abc/SelectTagOfTypeDialog.java similarity index 68% rename from src/com/jpexs/decompiler/flash/gui/abc/SelectDoABCDialog.java rename to src/com/jpexs/decompiler/flash/gui/abc/SelectTagOfTypeDialog.java index 51fecc4b0..16b3639ce 100644 --- a/src/com/jpexs/decompiler/flash/gui/abc/SelectDoABCDialog.java +++ b/src/com/jpexs/decompiler/flash/gui/abc/SelectTagOfTypeDialog.java @@ -18,10 +18,9 @@ package com.jpexs.decompiler.flash.gui.abc; import com.jpexs.decompiler.flash.SWF; import com.jpexs.decompiler.flash.gui.AppDialog; -import com.jpexs.decompiler.flash.gui.TreeNodeType; import com.jpexs.decompiler.flash.gui.View; import com.jpexs.decompiler.flash.gui.tagtree.TagTree; -import com.jpexs.decompiler.flash.tags.ABCContainerTag; +import com.jpexs.decompiler.flash.tags.ShowFrameTag; import com.jpexs.decompiler.flash.tags.Tag; import java.awt.Component; import java.awt.Container; @@ -40,30 +39,38 @@ import javax.swing.JPanel; * * @author JPEXS */ -public class SelectDoABCDialog extends AppDialog { +public class SelectTagOfTypeDialog extends AppDialog { - private JComboBox abcComboBox; - private ABCContainerTag result = null; + private JComboBox tagComboBox; + private Tag result = null; - public SelectDoABCDialog(Window window, SWF swf) { + public SelectTagOfTypeDialog(Window window, SWF swf, Class tagType, String tagTypeName, int minFrame) { super(window); - setTitle(translate("dialog.title")); + setTitle(translate("dialog.title").replace("%type%", tagTypeName)); Container cnt = getContentPane(); - abcComboBox = new JComboBox<>(); + tagComboBox = new JComboBox<>(); int pos = 0; + int frame = 1; for (Tag t : swf.getTags()) { - if (t instanceof ABCContainerTag) { - abcComboBox.addItem(new ComboItem(pos, (ABCContainerTag) t)); - pos++; + if (t instanceof ShowFrameTag) { + frame++; + } + + if (frame >= minFrame) { + if (tagType.isAssignableFrom(t.getClass())) { + tagComboBox.addItem(new ComboItem(pos, t, frame)); + pos++; + } } } - abcComboBox.setRenderer(new DefaultListCellRenderer() { + tagComboBox.setRenderer(new DefaultListCellRenderer() { @Override public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { JLabel label = (JLabel) super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); - label.setIcon(TagTree.getIconForType(TreeNodeType.AS)); + ComboItem item = (ComboItem) value; + label.setIcon(TagTree.getIconFor(item.tag)); label.setText(value.toString()); return label; } @@ -79,7 +86,7 @@ public class SelectDoABCDialog extends AppDialog { buttonsPanel.add(cancelButton); cnt.setLayout(new BoxLayout(cnt, BoxLayout.Y_AXIS)); - cnt.add(abcComboBox); + cnt.add(tagComboBox); cnt.add(buttonsPanel); pack(); @@ -89,13 +96,13 @@ public class SelectDoABCDialog extends AppDialog { View.centerScreen(this); } - public ABCContainerTag getResult() { + public Tag getResult() { return result; } @SuppressWarnings("unchecked") private void okButtonActionPerformed(ActionEvent evt) { - result = ((ComboItem) abcComboBox.getSelectedItem()).abc; + result = ((ComboItem) tagComboBox.getSelectedItem()).tag; setVisible(false); } @@ -104,13 +111,13 @@ public class SelectDoABCDialog extends AppDialog { setVisible(false); } - public ABCContainerTag showDialog() { + public Tag showDialog() { result = null; - if (abcComboBox.getItemCount() == 0) { + if (tagComboBox.getItemCount() == 0) { return null; } - if (abcComboBox.getItemCount() == 1) { - result = abcComboBox.getItemAt(0).abc; + if (tagComboBox.getItemCount() == 1) { + result = tagComboBox.getItemAt(0).tag; return result; } setVisible(true); @@ -120,16 +127,18 @@ public class SelectDoABCDialog extends AppDialog { class ComboItem { public int index; - public ABCContainerTag abc; + public Tag tag; + public int frame; - public ComboItem(int index, ABCContainerTag abc) { + public ComboItem(int index, Tag tag, int frame) { this.index = index; - this.abc = abc; + this.tag = tag; + this.frame = frame; } @Override public String toString() { - return "" + (index + 1) + ". " + abc.toString(); + return "" + (index + 1) + ". " + tag.toString() + " (frame " + frame + ")"; } } diff --git a/src/com/jpexs/decompiler/flash/gui/abc/SetClassToCharacterMappingDialog.java b/src/com/jpexs/decompiler/flash/gui/abc/SetClassToCharacterMappingDialog.java new file mode 100644 index 000000000..1a589e630 --- /dev/null +++ b/src/com/jpexs/decompiler/flash/gui/abc/SetClassToCharacterMappingDialog.java @@ -0,0 +1,616 @@ +/* + * 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.gui.abc; + +import com.jpexs.decompiler.flash.SWF; +import com.jpexs.decompiler.flash.abc.ABC; +import com.jpexs.decompiler.flash.abc.ScriptPack; +import com.jpexs.decompiler.flash.gui.AppDialog; +import static com.jpexs.decompiler.flash.gui.AppDialog.CANCEL_OPTION; +import static com.jpexs.decompiler.flash.gui.AppDialog.ERROR_OPTION; +import static com.jpexs.decompiler.flash.gui.AppDialog.OK_OPTION; +import com.jpexs.decompiler.flash.gui.SelectTagPositionDialog; +import com.jpexs.decompiler.flash.gui.View; +import com.jpexs.decompiler.flash.tags.ABCContainerTag; +import com.jpexs.decompiler.flash.tags.DefineBinaryDataTag; +import com.jpexs.decompiler.flash.tags.DefineFont4Tag; +import com.jpexs.decompiler.flash.tags.DefineSpriteTag; +import com.jpexs.decompiler.flash.tags.ShowFrameTag; +import com.jpexs.decompiler.flash.tags.SymbolClassTag; +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.ImageTag; +import com.jpexs.decompiler.flash.tags.base.SoundTag; +import com.jpexs.decompiler.flash.timeline.Timelined; +import com.jpexs.decompiler.flash.treeitems.Openable; +import com.jpexs.helpers.LinkedIdentityHashSet; +import java.awt.CardLayout; +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.HashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import javax.swing.BoxLayout; +import javax.swing.ButtonGroup; +import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JRadioButton; +import javax.swing.JTextField; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; + +/** + * + * @author JPEXS + */ +public class SetClassToCharacterMappingDialog extends AppDialog { + + private final JButton proceedButton = new JButton(translate("button.proceed")); + private final JButton cancelButton = new JButton(translate("button.cancel")); + + private final JTextField classNameTextField = new JTextField(30); + private final JTextField parentClassNameTextField = new JTextField(30); + private String selectedClass = null; + private String selectedParentClass = null; + private ABCContainerTag selectedAbcContainer; + private SymbolClassTag selectedSymbolClassTag; + private Tag selectedPosition; + private Timelined selectedTimelined; + private int result = ERROR_OPTION; + + private final SWF swf; + + private final int characterId; + + private int characterFrame = -1; + + + + private int abcCount = 0; + + private int symbolClassCount = 0; + + private ABCContainerTag foundInAbcContainer = null; + + private final List abcContainers = new ArrayList<>(); + + private int abcFrame = -1; + + private boolean createClass = false; + + private final JPanel classFoundOrNotOrErrorPanel; + + private final JLabel errorLabel = new JLabel(); + + private final JRadioButton existingAbcTagRadioButton = new JRadioButton(translate("class.notfound.create.abc.where.existing")); + private final JRadioButton newAbcTagRadioButton = new JRadioButton(translate("class.notfound.create.abc.where.new")); + + private final JRadioButton createClassRadioButton = new JRadioButton(translate("class.notfound.create")); + private final JRadioButton onlySetClassNameRadioButton = new JRadioButton(translate("class.notfound.onlySetClassName")); + + private final JRadioButton existingSymbolClassTagRadioButton = new JRadioButton(translate("class.notfound.onlySetClassName.symbolClass.where.existing")); + private final JRadioButton newSymbolClassTagRadioButton = new JRadioButton(translate("class.notfound.onlySetClassName.symbolClass.where.new")); + + + private final static String CLASS_NOT_FOUND_CARD = "Class not found panel"; + private final static String CLASS_FOUND_CARD = "Class found panel"; + private final static String ERROR_CARD = "Error panel"; + + private final static String CREATE_CLASS_CARD = "Create class panel"; + private final static String DO_NOT_CREATE_CLASS_CARD = "Do not create class panel"; + + private final static Map, String> tagTypeToParentClass = new HashMap<>(); + + + static { + tagTypeToParentClass.put(SoundTag.class, "flash.media.Sound"); + tagTypeToParentClass.put(ImageTag.class, "flash.display.Bitmap"); + tagTypeToParentClass.put(FontTag.class, "flash.text.Font"); + tagTypeToParentClass.put(DefineFont4Tag.class, "flash.text.Font"); + tagTypeToParentClass.put(DefineBinaryDataTag.class, "flash.utils.ByteArray"); + tagTypeToParentClass.put(DefineSpriteTag.class, "flash.display.Sprite"); + } + + public static String getParentClassFromCharacter(CharacterTag ch) { + for (Class cls : tagTypeToParentClass.keySet()) { + if (cls.isAssignableFrom(ch.getClass())) { + return tagTypeToParentClass.get(cls); + } + } + return null; + } + + public SetClassToCharacterMappingDialog(Window owner, SWF swf, int characterId) { + super(owner); + + this.swf = swf; + this.characterId = characterId; + + CharacterTag ch = swf.getCharacter(characterId); + if (ch == null) { + throw new RuntimeException("Character " + characterId + " not found"); + } + + parentClassNameTextField.setText(getParentClassFromCharacter(ch)); + + int frame = 1; + for (Tag t : swf.getTags()) { + if (t == ch) { + characterFrame = frame; + } + if (t instanceof ShowFrameTag) { + frame++; + } + } + frame = 1; + for (Tag t : swf.getTags()) { + if (frame >= characterFrame) { + if (t instanceof ABCContainerTag) { + abcCount++; + abcContainers.add((ABCContainerTag) t); + } + if (t instanceof SymbolClassTag) { + symbolClassCount++; + } + } + if (t instanceof ShowFrameTag) { + frame++; + } + } + + + + LinkedHashSet classNames = ch.getClassNames(); + + if (classNames.size() == 1) { + classNameTextField.setText(classNames.iterator().next()); + } + + setDefaultCloseOperation(HIDE_ON_CLOSE); + setTitle(translate("dialog.title")); + + Container cnt = getContentPane(); + cnt.setLayout(new BoxLayout(cnt, BoxLayout.Y_AXIS)); + + + + JPanel classFoundPanel = new JPanel(); + classFoundPanel.setLayout(new BoxLayout(classFoundPanel, BoxLayout.Y_AXIS)); + + JLabel classFoundLabel = new JLabel(translate("class.found")); + classFoundPanel.add(classFoundLabel); + JLabel symbolClassApropriateLabel = new JLabel(translate("symbolClassAppropriate")); + classFoundPanel.add(symbolClassApropriateLabel); + + JPanel classNotFoundPanel = new JPanel(); + classNotFoundPanel.setLayout(new BoxLayout(classNotFoundPanel, BoxLayout.Y_AXIS)); + JLabel notFoundLabel = new JLabel(translate("class.notfound")); + classNotFoundPanel.add(notFoundLabel); + JLabel createAskLabel = new JLabel(translate("class.notfound.createAsk")); + classNotFoundPanel.add(createAskLabel); + + ButtonGroup doCreateClassButtonGroup = new ButtonGroup(); + doCreateClassButtonGroup.add(createClassRadioButton); + doCreateClassButtonGroup.add(onlySetClassNameRadioButton); + + JPanel createNewClassAsk = new JPanel(new FlowLayout()); + createNewClassAsk.add(createClassRadioButton); + createNewClassAsk.add(onlySetClassNameRadioButton); + createClassRadioButton.setSelected(true); + classNotFoundPanel.add(createNewClassAsk); + + + JPanel createNewClassPanel = new JPanel(); + createNewClassPanel.setLayout(new BoxLayout(createNewClassPanel, BoxLayout.Y_AXIS)); + + JLabel parentClassNameLabel = new JLabel(translate("class.notfound.create.parentType")); + createNewClassPanel.add(parentClassNameLabel); + createNewClassPanel.add(parentClassNameTextField); + + JLabel abcWhereLabel = new JLabel(translate("class.notfound.create.abc.where")); + createNewClassPanel.add(abcWhereLabel); + ButtonGroup abcTargetButtonGroup = new ButtonGroup(); + abcTargetButtonGroup.add(existingAbcTagRadioButton); + abcTargetButtonGroup.add(newAbcTagRadioButton); + existingAbcTagRadioButton.setSelected(true); + + JPanel abcTargetPanel = new JPanel(new FlowLayout()); + abcTargetPanel.add(existingAbcTagRadioButton); + abcTargetPanel.add(newAbcTagRadioButton); + + createNewClassPanel.add(abcTargetPanel); + + JLabel symbolClassApropriate2Label = new JLabel(translate("symbolClassAppropriate")); + classFoundPanel.add(symbolClassApropriate2Label); + createNewClassPanel.add(symbolClassApropriate2Label); + + + JPanel doNotCreateNewClassPanel = new JPanel(); + doNotCreateNewClassPanel.setLayout(new BoxLayout(doNotCreateNewClassPanel, BoxLayout.Y_AXIS)); + + ButtonGroup whereToStoreMappingButtonGroup = new ButtonGroup(); + whereToStoreMappingButtonGroup.add(existingSymbolClassTagRadioButton); + whereToStoreMappingButtonGroup.add(newSymbolClassTagRadioButton); + doNotCreateNewClassPanel.add(new JLabel(translate("class.notfound.onlySetClassName.symbolClass.where"))); + + JPanel whereToStoreMappingPanel = new JPanel(new FlowLayout()); + whereToStoreMappingPanel.add(existingSymbolClassTagRadioButton); + whereToStoreMappingPanel.add(newSymbolClassTagRadioButton); + doNotCreateNewClassPanel.add(whereToStoreMappingPanel); + existingSymbolClassTagRadioButton.setSelected(true); + + setCentralAlignment(doNotCreateNewClassPanel); + setCentralAlignment(createNewClassPanel); + + JPanel createNewClassAskCards = new JPanel(new CardLayout()); + createNewClassAskCards.add(createNewClassPanel, CREATE_CLASS_CARD); + createNewClassAskCards.add(doNotCreateNewClassPanel, DO_NOT_CREATE_CLASS_CARD); + + classNotFoundPanel.add(createNewClassAskCards); + + ChangeListener createClassSwitched = new ChangeListener() { + @Override + public void stateChanged(ChangeEvent e) { + CardLayout cl = (CardLayout) createNewClassAskCards.getLayout(); + if (createClassRadioButton.isSelected()) { + cl.show(createNewClassAskCards, CREATE_CLASS_CARD); + } else { + cl.show(createNewClassAskCards, DO_NOT_CREATE_CLASS_CARD); + } + checkEnabled(); + } + }; + + createClassRadioButton.addChangeListener(createClassSwitched); + onlySetClassNameRadioButton.addChangeListener(createClassSwitched); + + JPanel buttonsPanel = new JPanel(new FlowLayout()); + proceedButton.addActionListener(this::okButtonActionPerformed); + cancelButton.addActionListener(this::cancelButtonActionPerformed); + buttonsPanel.add(proceedButton); + buttonsPanel.add(cancelButton); + + JLabel classNameLabel = new JLabel(translate("classname")); + cnt.add(classNameLabel); + cnt.add(classNameTextField); + + JPanel errorPanel = new JPanel(new FlowLayout()); + errorPanel.add(errorLabel); + + + setCentralAlignment(classFoundPanel); + setCentralAlignment(classNotFoundPanel); + setCentralAlignment(errorPanel); + + classFoundOrNotOrErrorPanel = new JPanel(new CardLayout()); + classFoundOrNotOrErrorPanel.add(classFoundPanel, CLASS_FOUND_CARD); + classFoundOrNotOrErrorPanel.add(classNotFoundPanel, CLASS_NOT_FOUND_CARD); + classFoundOrNotOrErrorPanel.add(errorPanel, ERROR_CARD); + + cnt.add(classFoundOrNotOrErrorPanel); + + cnt.add(buttonsPanel); + + setCentralAlignment((JComponent)cnt); + + if (abcCount == 0) { + newAbcTagRadioButton.setSelected(true); + //abcTargetPanel.setVisible(false); + newAbcTagRadioButton.setEnabled(false); + existingAbcTagRadioButton.setEnabled(false); + } + + if (symbolClassCount == 0) { + newSymbolClassTagRadioButton.setSelected(true); + newSymbolClassTagRadioButton.setEnabled(false); + existingSymbolClassTagRadioButton.setEnabled(false); + } + + + + existingSymbolClassTagRadioButton.addChangeListener(new ChangeListener() { + @Override + public void stateChanged(ChangeEvent e) { + checkEnabled(); + } + }); + newSymbolClassTagRadioButton.addChangeListener(new ChangeListener() { + @Override + public void stateChanged(ChangeEvent e) { + checkEnabled(); + } + }); + + existingAbcTagRadioButton.addChangeListener(new ChangeListener() { + @Override + public void stateChanged(ChangeEvent e) { + checkEnabled(); + } + }); + newAbcTagRadioButton.addChangeListener(new ChangeListener() { + @Override + public void stateChanged(ChangeEvent e) { + checkEnabled(); + } + }); + + classNameTextField.getDocument().addDocumentListener(new DocumentListener() { + @Override + public void insertUpdate(DocumentEvent e) { + checkEnabled(); + } + + @Override + public void removeUpdate(DocumentEvent e) { + checkEnabled(); + } + + @Override + public void changedUpdate(DocumentEvent e) { + checkEnabled(); + } + }); + + classNameTextField.addActionListener(this::okButtonActionPerformed); + checkEnabled(); + pack(); + setModal(true); + setResizable(false); + View.setWindowIcon(this); + View.centerScreen(this); + } + + private void setCentralAlignment(JComponent container) { + Component[] components = container.getComponents(); + for (Component component : components) { + if (component instanceof JComponent) { + ((JComponent) component).setAlignmentX(Component.CENTER_ALIGNMENT); + } + } + } + + private void checkEnabled() { + + boolean ok = true; + + String newClassName = classNameTextField.getText(); + + if (newClassName.isEmpty()) { + ok = false; + errorLabel.setText(""); + } + + if (newClassName.endsWith(".")) { + ok = false; + errorLabel.setText(""); + } + + if (ok) { + CharacterTag ch = swf.getCharacter(characterId); + LinkedHashSet oldClassNames = ch.getClassNames(); + if (oldClassNames.size() > 1) { + ok = false; + errorLabel.setText(translate("error.multipleClasses")); + } else if (oldClassNames.size() == 1 && newClassName.equals(oldClassNames.iterator().next())) { + ok = false; + errorLabel.setText(translate("error.needToModify")); + } else if (swf.getCharacterByClass(newClassName) != null) { + ok = false; + errorLabel.setText(translate("error.alreadyAssignedClass")); + } + } + + CardLayout cl = (CardLayout) classFoundOrNotOrErrorPanel.getLayout(); + + if (ok) { + List classNames = new ArrayList<>(); + classNames.add(newClassName); + foundInAbcContainer = null; + try { + List scriptPacks = swf.getScriptPacksByClassNames(classNames); + if (!scriptPacks.isEmpty()) { + ABC foundInAbc = scriptPacks.get(0).abc; //Assume there is only single pack with the class + + boolean foundContainer = false; + for (ABCContainerTag cnt : abcContainers) { + if (cnt.getABC() == foundInAbc) { + foundInAbcContainer = cnt; + break; + } + } + } + } catch (Exception ex) { + //ignore + } + + if (foundInAbcContainer != null) { + cl.show(classFoundOrNotOrErrorPanel, CLASS_FOUND_CARD); + } else { + cl.show(classFoundOrNotOrErrorPanel, CLASS_NOT_FOUND_CARD); + + if (createClassRadioButton.isSelected()) { + if (existingAbcTagRadioButton.isSelected() && abcCount == 1) { + proceedButton.setText(translate("button.ok")); + } else { + proceedButton.setText(translate("button.proceed")); + } + } else { + if (existingSymbolClassTagRadioButton.isSelected() && symbolClassCount == 1) { + proceedButton.setText(translate("button.ok")); + } else { + proceedButton.setText(translate("button.proceed")); + } + } + } + } else { + cl.show(classFoundOrNotOrErrorPanel,ERROR_CARD); + proceedButton.setText(translate("button.ok")); + } + + proceedButton.setEnabled(ok); + } + + private void okButtonActionPerformed(ActionEvent evt) { + if (!proceedButton.isEnabled()) { + return; + } + setVisible(false); + if (foundInAbcContainer == null) { + if (createClassRadioButton.isSelected()) { + if (existingAbcTagRadioButton.isSelected()) { + SelectTagOfTypeDialog selectDoABCDialog = new SelectTagOfTypeDialog(owner, swf, ABCContainerTag.class, "DoABC", characterFrame); + selectedAbcContainer = (ABCContainerTag) selectDoABCDialog.showDialog(); + if (selectedAbcContainer == null) { + cancelButtonActionPerformed(evt); + return; + } + } + if (newAbcTagRadioButton.isSelected()) { + SelectTagPositionDialog selectTagPositionDialog = new SelectTagPositionDialog(owner, swf, false, "DoABC", characterFrame); + if (selectTagPositionDialog.showDialog() != OK_OPTION) { + cancelButtonActionPerformed(evt); + return; + } + selectedPosition = selectTagPositionDialog.getSelectedTag(); + selectedTimelined = selectTagPositionDialog.getSelectedTimelined(); + } + } else { + if (existingSymbolClassTagRadioButton.isSelected()) { + SelectTagOfTypeDialog selectSymbolClassDialog = new SelectTagOfTypeDialog(owner, swf, SymbolClassTag.class, "SymbolClass", characterFrame); + selectedSymbolClassTag = (SymbolClassTag) selectSymbolClassDialog.showDialog(); + if (selectedSymbolClassTag == null) { + cancelButtonActionPerformed(evt); + return; + } + } + if (newSymbolClassTagRadioButton.isSelected()) { + SelectTagPositionDialog selectTagPositionDialog = new SelectTagPositionDialog(owner, swf, false, "SymbolClass", characterFrame); + if (selectTagPositionDialog.showDialog() != OK_OPTION) { + cancelButtonActionPerformed(evt); + return; + } + selectedPosition = selectTagPositionDialog.getSelectedTag(); + selectedTimelined = selectTagPositionDialog.getSelectedTimelined(); + } + } + } else { + selectedAbcContainer = foundInAbcContainer; + } + + + if (selectedAbcContainer != null) { + int frame = 1; + for (Tag t : swf.getTags()) { + if (t == selectedAbcContainer) { + abcFrame = frame; + break; + } + if (t instanceof ShowFrameTag) { + frame++; + } + } + } + + createClass = foundInAbcContainer == null && createClassRadioButton.isSelected(); + + result = OK_OPTION; + selectedClass = classNameTextField.getText(); + selectedParentClass = parentClassNameTextField.getText(); + setVisible(false); + } + + private void cancelButtonActionPerformed(ActionEvent evt) { + selectedClass = null; + selectedParentClass = null; + selectedAbcContainer = null; + selectedPosition = null; + selectedTimelined = null; + selectedSymbolClassTag = null; + foundInAbcContainer = null; + createClass = false; + abcFrame = -1; + characterFrame = -1; + result = CANCEL_OPTION; + setVisible(false); + } + + public int showDialog() { + selectedClass = null; + selectedParentClass = null; + selectedAbcContainer = null; + selectedPosition = null; + selectedTimelined = null; + selectedSymbolClassTag = null; + foundInAbcContainer = null; + createClass = false; + abcFrame = -1; + setVisible(true); + return result; + } + + public Tag getSelectedPosition() { + return selectedPosition; + } + + public String getSelectedClass() { + return selectedClass; + } + + public String getSelectedParentClass() { + return selectedParentClass; + } + + public SymbolClassTag getSelectedSymbolClassTag() { + return selectedSymbolClassTag; + } + + public Timelined getSelectedTimelined() { + return selectedTimelined; + } + + public ABCContainerTag getSelectedAbcContainer() { + return selectedAbcContainer; + } + + public boolean isClassFound() { + return foundInAbcContainer != null; + } + + public int getCharacterFrame() { + return characterFrame; + } + + public int getAbcFrame() { + return abcFrame; + } + + public boolean doCreateClass() { + return createClass; + } +} diff --git a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties index 58a72d460..feb7d863c 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties @@ -1259,4 +1259,6 @@ contextmenu.showInFramesFolder = Show in frames folder contextmenu.collapseAll = Collapse all contextmenu.collectDepthAsSprites = Collect tags at same depth as sprites -preview.resample = Resample sound to 44kHz \ No newline at end of file +preview.resample = Resample sound to 44kHz + +contextmenu.setClassToCharacterMapping = Set class to character mapping \ 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 7c35eb9b2..111df55d0 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_cs.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_cs.properties @@ -1234,4 +1234,6 @@ contextmenu.showInFramesFolder = Zobrazit ve slo\u017ece sn\u00edmk\u016f contextmenu.collapseAll = Sbalit v\u0161e contextmenu.collectDepthAsSprites = Posb\u00edrat tagy ve stejn\u00e9 hloubce do sprit\u016f -preview.resample = Resample sound to 44kHz \ No newline at end of file +preview.resample = Resample sound to 44kHz + +contextmenu.setClassToCharacterMapping = Nastavit mapov\u00e1n\u00ed t\u0159\u00eddy na charakter \ No newline at end of file diff --git a/src/com/jpexs/decompiler/flash/gui/locales/SelectTagPositionDialog.properties b/src/com/jpexs/decompiler/flash/gui/locales/SelectTagPositionDialog.properties index 787146a54..aeb42589f 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/SelectTagPositionDialog.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/SelectTagPositionDialog.properties @@ -17,3 +17,4 @@ dialog.title = Select tag position in %filetitle% button.ok = OK button.cancel = Cancel timeline.end = end of timeline +dialog.title.new.typed = Select new %type% tag position in %filetitle% \ No newline at end of file diff --git a/src/com/jpexs/decompiler/flash/gui/locales/SelectTagPositionDialog_cs.properties b/src/com/jpexs/decompiler/flash/gui/locales/SelectTagPositionDialog_cs.properties index 77d3b490d..f7c209784 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/SelectTagPositionDialog_cs.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/SelectTagPositionDialog_cs.properties @@ -17,3 +17,4 @@ dialog.title = Vyberte pozici tagu v %filetitle% button.ok = OK button.cancel = Storno timeline.end = konec timeliny +dialog.title.new.typed = Vyberte pozici nov\u00e9ho tagu %type% v %filetitle% diff --git a/src/com/jpexs/decompiler/flash/gui/locales/abc/SelectTagOfTypeDialog.properties b/src/com/jpexs/decompiler/flash/gui/locales/abc/SelectTagOfTypeDialog.properties new file mode 100644 index 000000000..7cb165d4f --- /dev/null +++ b/src/com/jpexs/decompiler/flash/gui/locales/abc/SelectTagOfTypeDialog.properties @@ -0,0 +1,18 @@ +# Copyright (C) 2022 JPEXS +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +dialog.title = Select %type% tag +button.ok = OK +button.cancel = Cancel diff --git a/src/com/jpexs/decompiler/flash/gui/locales/abc/SelectTagOfTypeDialog_cs.properties b/src/com/jpexs/decompiler/flash/gui/locales/abc/SelectTagOfTypeDialog_cs.properties new file mode 100644 index 000000000..df30cb5d5 --- /dev/null +++ b/src/com/jpexs/decompiler/flash/gui/locales/abc/SelectTagOfTypeDialog_cs.properties @@ -0,0 +1,18 @@ +# Copyright (C) 2022 JPEXS +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +dialog.title = Vybrat %type% tag +button.ok = OK +button.cancel = Storno diff --git a/src/com/jpexs/decompiler/flash/gui/locales/abc/SetClassToCharacterMappingDialog.properties b/src/com/jpexs/decompiler/flash/gui/locales/abc/SetClassToCharacterMappingDialog.properties new file mode 100644 index 000000000..5cbcaf9b4 --- /dev/null +++ b/src/com/jpexs/decompiler/flash/gui/locales/abc/SetClassToCharacterMappingDialog.properties @@ -0,0 +1,43 @@ +# 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 . + +dialog.title = Set class to character mapping +button.ok = OK +button.proceed = Proceed +button.cancel = Cancel + +classname = Fully qualified class name: + +error.multipleClasses = Error: This character has already assigned more than single class, it cannot be renamed via this tool. However, you can still manually modify the SymbolClass tag. +error.alreadyAssignedClass = Error: This class is already assigned to different character +error.needToModify = Modify the classname to a new name. + +class.found = Existing class with the name found. +class.notfound = Class with the name does not exist yet. + +symbolClassAppropriate = SymbolClass tag in the nearest appropriate frame will be modified or created. + +class.notfound.createAsk = Do you want the class to be created? + +class.notfound.create = Yes, create class +class.notfound.create.parentType = Parent class name (fully qualified): +class.notfound.create.abc.where = Where to create byte code: +class.notfound.create.abc.where.existing = Existing DoABC tag +class.notfound.create.abc.where.new = New DoABC tag + +class.notfound.onlySetClassName = No, just assign class name +class.notfound.onlySetClassName.symbolClass.where = Where to store the mapping: +class.notfound.onlySetClassName.symbolClass.where.existing = Existing SymbolClass tag +class.notfound.onlySetClassName.symbolClass.where.new = New SymbolClass tag \ No newline at end of file diff --git a/src/com/jpexs/decompiler/flash/gui/locales/abc/SetClassToCharacterMappingDialog_cs.properties b/src/com/jpexs/decompiler/flash/gui/locales/abc/SetClassToCharacterMappingDialog_cs.properties new file mode 100644 index 000000000..c700f1ab3 --- /dev/null +++ b/src/com/jpexs/decompiler/flash/gui/locales/abc/SetClassToCharacterMappingDialog_cs.properties @@ -0,0 +1,43 @@ +# 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 . + +dialog.title = Nastavit mapov\u00e1n\u00ed t\u0159\u00eddy na charakter +button.ok = OK +button.proceed = Pokra\u010dovat +button.cancel = Storno + +classname = Pln\u011b kvalifikovan\u00fd n\u00e1zev t\u0159\u00eddy: + +error.multipleClasses = Chyba: Tento charakter m\u00e1 ji\u017e p\u0159i\u0159azeno v\u00edce jak jednu t\u0159\u00eddu, nelze ho p\u0159ejmenovat t\u00edmto n\u00e1strojem. Ka\u017edopadn\u011b m\u016f\u017eete st\u00e1le upravit SymbolClass tag ru\u010dn\u011b. +error.alreadyAssignedClass = Chyba: Tato t\u0159\u00edda je ji\u017e p\u0159i\u0159azena jin\u00e9mu charakteru +error.needToModify = Upravte n\u00e1zev t\u0159\u00eddy na n\u011bjak\u00fd nov\u00fd n\u00e1zev. + +class.found = Nalezena existuj\u00edc\u00ed t\u0159\u00edda s t\u00edmto n\u00e1zvem. +class.notfound = T\u0159\u00edda s t\u00edmto n\u00e1zvem zat\u00edm neexistuje. + +symbolClassAppropriate = Bude vytvo\u0159en nebo upraven SymbolClass tag v nejbli\u017e\u0161\u00edm vhodn\u00e9m framu. + +class.notfound.createAsk = Chcete tuto t\u0159\u00eddu vytvo\u0159it? + +class.notfound.create = Ano, vytvo\u0159it t\u0159\u00eddu +class.notfound.create.parentType = N\u00e1zev rodi\u010dovsk\u00e9 t\u0159\u00eddy (pln\u011b kvalifikovan\u00fd): +class.notfound.create.abc.where = Kam um\u00edstit bajt k\u00f3d: +class.notfound.create.abc.where.existing = Existuj\u00edc\u00ed DoABC tag +class.notfound.create.abc.where.new = Nov\u00fd DoABC tag + +class.notfound.onlySetClassName = Ne, jen p\u0159i\u0159adit n\u00e1zev t\u0159\u00eddy +class.notfound.onlySetClassName.symbolClass.where = Kam um\u00edstit mapov\u00e1n\u00ed: +class.notfound.onlySetClassName.symbolClass.where.existing = Existuj\u00edc\u00ed SymbolClass tag +class.notfound.onlySetClassName.symbolClass.where.new = Nov\u00fd SymbolClass tag \ 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 474a202ad..c61b7c388 100644 --- a/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeContextMenu.java +++ b/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeContextMenu.java @@ -47,6 +47,7 @@ import com.jpexs.decompiler.flash.gui.View; import com.jpexs.decompiler.flash.gui.ViewMessages; import com.jpexs.decompiler.flash.gui.abc.ABCExplorerDialog; import com.jpexs.decompiler.flash.gui.abc.AddClassDialog; +import com.jpexs.decompiler.flash.gui.abc.SetClassToCharacterMappingDialog; import com.jpexs.decompiler.flash.gui.abc.ClassesListTreeModel; import com.jpexs.decompiler.flash.gui.action.AddScriptDialog; import com.jpexs.decompiler.flash.helpers.GraphTextWriter; @@ -62,6 +63,7 @@ import com.jpexs.decompiler.flash.tags.DefineSoundTag; import com.jpexs.decompiler.flash.tags.DefineSpriteTag; import com.jpexs.decompiler.flash.tags.DefineVideoStreamTag; import com.jpexs.decompiler.flash.tags.DoABC2Tag; +import com.jpexs.decompiler.flash.tags.DoABCTag; import com.jpexs.decompiler.flash.tags.DoActionTag; import com.jpexs.decompiler.flash.tags.DoInitActionTag; import com.jpexs.decompiler.flash.tags.ExportAssetsTag; @@ -79,6 +81,7 @@ import com.jpexs.decompiler.flash.tags.ShowFrameTag; import com.jpexs.decompiler.flash.tags.SoundStreamBlockTag; import com.jpexs.decompiler.flash.tags.StartSound2Tag; import com.jpexs.decompiler.flash.tags.StartSoundTag; +import com.jpexs.decompiler.flash.tags.SymbolClassTag; import com.jpexs.decompiler.flash.tags.Tag; import com.jpexs.decompiler.flash.tags.TagTypeInfo; import com.jpexs.decompiler.flash.tags.UnknownTag; @@ -172,6 +175,8 @@ public class TagTreeContextMenu extends JPopupMenu { private final MainPanel mainPanel; + private JMenuItem setClassToCharacterMappingMenuItem; + private JMenuItem expandRecursiveMenuItem; private JMenuItem collapseRecursiveMenuItem; @@ -336,8 +341,8 @@ public class TagTreeContextMenu extends JPopupMenu { } public TagTreeContextMenu(final List trees, MainPanel mainPanel) { - this.mainPanel = mainPanel; - + this.mainPanel = mainPanel; + expandRecursiveMenuItem = new JMenuItem(mainPanel.translate("contextmenu.expandAll")); expandRecursiveMenuItem.addActionListener(this::expandRecursiveActionPerformed); expandRecursiveMenuItem.setIcon(View.getIcon("expand16")); @@ -438,6 +443,11 @@ public class TagTreeContextMenu extends JPopupMenu { replaceRefsWithTagMenuItem.addActionListener(this::replaceRefsWithTagActionPerformed); replaceRefsWithTagMenuItem.setIcon(View.getIcon("replacewithtag16")); add(replaceRefsWithTagMenuItem); + + setClassToCharacterMappingMenuItem = new JMenuItem(mainPanel.translate("contextmenu.setClassToCharacterMapping")); //FIXME + setClassToCharacterMappingMenuItem.addActionListener(this::setClassToCharacterMappingActionPerformed); + setClassToCharacterMappingMenuItem.setIcon(View.getIcon("asclass16")); + add(setClassToCharacterMappingMenuItem); abcExplorerMenuItem = new JMenuItem(mainPanel.translate("contextmenu.abcexplorer")); abcExplorerMenuItem.addActionListener(this::abcExplorerActionPerformed); @@ -1104,6 +1114,7 @@ public class TagTreeContextMenu extends JPopupMenu { boolean hasExportableNodes = tree.hasExportableNodes(); + setClassToCharacterMappingMenuItem.setVisible(false); expandRecursiveMenuItem.setVisible(false); collapseRecursiveMenuItem.setVisible(false); pinMenuItem.setVisible(false); @@ -1342,6 +1353,12 @@ public class TagTreeContextMenu extends JPopupMenu { addTagAfterMenu.add(othersMenu);*/ addTagAfterMenu.setVisible(addTagAfterMenu.getItemCount() > 0); + if ((firstItem instanceof CharacterTag)) { + if (SetClassToCharacterMappingDialog.getParentClassFromCharacter((CharacterTag) firstItem) != null) { + setClassToCharacterMappingMenuItem.setVisible(true); + } + } + if (tree.getModel().getChildCount(firstItem) > 0) { expandRecursiveMenuItem.setVisible(true); collapseRecursiveMenuItem.setVisible(true); @@ -1945,7 +1962,7 @@ public class TagTreeContextMenu extends JPopupMenu { selectedTimelined = (SWF) item; } - SelectTagPositionDialog selectPositionDialog = new SelectTagPositionDialog(mainPanel.getMainFrame().getWindow(), swf, selectedTag, selectedTimelined, true, selectNext); + SelectTagPositionDialog selectPositionDialog = new SelectTagPositionDialog(mainPanel.getMainFrame().getWindow(), swf, selectedTag, selectedTimelined, true, selectNext, null, 1); if (selectPositionDialog.showDialog() == AppDialog.OK_OPTION) { selectedTimelined = selectPositionDialog.getSelectedTimelined(); selectedTag = selectPositionDialog.getSelectedTag(); @@ -2485,6 +2502,163 @@ public class TagTreeContextMenu extends JPopupMenu { mainPanel.setTagTreeSelectedNode(mainPanel.getCurrentTree(), ((SWF) itemj.getOpenable()).getCharacter(hasCharacterId.getCharacterId())); } + /* + How set class to character mapping work in AS3: + a) a character is selected + b) if the character already has assigned more than 1 class then exit with a message that user must do this manually + c) new class name is entered, must be different than previous + d) if new class name is already assigned to different character, exit + e) find the ABC, where class with that name is defined + if found + determine its frame + find nearest frame >= ABC frame, such that the character is defined in it (or is defined earlier in the file) + if the SymbolClass exists in that frame, use that one + otherwise create new SymbolClass in that frame + else (not found) + suggest creating new class + select parent class (extends) - the default is set according to type of the character(sound = flash.media.Sound, etc.) + choose whether to use existing ABC or add new ABC + AFTER PRESSING OK: + select existing ABC or add new ABC (select its position). + find nearest frame >= ABC frame, such that the character is defined in it (or is defined earlier in the file) + if the SymbolClass exists in that frame, use that one + otherwise create new SymbolClass in that frame + + or suggest not creating class, set only its name + choose whether to use existing Symbolclass or add new + AFTER PRESSING OK: + select existing SymbolClass or add new SymbolClass (on selected position). + (Selected frame for target SymbolClass must have defined the character in it or earlier in the file) + AFTER PRESSING OK: + I. if there is previously assigned classname, find associated SymbolClass and remove the mapping from it + II. if the previous Symbolclass is empty, remove it + III. add new character mapping to new SymbolClass determined + */ + private void setClassToCharacterMappingActionPerformed(ActionEvent evt) { + CharacterTag ch = (CharacterTag) getCurrentItem(); + SWF swf = ch.getSwf(); + SetClassToCharacterMappingDialog d = new SetClassToCharacterMappingDialog(Main.getDefaultDialogsOwner(), swf, ch.getCharacterId()); + if (d.showDialog() != AppDialog.OK_OPTION) { + return; + } + + int abcFrame = d.getAbcFrame(); + int characterFrame = d.getCharacterFrame(); + + SymbolClassTag selectedSymbolClass = d.getSelectedSymbolClassTag(); + ABCContainerTag selectedAbcContainer = d.getSelectedAbcContainer(); + String className = d.getSelectedClass(); + String parentClassName = d.getSelectedParentClass(); + + if (!d.isClassFound() && !d.doCreateClass() && selectedSymbolClass == null) { //we selected position of new SymbolClass + selectedSymbolClass = new SymbolClassTag(swf); + selectedSymbolClass.setTimelined(swf); + Tag pos = d.getSelectedPosition(); + if (pos == null) { + swf.addTag(selectedSymbolClass); + } else { + swf.addTag(swf.indexOfTag(pos), selectedSymbolClass); + } + } + + if (!d.isClassFound() && d.doCreateClass()) { + if (d.getSelectedAbcContainer() == null) { + selectedAbcContainer = new DoABC2Tag(swf); + ((Tag)selectedAbcContainer).setTimelined(swf); + Tag pos = d.getSelectedPosition(); + if (pos == null) { + swf.addTag((Tag) selectedAbcContainer); + } else { + swf.addTag(swf.indexOfTag(pos), (Tag) selectedAbcContainer); + } + } + + String pkg = className.contains(".") ? className.substring(0, className.lastIndexOf(".")) : ""; + String classSimpleName = className.contains(".") ? className.substring(className.lastIndexOf(".") + 1) : className; + String fileName = className.replace(".", "/"); + String[] pkgParts = new String[0]; + if (!pkg.isEmpty()) { + if (pkg.contains(".")) { + pkgParts = pkg.split("\\."); + } else { + pkgParts = new String[]{pkg}; + } + } + try { + AbcIndexing abcIndex = swf.getAbcIndex(); + abcIndex.selectAbc(selectedAbcContainer.getABC()); + ActionScript3Parser parser = new ActionScript3Parser(abcIndex); + + DottedChain dc = new DottedChain(pkgParts); + String script = "package " + dc.toPrintableString(true) + " {" + + (parentClassName.isEmpty() ? "" : "import " + parentClassName + ";") + + "public class " + IdentifiersDeobfuscation.printIdentifier(true, classSimpleName) + (parentClassName.isEmpty() ? "" : " extends " + parentClassName) + " {" + + " }" + + "}"; + parser.addScript(script, fileName, 0, 0, swf.getDocumentClass()); + } catch (IOException | InterruptedException | AVM2ParseException | CompilationException ex) { + Logger.getLogger(TagTreeContextMenu.class.getName()).log(Level.SEVERE, "Error during script compilation", ex); + } + + ((Tag) selectedAbcContainer).setModified(true); + } + + if (selectedSymbolClass == null) { + int symbolClassFrame = Math.max(abcFrame, characterFrame); + int frame = 1; + int pos = 0; + int symbolClassPos = -1; + for (Tag t : swf.getTags()) { + if ((t instanceof SymbolClassTag) && (symbolClassFrame == frame)) { + selectedSymbolClass = (SymbolClassTag) t; + break; + } + if (t instanceof ShowFrameTag) { + if (symbolClassFrame == frame) { + symbolClassPos = pos; + } + frame++; + } + pos++; + } + if (selectedSymbolClass == null) { + if (symbolClassPos == -1) { + symbolClassPos = pos; + } + selectedSymbolClass = new SymbolClassTag(swf); + selectedSymbolClass.setTimelined(swf); + swf.addTag(symbolClassPos, selectedSymbolClass); + } + } + + + //remove previously assigned name + ReadOnlyTagList tags = swf.getTags(); + for (int j = 0; j < tags.size(); j++) { + Tag t = tags.get(j); + if (t instanceof SymbolClassTag) { + SymbolClassTag sct = (SymbolClassTag) t; + for (int i = sct.tags.size() - 1; i >= 0; i--) { + if (sct.tags.get(i) == ch.getCharacterId()) { + sct.names.remove(i); + sct.tags.remove(i); + } + } + if (sct.names.isEmpty() && sct != selectedSymbolClass) { + swf.removeTag(t); + } + } + } + + selectedSymbolClass.tags.add(ch.getCharacterId()); + selectedSymbolClass.names.add(className); + + swf.clearAllCache(); + swf.assignClassesToSymbols(); + swf.setModified(true); + mainPanel.refreshTree(swf); + } + private void expandRecursiveActionPerformed(ActionEvent evt) { AbstractTagTree tree = getTree(); TreePath path = tree.getFullModel().getTreePath(getCurrentItem()); @@ -3315,6 +3489,8 @@ public class TagTreeContextMenu extends JPopupMenu { public void run() { Map> tagsToRemoveBySwf = new HashMap<>(); Set swfsToClearCache = new HashSet<>(); + Set swfToAssignClassesToSymbols = new HashSet<>(); + Set swfToAssignExportNamesToSymbols = new HashSet<>(); for (int i = 0; i < itemsToRemove.size(); i++) { Object item = itemsToRemove.get(i); @@ -3422,7 +3598,7 @@ public class TagTreeContextMenu extends JPopupMenu { } } } - } + } if (item instanceof TreeItem) { mainPanel.unpinItem((TreeItem) item); @@ -3449,13 +3625,24 @@ public class TagTreeContextMenu extends JPopupMenu { } for (Tag tag : tagsToRemove) { + + SWF swf = tag.getSwf(); + + if (tag instanceof SymbolClassTag) { + swfToAssignClassesToSymbols.add(swf); + } + + if (tag instanceof ExportAssetsTag) { + swfToAssignExportNamesToSymbols.add(swf); + } + if (!tagsToRemoveBySwf.containsKey(swf)) { tagsToRemoveBySwf.put(swf, new ArrayList<>()); } tagsToRemoveBySwf.get(swf).add(tag); - mainPanel.unpinItem(tag); + mainPanel.unpinItem(tag); } for (SWF swf : tagsToRemoveBySwf.keySet()) { @@ -3484,6 +3671,14 @@ public class TagTreeContextMenu extends JPopupMenu { swf.clearAllCache(); } + for (SWF swf : swfToAssignClassesToSymbols) { + swf.assignClassesToSymbols(); + } + + for (SWF swf: swfToAssignExportNamesToSymbols) { + swf.assignExportNamesToSymbols(); + } + if (!framesToRemove.isEmpty()) { Timelined tim = framesToRemove.get(0).timeline.timelined; SWF swf = tim.getSwf(); @@ -3713,7 +3908,7 @@ public class TagTreeContextMenu extends JPopupMenu { return; } - SelectTagPositionDialog dialog = new SelectTagPositionDialog(Main.getDefaultDialogsOwner(), t.getSwf(), t, timelined, true, false); + SelectTagPositionDialog dialog = new SelectTagPositionDialog(Main.getDefaultDialogsOwner(), t.getSwf(), t, timelined, true, false, null, 1); if (dialog.showDialog() == AppDialog.OK_OPTION) { Tag selectedTag = dialog.getSelectedTag(); Timelined selectedTimelined = dialog.getSelectedTimelined();