diff --git a/CHANGELOG.md b/CHANGELOG.md index 70673a2b4..487354129 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ All notable changes to this project will be documented in this file. - [#289] Support for Aero Snap on Windows - [#2412] Show coordinates on stage mouse move for up to 2 decimal places - "Show detail" context menu item for items in folder preview +- [#1682] AS2 - Adding class by context menu on packages ### Fixed - [#2456] FLA export - NullPointer exception while exporting to CS4 or lower via commandline @@ -3811,6 +3812,7 @@ Major version of SWF to XML export changed to 2. [#1418]: https://www.free-decompiler.com/flash/issues/1418 [#289]: https://www.free-decompiler.com/flash/issues/289 [#2412]: https://www.free-decompiler.com/flash/issues/2412 +[#1682]: https://www.free-decompiler.com/flash/issues/1682 [#2456]: https://www.free-decompiler.com/flash/issues/2456 [#2459]: https://www.free-decompiler.com/flash/issues/2459 [#2427]: https://www.free-decompiler.com/flash/issues/2427 @@ -4073,7 +4075,6 @@ Major version of SWF to XML export changed to 2. [#1888]: https://www.free-decompiler.com/flash/issues/1888 [#1892]: https://www.free-decompiler.com/flash/issues/1892 [#355]: https://www.free-decompiler.com/flash/issues/355 -[#1682]: https://www.free-decompiler.com/flash/issues/1682 [#1981]: https://www.free-decompiler.com/flash/issues/1981 [#1982]: https://www.free-decompiler.com/flash/issues/1982 [#1986]: https://www.free-decompiler.com/flash/issues/1986 diff --git a/src/com/jpexs/decompiler/flash/gui/action/AddScriptDialog.java b/src/com/jpexs/decompiler/flash/gui/action/AddScriptDialog.java index 3fe36535b..62a9b29dc 100644 --- a/src/com/jpexs/decompiler/flash/gui/action/AddScriptDialog.java +++ b/src/com/jpexs/decompiler/flash/gui/action/AddScriptDialog.java @@ -46,6 +46,8 @@ import java.awt.Window; import java.awt.event.ActionEvent; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; import java.util.ArrayList; import java.util.Collections; import java.util.Enumeration; @@ -122,6 +124,11 @@ public class AddScriptDialog extends AppDialog { private List existingClasses = new ArrayList<>(); private List spriteIdsWithDoInitAction; + private boolean createClassSet = false; + + private final JPanel classPanel; + private final JPanel topPanel; + public AddScriptDialog(Window owner, SWF swf) { super(owner); setDefaultCloseOperation(HIDE_ON_CLOSE); @@ -151,7 +158,7 @@ public class AddScriptDialog extends AppDialog { panButtons.add(okButton); panButtons.add(cancelButton); - JPanel topPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); + topPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); JLabel typeLabel = new JLabel(translate("type")); typeComboBox = new JComboBox<>(new String[]{ @@ -196,7 +203,7 @@ public class AddScriptDialog extends AppDialog { centerPanel.add(createSpriteInitPanel(), "" + TYPE_SPRITE_INIT); centerPanel.add(createButtonPanel(), "" + TYPE_BUTTON_EVENT); centerPanel.add(createInstancePanel(), "" + TYPE_INSTANCE_EVENT); - centerPanel.add(createClassPanel(), "" + TYPE_CLASS); + centerPanel.add(classPanel = createClassPanel(), "" + TYPE_CLASS); cnt.add(centerPanel, BorderLayout.CENTER); cnt.add(panButtons, BorderLayout.SOUTH); @@ -204,10 +211,33 @@ public class AddScriptDialog extends AppDialog { setSize(900, 600); setModal(true); setResizable(true); - View.setWindowIcon(this); + View.setWindowIcon(this, "scriptadd"); View.centerScreen(this); checkEnabled(); + addWindowListener(new WindowAdapter() { + @Override + public void windowOpened(WindowEvent e) { + if (createClassSet) { + classNameTextField.requestFocusInWindow(); + } + } + }); + } + + public void setCreateClassOnly(String prefillClassName) { + setTitle(translate("dialog.title.combined").replace("%title%", translate("dialog.title")).replace("%type%", translate("type.class"))); + typeComboBox.setSelectedIndex(5); + centerPanel.removeAll(); + centerPanel.add(classPanel, "" + TYPE_CLASS); + ((CardLayout) centerPanel.getLayout()).show(centerPanel, "" + TYPE_CLASS); + topPanel.setVisible(false); + pack(); + classNameTextField.setText(prefillClassName); + classNameTextField.setSelectionStart(prefillClassName.length()); + createClassSet = true; + checkEnabled(); + View.centerScreen(this); } private JPanel createFramePanel(DocumentListener checkEnabledDocumentListener) { @@ -973,6 +1003,9 @@ public class AddScriptDialog extends AppDialog { } if (type == TYPE_CLASS) { + if (classNameTextField.getText().trim().endsWith(".")) { + okButton.setEnabled(false); + } if (classNameTextField.getText().trim().isEmpty()) { okButton.setEnabled(false); } diff --git a/src/com/jpexs/decompiler/flash/gui/graphics/scriptadd32.png b/src/com/jpexs/decompiler/flash/gui/graphics/scriptadd32.png new file mode 100644 index 000000000..572a5e3f8 Binary files /dev/null and b/src/com/jpexs/decompiler/flash/gui/graphics/scriptadd32.png differ diff --git a/src/com/jpexs/decompiler/flash/gui/locales/action/AddScriptDialog.properties b/src/com/jpexs/decompiler/flash/gui/locales/action/AddScriptDialog.properties index f7beace6a..cb6e16755 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/action/AddScriptDialog.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/action/AddScriptDialog.properties @@ -11,3 +11,5 @@ type.instance.event = Instance event (CLIPACTIONRECORD) type.class = AS2 Class classname = Fully qualified class name: message.classexists = Class with this name already exists. Please select another name. +# In some cases we display only part of the dialog (like add class), in such case, dialog title is modified +dialog.title.combined = %title% - %type% \ No newline at end of file diff --git a/src/com/jpexs/decompiler/flash/gui/locales/action/AddScriptDialog_cs.properties b/src/com/jpexs/decompiler/flash/gui/locales/action/AddScriptDialog_cs.properties index eba458ba4..2a06f2fff 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/action/AddScriptDialog_cs.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/action/AddScriptDialog_cs.properties @@ -11,3 +11,5 @@ type.instance.event = Ud\u00e1lost instance (CLIPACTIONRECORD) type.class = AS2 T\u0159\u00edda classname = Pln\u011b kvalifikovan\u00fd n\u00e1zev t\u0159\u00eddy message.classexists = T\u0159\u00edda tohoto n\u00e1zvu ji\u017e existuje. Pros\u00edm zvolte jin\u00fd n\u00e1zev. +# In some cases we display only part of the dialog (like add class), in such case, dialog title is modified +dialog.title.combined = %title% - %type% \ 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 5aa12f1d9..6e611f119 100644 --- a/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeContextMenu.java +++ b/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeContextMenu.java @@ -305,12 +305,14 @@ public class TagTreeContextMenu extends JPopupMenu { private JMenuItem addAs12SpriteInitScriptMenuItem; + private JMenuItem addAs12ClassMenuItem; + private JMenuItem addAs3ClassMenuItem; private JMenuItem textSearchMenuItem; private JMenuItem moveTagMenuItem; - + private JMenuItem showDetailMenuItem; private JMenuItem showInResourcesViewTagMenuItem; @@ -318,7 +320,7 @@ public class TagTreeContextMenu extends JPopupMenu { private JMenuItem showInTagListViewTagMenuItem; private JMenuItem showInHexDumpViewTagMenuItem; - + private JMenuItem showInEasyViewTagMenuItem; private JMenuItem showInFramesFolderMenuItem; @@ -380,7 +382,7 @@ public class TagTreeContextMenu extends JPopupMenu { showDetailMenuItem.setIcon(View.getIcon("detail16")); add(showDetailMenuItem); addSeparator(); - + expandRecursiveMenuItem = new JMenuItem(mainPanel.translate("contextmenu.expandAll")); expandRecursiveMenuItem.addActionListener(this::expandRecursiveActionPerformed); expandRecursiveMenuItem.setIcon(View.getIcon("expand16")); @@ -415,17 +417,17 @@ public class TagTreeContextMenu extends JPopupMenu { configurePathResolvingMenuItem.setIcon(View.getIcon("settings16")); add(configurePathResolvingMenuItem); - addSeparator(); + addSeparator(); jumpToCharacterMenuItem = new JMenuItem(mainPanel.translate("contextmenu.jumpToCharacter")); jumpToCharacterMenuItem.addActionListener(this::jumpToCharacterActionPerformed); jumpToCharacterMenuItem.setIcon(View.getIcon("jumpto16")); - add(jumpToCharacterMenuItem); - + add(jumpToCharacterMenuItem); + showInFramesFolderMenuItem = new JMenuItem(mainPanel.translate("contextmenu.showInFramesFolder")); showInFramesFolderMenuItem.addActionListener(this::showInFramesFolderActionPerformed); showInFramesFolderMenuItem.setIcon(View.getIcon("frame16")); add(showInFramesFolderMenuItem); - + showInResourcesViewTagMenuItem = new JMenuItem(mainPanel.translate("contextmenu.showInResources")); showInResourcesViewTagMenuItem.addActionListener(this::showInResourcesViewActionPerformed); showInResourcesViewTagMenuItem.setIcon(View.getIcon("folder16")); @@ -440,7 +442,7 @@ public class TagTreeContextMenu extends JPopupMenu { showInHexDumpViewTagMenuItem.addActionListener(this::showInHexDumpViewActionPerformed); showInHexDumpViewTagMenuItem.setIcon(View.getIcon("viewhex16")); add(showInHexDumpViewTagMenuItem); - + showInEasyViewTagMenuItem = new JMenuItem(mainPanel.translate("contextmenu.showInEasy")); showInEasyViewTagMenuItem.addActionListener(this::showInEasyViewActionPerformed); showInEasyViewTagMenuItem.setIcon(View.getIcon("easy16")); @@ -622,6 +624,11 @@ public class TagTreeContextMenu extends JPopupMenu { addAs12SpriteInitScriptMenuItem.setIcon(View.getIcon("scriptadd16")); add(addAs12SpriteInitScriptMenuItem); + addAs12ClassMenuItem = new JMenuItem(mainPanel.translate("contextmenu.addClass")); + addAs12ClassMenuItem.addActionListener(this::addAs12ClassActionPerformed); + addAs12ClassMenuItem.setIcon(View.getIcon("scriptadd16")); + add(addAs12ClassMenuItem); + addAs3ClassMenuItem = new JMenuItem(mainPanel.translate("contextmenu.addClass")); addAs3ClassMenuItem.addActionListener(this::addAs3ClassActionPerformed); addAs3ClassMenuItem.setIcon(View.getIcon("scriptadd16")); @@ -1326,6 +1333,7 @@ public class TagTreeContextMenu extends JPopupMenu { addAs12ButtonEventScriptMenuItem.setVisible(false); addAs12InstanceEventScriptMenuItem.setVisible(false); addAs12SpriteInitScriptMenuItem.setVisible(false); + addAs12ClassMenuItem.setVisible(false); addAs3ClassMenuItem.setVisible(false); textSearchMenuItem.setVisible(hasScripts || hasTexts); moveTagMenuItem.setVisible(items.size() == 1 && (items.get(0) instanceof Tag)); @@ -1418,6 +1426,24 @@ public class TagTreeContextMenu extends JPopupMenu { if ((firstItem instanceof DefineSpriteTag) || ((firstItem instanceof TagScript) && ((TagScript) firstItem).getTag() instanceof DefineSpriteTag)) { addAs12SpriteInitScriptMenuItem.setVisible(true); } + if (firstItem instanceof AS2Package) { + AS2Package pkg = (AS2Package) firstItem; + AbstractTagTreeModel model = mainPanel.getCurrentTree().getFullModel(); + TreeItem it = pkg; + String firstPkg = null; + while (it instanceof AS2Package) { + pkg = (AS2Package) it; + if (pkg.isDefaultPackage()) { + firstPkg = ""; + } else { + firstPkg = pkg.getName(); + } + it = model.getParent(it); + } + if ("__Packages".equals(firstPkg)) { + addAs12ClassMenuItem.setVisible(true); + } + } if (firstItem instanceof ClassesListTreeModel) { addAs3ClassMenuItem.setVisible(true); if (firstItem.getOpenable() instanceof SWF) { @@ -1615,7 +1641,7 @@ public class TagTreeContextMenu extends JPopupMenu { addFramesBeforeMenuItem.setVisible(true); addFramesAfterMenuItem.setVisible(true); } - + if (inFolder) { showDetailMenuItem.setVisible(true); } @@ -1636,7 +1662,7 @@ public class TagTreeContextMenu extends JPopupMenu { if (firstItem instanceof Timelined) { showInEasyViewTagMenuItem.setVisible(true); } - + if ((firstItem instanceof Tag) || (firstItem instanceof CLIPACTIONRECORD) || (firstItem instanceof BUTTONRECORD) @@ -3224,6 +3250,33 @@ public class TagTreeContextMenu extends JPopupMenu { Main.getMainFrame().getPanel().searchInActionScriptOrText(null, getCurrentItem().getOpenable(), true); } + private void addAs12ClassActionPerformed(ActionEvent evt) { + AS2Package pkg = (AS2Package) getCurrentItem(); + AbstractTagTreeModel model = mainPanel.getCurrentTree().getFullModel(); + TreeItem it = pkg; + List pkgParts = new ArrayList<>(); + while (it instanceof AS2Package) { + pkg = (AS2Package) it; + if (!pkg.isDefaultPackage()) { + pkgParts.add(0, pkg.getName()); + } + it = model.getParent(it); + } + pkgParts.remove(0); + String fullPkgName = String.join(".", pkgParts); + SWF swf = (SWF) pkg.getOpenable(); + AddScriptDialog addScriptDialog = new AddScriptDialog(Main.getDefaultDialogsOwner(), swf); + String prefix = fullPkgName; + if (!prefix.isEmpty()) { + prefix += "."; + } + addScriptDialog.setCreateClassOnly(prefix); + if (addScriptDialog.showDialog() != JOptionPane.OK_OPTION) { + return; + } + createClass(addScriptDialog.getClassName(), swf); + } + private void addAs3ClassActionPerformed(ActionEvent evt) { AbstractTagTree tree = getTree(); //using tagTree only here is safe since tagListTree does not have AS3 classes @@ -3729,6 +3782,130 @@ public class TagTreeContextMenu extends JPopupMenu { addSpriteInitScript(sprite.getSwf(), sprite); } + private void createClass(String className, SWF swf) { + ReadOnlyTagList tags = swf.getTags(); + List exportedIds = new ArrayList<>(); + for (int i = 0; i < tags.size(); i++) { + if (tags.get(i) instanceof ExportAssetsTag) { + ExportAssetsTag ea = (ExportAssetsTag) tags.get(i); + exportedIds.addAll(ea.tags); + } + } + + int insertPos = -1; + for (int i = 0; i < tags.size(); i++) { + if (tags.get(i) instanceof DoInitActionTag) { + DoInitActionTag doinit = (DoInitActionTag) tags.get(i); + if (!exportedIds.contains(doinit.spriteId)) { + //this is #initpragma, make sure class is inserted before it + insertPos = i; + break; + } + } + } + if (insertPos == -1) { + for (int i = 0; i < tags.size(); i++) { + if (tags.get(i) instanceof ShowFrameTag) { + insertPos = i; + break; + } + } + } + + if (insertPos > -1) { + int characterId = swf.getNextCharacterId(); + DefineSpriteTag sprite = new DefineSpriteTag(swf); + sprite.spriteId = characterId; + sprite.hasEndTag = true; + sprite.setTimelined(swf); + + String exportName = "__Packages." + className; + + ExportAssetsTag exportAssets = new ExportAssetsTag(swf); + exportAssets.names = new ArrayList<>(); + exportAssets.names.add(exportName); + exportAssets.tags = new ArrayList<>(); + exportAssets.tags.add(characterId); + exportAssets.setTimelined(swf); + + DoInitActionTag doInit = new DoInitActionTag(swf); + doInit.spriteId = characterId; + doInit.setTimelined(swf); + + ActionScript2Parser parser = new ActionScript2Parser(swf, doInit); + + String[] parts = className.contains(".") ? className.split("\\.") : new String[]{className}; + DottedChain dc = new DottedChain(parts); + + try { + List actions = parser.actionsFromString("class " + dc.toPrintableString(false) + "{}", swf.getCharset()); + doInit.setActions(actions); + } catch (ActionParseException | IOException | CompilationException + | InterruptedException ex) { + //ignore + } + + sprite.setExportName(exportName); + + swf.addTag(insertPos, sprite); + swf.addTag(insertPos + 1, exportAssets); + swf.addTag(insertPos + 2, doInit); + + swf.clearAllCache(); + swf.setModified(true); + mainPanel.refreshTree(swf); + + FolderItem scriptsNode = (FolderItem) mainPanel.tagTree.getFullModel().getScriptsNode(swf); + TreePath scriptsPath = mainPanel.tagTree.getFullModel().getTreePath(scriptsNode); + String[] classParts = className.contains(".") ? className.split("\\.") : new String[]{className}; + String pkgName = className.contains(".") ? className.substring(0, className.lastIndexOf(".")) : ""; + + for (TreeItem subItem : scriptsNode.subItems) { + if (subItem instanceof AS2Package) { + AS2Package pkg = (AS2Package) subItem; + if (pkg.getName().equals("__Packages")) { + TreePath classPath = scriptsPath.pathByAddingChild(pkg); + + if (Configuration.flattenASPackages.get()) { + List subs = pkg.getAllChildren(); + for (TreeItem s : subs) { + if (s instanceof AS2Package) { + AS2Package p = (AS2Package) s; + + if ((!p.isDefaultPackage() && pkgName.equals(p.getName())) + || (p.isDefaultPackage() && pkgName.isEmpty())) { + pkg = p; + classPath = classPath.pathByAddingChild(pkg); + break; + } + } + } + } else { + loopi: + for (int i = 0; i < classParts.length - 1; i++) { + List subs = pkg.getAllChildren(); + for (TreeItem s : subs) { + if (s instanceof AS2Package) { + AS2Package p = (AS2Package) s; + if (p.getName().equals(classParts[i])) { + pkg = (AS2Package) s; + classPath = classPath.pathByAddingChild(pkg); + break; + } + } + } + } + } + classPath = classPath.pathByAddingChild(doInit); + mainPanel.tagTree.setSelectionPath(classPath); + break; + } + } + } + + } + } + private void addAs12ScriptActionPerformed(ActionEvent evt) { List sel = getSelectedItems(); if (!sel.isEmpty()) { @@ -3761,110 +3938,7 @@ public class TagTreeContextMenu extends JPopupMenu { } addInstanceEventScript(swf, tim, placeType, frame); } else if (addScriptDialog.getScriptType() == AddScriptDialog.TYPE_CLASS) { - String className = addScriptDialog.getClassName(); - ReadOnlyTagList tags = swf.getTags(); - List exportedIds = new ArrayList<>(); - for (int i = 0; i < tags.size(); i++) { - if (tags.get(i) instanceof ExportAssetsTag) { - ExportAssetsTag ea = (ExportAssetsTag) tags.get(i); - exportedIds.addAll(ea.tags); - } - } - - int insertPos = -1; - for (int i = 0; i < tags.size(); i++) { - if (tags.get(i) instanceof DoInitActionTag) { - DoInitActionTag doinit = (DoInitActionTag) tags.get(i); - if (!exportedIds.contains(doinit.spriteId)) { - //this is #initpragma, make sure class is inserted before it - insertPos = i; - break; - } - } - } - if (insertPos == -1) { - for (int i = 0; i < tags.size(); i++) { - if (tags.get(i) instanceof ShowFrameTag) { - insertPos = i; - break; - } - } - } - - if (insertPos > -1) { - int characterId = swf.getNextCharacterId(); - DefineSpriteTag sprite = new DefineSpriteTag(swf); - sprite.spriteId = characterId; - sprite.hasEndTag = true; - sprite.setTimelined(swf); - - String exportName = "__Packages." + className; - - ExportAssetsTag exportAssets = new ExportAssetsTag(swf); - exportAssets.names = new ArrayList<>(); - exportAssets.names.add(exportName); - exportAssets.tags = new ArrayList<>(); - exportAssets.tags.add(characterId); - exportAssets.setTimelined(swf); - - DoInitActionTag doInit = new DoInitActionTag(swf); - doInit.spriteId = characterId; - doInit.setTimelined(swf); - - ActionScript2Parser parser = new ActionScript2Parser(swf, doInit); - - String[] parts = className.contains(".") ? className.split("\\.") : new String[]{className}; - DottedChain dc = new DottedChain(parts); - - try { - List actions = parser.actionsFromString("class " + dc.toPrintableString(false) + "{}", swf.getCharset()); - doInit.setActions(actions); - } catch (ActionParseException | IOException | CompilationException - | InterruptedException ex) { - //ignore - } - - sprite.setExportName(exportName); - - swf.addTag(insertPos, sprite); - swf.addTag(insertPos + 1, exportAssets); - swf.addTag(insertPos + 2, doInit); - - swf.clearAllCache(); - swf.setModified(true); - mainPanel.refreshTree(swf); - - TreePath selection = mainPanel.tagTree.getSelectionPath(); - TreePath swfPath = selection.getParentPath(); - FolderItem scriptsNode = (FolderItem) mainPanel.tagTree.getFullModel().getScriptsNode(swf); - TreePath scriptsPath = swfPath.pathByAddingChild(scriptsNode); - String[] classParts = className.contains(".") ? className.split("\\.") : new String[]{className}; - - for (TreeItem subItem : scriptsNode.subItems) { - if (subItem instanceof AS2Package) { - AS2Package pkg = (AS2Package) subItem; - if (pkg.getName().equals("__Packages")) { - TreePath classPath = scriptsPath.pathByAddingChild(pkg); - for (int i = 0; i < classParts.length - 1; i++) { - List subs = pkg.getAllChildren(); - for (TreeItem s : subs) { - if (s instanceof AS2Package) { - if (((AS2Package) s).getName().equals(classParts[i])) { - pkg = (AS2Package) s; - classPath = classPath.pathByAddingChild(pkg); - break; - } - } - } - } - classPath = classPath.pathByAddingChild(doInit); - mainPanel.tagTree.setSelectionPath(classPath); - break; - } - } - } - - } + createClass(addScriptDialog.getClassName(), swf); } else if (addScriptDialog.getScriptType() == AddScriptDialog.TYPE_SPRITE_INIT) { DefineSpriteTag sprite = addScriptDialog.getSprite(); addSpriteInitScript(swf, sprite); @@ -4411,7 +4485,7 @@ public class TagTreeContextMenu extends JPopupMenu { mainPanel.folderPreviewPanel.goToSelection(); mainPanel.folderListPanel.goToSelection(); } - + private void showInResourcesViewActionPerformed(ActionEvent evt) { TreeItem item = getCurrentItem(); mainPanel.showView(MainPanel.VIEW_RESOURCES); @@ -4434,7 +4508,7 @@ public class TagTreeContextMenu extends JPopupMenu { } mainPanel.setTagTreeSelectedNode(mainPanel.tagListTree, item); mainPanel.updateMenu(); - } + } private void showInHexDumpViewActionPerformed(ActionEvent evt) { if (mainPanel.isModified()) { @@ -4448,7 +4522,7 @@ public class TagTreeContextMenu extends JPopupMenu { mainPanel.dumpTree.setSelectedItem(item); mainPanel.updateMenu(); } - + private void showInEasyViewActionPerformed(ActionEvent evt) { TreeItem item = getCurrentItem(); if (item instanceof TagScript) {