From 9bbcc2b95895d80b4a84073ca3497096b0b30dc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jindra=20Pet=C5=99=C3=ADk?= Date: Fri, 2 Aug 2024 11:44:05 +0200 Subject: [PATCH] Added: #1290 Export to IntelliJ IDEA project Added: Export FLA context menu on SWFs --- CHANGELOG.md | 2 + .../swf/SwfFlashDevelopExporter.java | 6 - .../swf/SwfIntelliJIdeaExporter.java | 161 ++++++++++++++++++ .../decompiler/flash/gui/MainFrameMenu.java | 17 +- .../jpexs/decompiler/flash/gui/MainPanel.java | 112 +++++++++++- .../flash/gui/graphics/exportidea16.png | Bin 0 -> 5652 bytes .../flash/gui/graphics/exportidea32.png | Bin 0 -> 2065 bytes .../flash/gui/locales/MainFrame.properties | 14 +- .../flash/gui/locales/MainFrame_cs.properties | 14 +- .../flash/gui/locales/MainFrame_de.properties | 2 + .../flash/gui/locales/MainFrame_es.properties | 2 + .../flash/gui/locales/MainFrame_fr.properties | 2 + .../flash/gui/locales/MainFrame_hu.properties | 2 + .../flash/gui/locales/MainFrame_it.properties | 2 + .../flash/gui/locales/MainFrame_ja.properties | 2 + .../flash/gui/locales/MainFrame_nl.properties | 2 + .../flash/gui/locales/MainFrame_pl.properties | 2 + .../flash/gui/locales/MainFrame_pt.properties | 2 + .../gui/locales/MainFrame_pt_BR.properties | 2 + .../flash/gui/locales/MainFrame_ru.properties | 2 + .../flash/gui/locales/MainFrame_sv.properties | 2 + .../flash/gui/locales/MainFrame_tr.properties | 2 + .../flash/gui/locales/MainFrame_uk.properties | 2 + .../flash/gui/locales/MainFrame_zh.properties | 2 + .../flash/gui/tagtree/TagTreeContextMenu.java | 47 +++-- 25 files changed, 381 insertions(+), 22 deletions(-) create mode 100644 libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/swf/SwfIntelliJIdeaExporter.java create mode 100644 src/com/jpexs/decompiler/flash/gui/graphics/exportidea16.png create mode 100644 src/com/jpexs/decompiler/flash/gui/graphics/exportidea32.png diff --git a/CHANGELOG.md b/CHANGELOG.md index 101d44d18..2fc1f4d95 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,8 @@ All notable changes to this project will be documented in this file. - [#2263] Expand one level more (`+` sign) for needed/dependent characters in tag info panel to show full tag name as in tree - [#1290] Export to FlashDevelop project +- [#1290] Export to IntelliJ IDEA project +- Export FLA context menu on SWFs ### Fixed - Debugger - getting children of top level variables diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/swf/SwfFlashDevelopExporter.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/swf/SwfFlashDevelopExporter.java index 1af5a4623..97e97486d 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/swf/SwfFlashDevelopExporter.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/swf/SwfFlashDevelopExporter.java @@ -24,16 +24,10 @@ import com.jpexs.decompiler.flash.configuration.Configuration; import com.jpexs.decompiler.flash.exporters.modes.ScriptExportMode; import com.jpexs.decompiler.flash.exporters.settings.ScriptExportSettings; import com.jpexs.decompiler.flash.tags.SetBackgroundColorTag; -import com.jpexs.decompiler.flash.xfl.XFLXmlWriter; -import com.jpexs.helpers.Helper; -import com.jpexs.helpers.XmlPrettyFormat; import com.jpexs.helpers.utf8.Utf8Helper; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; -import java.util.logging.Level; -import java.util.logging.Logger; -import javax.xml.stream.XMLStreamException; /** * diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/swf/SwfIntelliJIdeaExporter.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/swf/SwfIntelliJIdeaExporter.java new file mode 100644 index 000000000..db9364673 --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/swf/SwfIntelliJIdeaExporter.java @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2010-2024 JPEXS, All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3.0 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. + */ +package com.jpexs.decompiler.flash.exporters.swf; + +import com.jpexs.decompiler.flash.AbortRetryIgnoreHandler; +import com.jpexs.decompiler.flash.EventListener; +import com.jpexs.decompiler.flash.SWF; +import com.jpexs.decompiler.flash.configuration.Configuration; +import com.jpexs.decompiler.flash.exporters.modes.ScriptExportMode; +import com.jpexs.decompiler.flash.exporters.settings.ScriptExportSettings; +import com.jpexs.helpers.utf8.Utf8Helper; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * + * @author JPEXS + */ +public class SwfIntelliJIdeaExporter { + + private static String doubleToString(double d) { + String ds = "" + d; + if (ds.endsWith(".0")) { + ds = ds.substring(0, ds.length() - 2); + } + return ds; + } + + public void exportIntelliJIdeaProject(SWF swf, File outFile, AbortRetryIgnoreHandler handler) throws IOException { + exportIntelliJIdeaProject(swf, outFile, handler, null); + } + + public void exportIntelliJIdeaProject(SWF swf, File outFile, AbortRetryIgnoreHandler handler, EventListener eventListener) throws IOException { + if (!swf.isAS3()) { + throw new IllegalArgumentException("SWF must be AS3"); + } + + String simpleName = outFile.getName(); + if (simpleName.contains(".")) { + simpleName = simpleName.substring(0, simpleName.lastIndexOf(".")); + } + + File baseDir = outFile.getParentFile(); + File ideaDir = new File(baseDir, ".idea"); + + String documentClass = swf.getDocumentClass(); + if (documentClass == null) { + documentClass = ""; + } + + List additionalOptions = new ArrayList<>(); + + additionalOptions.add("-default-size " + Math.round(swf.displayRect.getWidth() / SWF.unitDivisor) + " " + Math.round(swf.displayRect.getHeight() / SWF.unitDivisor)); + additionalOptions.add("-default-frame-rate " + Math.round(swf.frameRate)); + if (swf.getBackgroundColor() != null) { + additionalOptions.add("-default-background-color " + swf.getBackgroundColor().backgroundColor.toHexRGB()); + } + + String imlData = "\n" + + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + ""; + try (FileOutputStream fos = new FileOutputStream(outFile)) { + fos.write(Utf8Helper.getBytes(imlData)); + } + + ideaDir.mkdir(); + + String modulesXml = "\n" + + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + ""; + try (FileOutputStream fos = new FileOutputStream(new File(ideaDir, "modules.xml"))) { + fos.write(Utf8Helper.getBytes(modulesXml)); + } + + String flexCompilerXml = "\n" + + "\n" + + " \n" + + " \n" + + " \n" + + ""; + try (FileOutputStream fos = new FileOutputStream(new File(ideaDir, "flexCompiler.xml"))) { + fos.write(Utf8Helper.getBytes(flexCompilerXml)); + } + + String miscXml = "\n" + + "\n" + + " \n" + + " \n" + + " \n" + + ""; + try (FileOutputStream fos = new FileOutputStream(new File(ideaDir, "misc.xml"))) { + fos.write(Utf8Helper.getBytes(miscXml)); + } + + try (FileOutputStream fos = new FileOutputStream(new File(ideaDir, ".name"))) { + fos.write(Utf8Helper.getBytes(outFile.getName())); + } + + String gitIgnore = "# Default ignored files\n" + + "/shelf/\n" + + "/workspace.xml\n" + + "# Editor-based HTTP Client requests\n" + + "/httpRequests/\n" + + "# Datasource local storage ignored files\n" + + "/dataSources/\n" + + "/dataSources.local.xml"; + try (FileOutputStream fos = new FileOutputStream(new File(ideaDir, ".gitignore"))) { + fos.write(Utf8Helper.getBytes(gitIgnore)); + } + + boolean parallel = Configuration.parallelSpeedUp.get(); + ScriptExportSettings scriptExportSettings = new ScriptExportSettings(ScriptExportMode.AS, false, false, true, false, false); + swf.exportActionScript(handler, new File(outFile.getParentFile(), "src").getAbsolutePath(), scriptExportSettings, parallel, eventListener); + } +} diff --git a/src/com/jpexs/decompiler/flash/gui/MainFrameMenu.java b/src/com/jpexs/decompiler/flash/gui/MainFrameMenu.java index 66d707519..d24490f24 100644 --- a/src/com/jpexs/decompiler/flash/gui/MainFrameMenu.java +++ b/src/com/jpexs/decompiler/flash/gui/MainFrameMenu.java @@ -461,7 +461,18 @@ public abstract class MainFrameMenu implements MenuBuilder { return; } - mainFrame.getPanel().exportFlashDevelopProject((SWF) openable); + mainFrame.getPanel().exportFlashDevelop((SWF) openable); + } + + protected void exportIdeaActionPerformed(ActionEvent evt) { + if (Main.isWorking()) { + return; + } + if (mainFrame.getPanel().checkEdited()) { + return; + } + + mainFrame.getPanel().exportIdea((SWF) openable); } protected void exportFlaActionPerformed(ActionEvent evt) { @@ -1044,6 +1055,8 @@ public abstract class MainFrameMenu implements MenuBuilder { setMenuEnabled("/file/export/exportFla", allSameSwf && openableSelected && !isWorking); setMenuEnabled("_/exportFlashDevelop", swfSelected && !isWorking); setMenuEnabled("/file/export/exportFlashDevelop", allSameSwf && openableSelected && isAs3 && !isWorking); + setMenuEnabled("_/exportIdea", swfSelected && !isWorking); + setMenuEnabled("/file/export/exportIdea", allSameSwf && openableSelected && isAs3 && !isWorking); setMenuEnabled("_/exportSelected", openableSelected && !isWorking); setMenuEnabled("/file/export/exportSelected", openableSelected && !isWorking); setMenuEnabled("/file/export/exportXml", swfSelected && !isWorking); @@ -1142,6 +1155,7 @@ public abstract class MainFrameMenu implements MenuBuilder { addSeparator("_"); addMenuItem("_/exportFla", translate("menu.file.export.fla"), "exportfla32", this::exportFlaActionPerformed, PRIORITY_TOP, null, true, null, false); addMenuItem("_/exportFlashDevelop", translate("menu.file.export.flashDevelop"), "exportflashdevelop32", this::exportFlashDevelopActionPerformed, PRIORITY_TOP, null, true, null, false); + addMenuItem("_/exportIdea", translate("menu.file.export.idea"), "exportidea32", this::exportIdeaActionPerformed, PRIORITY_TOP, null, true, null, false); addMenuItem("_/exportAll", translate("menu.file.export.all"), "export32", this::exportAllActionPerformed, PRIORITY_TOP, null, true, null, false); addMenuItem("_/exportSelected", translate("menu.file.export.selection"), "exportsel32", this::exportSelectedActionPerformed, PRIORITY_TOP, null, true, null, false); addSeparator("_"); @@ -1175,6 +1189,7 @@ public abstract class MainFrameMenu implements MenuBuilder { addMenuItem("/file/export", translate("menu.export"), null, null, 0, null, false, null, false); addMenuItem("/file/export/exportFla", translate("menu.file.export.fla"), "exportfla32", this::exportFlaActionPerformed, PRIORITY_TOP, null, true, null, false); addMenuItem("/file/export/exportFlashDevelop", translate("menu.file.export.flashDevelop"), "exportflashdevelop32", this::exportFlashDevelopActionPerformed, PRIORITY_TOP, null, true, null, false); + addMenuItem("/file/export/exportIdea", translate("menu.file.export.idea"), "exportidea32", this::exportIdeaActionPerformed, PRIORITY_TOP, null, true, null, false); addMenuItem("/file/export/exportXml", translate("menu.file.export.xml"), "exportxml32", this::exportXmlActionPerformed, PRIORITY_MEDIUM, null, true, null, false); addMenuItem("/file/export/exportAll", translate("menu.file.export.all"), "export16", this::exportAllActionPerformed, PRIORITY_MEDIUM, null, true, new HotKey("CTRL+SHIFT+E"), false); addMenuItem("/file/export/exportSelected", translate("menu.file.export.selection"), "exportsel16", this::exportSelectedActionPerformed, PRIORITY_MEDIUM, null, true, null, false); diff --git a/src/com/jpexs/decompiler/flash/gui/MainPanel.java b/src/com/jpexs/decompiler/flash/gui/MainPanel.java index 2d3461251..b484cebce 100644 --- a/src/com/jpexs/decompiler/flash/gui/MainPanel.java +++ b/src/com/jpexs/decompiler/flash/gui/MainPanel.java @@ -89,6 +89,7 @@ import com.jpexs.decompiler.flash.exporters.settings.SpriteExportSettings; import com.jpexs.decompiler.flash.exporters.settings.SymbolClassExportSettings; import com.jpexs.decompiler.flash.exporters.settings.TextExportSettings; import com.jpexs.decompiler.flash.exporters.swf.SwfFlashDevelopExporter; +import com.jpexs.decompiler.flash.exporters.swf.SwfIntelliJIdeaExporter; import com.jpexs.decompiler.flash.exporters.swf.SwfJavaExporter; import com.jpexs.decompiler.flash.exporters.swf.SwfXmlExporter; import com.jpexs.decompiler.flash.flexsdk.MxmlcAs3ScriptReplacer; @@ -3311,7 +3312,7 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se } - public void exportFlashDevelopProject(final SWF swf) { + public void exportFlashDevelop(final SWF swf) { if (swf == null) { return; } @@ -3419,6 +3420,115 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se } }.execute(); } + + public void exportIdea(final SWF swf) { + if (swf == null) { + return; + } + + JFileChooser fc = new JFileChooser(); + String selDir = Configuration.lastOpenDir.get(); + fc.setCurrentDirectory(new File(selDir)); + if (!selDir.endsWith(File.separator)) { + selDir += File.separator; + } + String swfShortName = swf.getShortFileName(); + if ("".equals(swfShortName)) { + swfShortName = "untitled.swf"; + } + String fileName; + if (swfShortName.contains(".")) { + fileName = swfShortName.substring(0, swfShortName.lastIndexOf(".")) + ".iml"; + } else { + fileName = swfShortName + ".iml"; + } + + FileFilter f = new FileFilter() { + @Override + public boolean accept(File f) { + return f.isDirectory() || (f.getName().toLowerCase(Locale.ENGLISH).endsWith(".iml")); + } + + @Override + public String getDescription() { + return translate("filter.iml"); + } + }; + fc.setFileFilter(f); + fc.setAcceptAllFileFilterUsed(false); + fc.setSelectedFile(new File(selDir + fileName)); + + if (fc.showSaveDialog(this) != JFileChooser.APPROVE_OPTION) { + return; + } + + Configuration.lastOpenDir.set(Helper.fixDialogFile(fc.getSelectedFile()).getParentFile().getAbsolutePath()); + File sf = Helper.fixDialogFile(fc.getSelectedFile()); + SwfIntelliJIdeaExporter exporter = new SwfIntelliJIdeaExporter(); + + String path = sf.getAbsolutePath(); + if (path.endsWith(".iml")) { + path = path.substring(0, path.length() - ".iml".length()); + } + path += ".iml"; + + final String fpath = path; + + long timeBefore = System.currentTimeMillis(); + new CancellableWorker() { + @Override + protected Void doInBackground() throws Exception { + Helper.freeMem(); + + CancellableWorker w = this; + + ProgressListener prog = new ProgressListener() { + @Override + public void progress(int p) { + } + + @Override + public void status(String status) { + Main.startWork(translate("work.exporting.idea") + "..." + status, w); + } + }; + EventListener evl = swf.getExportEventListener(); + try { + AbortRetryIgnoreHandler errorHandler = new GuiAbortRetryIgnoreHandler(); + exporter.exportIntelliJIdeaProject(swf, new File(fpath), errorHandler, evl); + } catch (Exception ex) { + logger.log(Level.SEVERE, "IDEA export error", ex); + ViewMessages.showMessageDialog(MainPanel.this, translate("error.export") + ": " + ex.getClass().getName() + " " + ex.getLocalizedMessage(), translate("error"), JOptionPane.ERROR_MESSAGE); + } + Helper.freeMem(); + return null; + } + + @Override + protected void onStart() { + Main.startWork(translate("work.exporting.idea") + "...", this); + } + + @Override + protected void done() { + Main.stopWork(); + long timeAfter = System.currentTimeMillis(); + final long timeMs = timeAfter - timeBefore; + + View.execInEventDispatch(() -> { + setStatus(translate("export.finishedin").replace("%time%", Helper.formatTimeSec(timeMs))); + }); + + if (Configuration.openFolderAfterFlaExport.get()) { + try { + Desktop.getDesktop().open(new File(fpath).getAbsoluteFile().getParentFile()); + } catch (IOException ex) { + logger.log(Level.SEVERE, null, ex); + } + } + } + }.execute(); + } public void exportFla(final SWF swf) { if (swf == null) { diff --git a/src/com/jpexs/decompiler/flash/gui/graphics/exportidea16.png b/src/com/jpexs/decompiler/flash/gui/graphics/exportidea16.png new file mode 100644 index 0000000000000000000000000000000000000000..b68dd219509747110d7ddcb25ba60ce2ea2b5502 GIT binary patch literal 5652 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 vFkEK!q5vX50`T6a!*CIpDgVv=f7rhPTG2Hhj3