diff --git a/CHANGELOG.md b/CHANGELOG.md index d52fb7810..27c6e19cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ All notable changes to this project will be documented in this file. - [#1280] AS3 Direct editation of traits with the same name - [#1743] GFX - Adding DefineExternalImage2 and DefineSubImage tags - [#1822], [#1803] AS3 direct editation - optional using AIR (airglobal.swc) to compile +- [#1501] Bulk import shapes ### Fixed - [#1869] Replace references now replaces all references, not just PlaceObject @@ -30,6 +31,7 @@ All notable changes to this project will be documented in this file. - [#1840] AS3 Direct editation - Type mismatched for a trait - [#1840] Proper if..continue..break handling - [#1877] Recalculate dependent characters and frames on removing / editing item +- DefineShape4 SVG import NullPointerException ### Changed - GFX - DefineExternalImage2 no longer handled as character @@ -2611,6 +2613,7 @@ All notable changes to this project will be documented in this file. [#1743]: https://www.free-decompiler.com/flash/issues/1743 [#1822]: https://www.free-decompiler.com/flash/issues/1822 [#1803]: https://www.free-decompiler.com/flash/issues/1803 +[#1501]: https://www.free-decompiler.com/flash/issues/1501 [#1869]: https://www.free-decompiler.com/flash/issues/1869 [#1872]: https://www.free-decompiler.com/flash/issues/1872 [#1692]: https://www.free-decompiler.com/flash/issues/1692 diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/configuration/Configuration.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/configuration/Configuration.java index 61c539128..7ff607c37 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/configuration/Configuration.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/configuration/Configuration.java @@ -798,6 +798,10 @@ public final class Configuration { @ConfigurationFile(".*\\.swc$") public static ConfigurationItem airLibLocation = null; + @ConfigurationDefaultBoolean(true) + @ConfigurationCategory("ui") + public static ConfigurationItem showImportShapeInfo = null; + private enum OSId { WINDOWS, OSX, UNIX } diff --git a/src/com/jpexs/decompiler/flash/gui/MainFrameMenu.java b/src/com/jpexs/decompiler/flash/gui/MainFrameMenu.java index afc37240a..2f6042ff4 100644 --- a/src/com/jpexs/decompiler/flash/gui/MainFrameMenu.java +++ b/src/com/jpexs/decompiler/flash/gui/MainFrameMenu.java @@ -294,7 +294,21 @@ public abstract class MainFrameMenu implements MenuBuilder { } mainFrame.getPanel().importImage(swf); } + + protected void importShapesActionPerformed(ActionEvent evt) { + if (Main.isWorking()) { + return; + } + mainFrame.getPanel().importShape(swf, false); + } + protected void importShapesNoFillActionPerformed(ActionEvent evt) { + if (Main.isWorking()) { + return; + } + mainFrame.getPanel().importShape(swf, true); + } + protected void importSymbolClassActionPerformed(ActionEvent evt) { if (Main.isWorking()) { return; @@ -932,6 +946,8 @@ public abstract class MainFrameMenu implements MenuBuilder { addMenuItem("/file/import/importScript", translate("menu.file.import.script"), "importscript32", this::importScriptActionPerformed, PRIORITY_MEDIUM, null, true, null, false); addMenuItem("/file/import/importOther", translate("menu.file.import.other"), "importother32", null, PRIORITY_MEDIUM, null, false, null, false); addMenuItem("/file/import/importOther/importImages", translate("menu.file.import.image"), "importimage32", this::importImagesActionPerformed, PRIORITY_MEDIUM, null, true, null, false); + addMenuItem("/file/import/importOther/importShapes", translate("menu.file.import.shape"), "importshape32", this::importShapesActionPerformed, PRIORITY_MEDIUM, null, true, null, false); + addMenuItem("/file/import/importOther/importShapesNoFill", translate("menu.file.import.shapeNoFill"), "importshape32", this::importShapesNoFillActionPerformed, PRIORITY_MEDIUM, null, true, null, false); addMenuItem("/file/import/importOther/importSymbolClass", translate("menu.file.import.symbolClass"), "importsymbolclass32", this::importSymbolClassActionPerformed, PRIORITY_MEDIUM, null, true, null, false); finishMenu("/file/import/importOther"); finishMenu("/file/import"); diff --git a/src/com/jpexs/decompiler/flash/gui/MainPanel.java b/src/com/jpexs/decompiler/flash/gui/MainPanel.java index e4dab41a0..1e64df7fb 100644 --- a/src/com/jpexs/decompiler/flash/gui/MainPanel.java +++ b/src/com/jpexs/decompiler/flash/gui/MainPanel.java @@ -2799,6 +2799,99 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se } } + public void importShape(final SWF swf, boolean noFill) { + ViewMessages.showMessageDialog(MainPanel.this, translate("message.info.importShapes"), translate("message.info"), JOptionPane.INFORMATION_MESSAGE, Configuration.showImportShapeInfo); + JFileChooser chooser = new JFileChooser(); + chooser.setCurrentDirectory(new File(Configuration.lastExportDir.get())); + chooser.setDialogTitle(translate("import.select.directory")); + chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + chooser.setAcceptAllFileFilterUsed(false); + if (chooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) { + String selFile = Helper.fixDialogFile(chooser.getSelectedFile()).getAbsolutePath(); + File shapesDir = new File(Path.combine(selFile, ShapeExportSettings.EXPORT_FOLDER_NAME)); + ShapeImporter shapeImporter = new ShapeImporter(); + SvgImporter svgImporter = new SvgImporter(); + + final long timeBefore = System.currentTimeMillis(); + new CancellableWorker() { + + private int count = 0; + + @Override + public Void doInBackground() throws Exception { + try { + Map characters = swf.getCharacters(); + List extensions = Arrays.asList("svg", "png", "jpg", "jpeg", "gif", "bmp"); + for (int characterId : characters.keySet()) { + CharacterTag tag = characters.get(characterId); + if (tag instanceof ShapeTag) { + ShapeTag shapeTag = (ShapeTag) tag; + List existingFilesForImageTag = new ArrayList<>(); + for (String ext : extensions) { + File sourceFile = new File(Path.combine(shapesDir.getPath(), "" + characterId + "." + ext)); + if (sourceFile.exists()) { + existingFilesForImageTag.add(sourceFile); + } + } + + if (existingFilesForImageTag.isEmpty()) { + continue; + } + + if (existingFilesForImageTag.size() > 1) { + Logger.getLogger(MainPanel.class.getName()).log(Level.WARNING, "Multiple matching files for shape tag {0} exists, {1} selected", new Object[]{characterId, existingFilesForImageTag.get(0).getName()}); + } + File sourceFile = existingFilesForImageTag.get(0); + + try { + if (sourceFile.getAbsolutePath().toLowerCase().endsWith(".svg")) { + svgImporter.importSvg(shapeTag, Helper.readTextFile(sourceFile.getAbsolutePath()), !noFill); + } else { + shapeImporter.importImage(shapeTag, Helper.readFile(sourceFile.getAbsolutePath()), 0, !noFill); + } + count++; + } catch (IOException ex) { + Logger.getLogger(MainPanel.class.getName()).log(Level.WARNING, "Cannot import shape " + characterId + " from file " + sourceFile.getName(), ex); + } + if (Thread.currentThread().isInterrupted()) { + break; + } + } + } + swf.clearImageCache(); + swf.clearShapeCache(); + } catch (Exception ex) { + logger.log(Level.SEVERE, "Error during import", ex); + ViewMessages.showMessageDialog(null, translate("error.import") + ": " + ex.getClass().getName() + " " + ex.getLocalizedMessage()); + } + return null; + } + + @Override + protected void onStart() { + Main.startWork(translate("work.importing") + "...", this); + } + + @Override + protected void done() { + Main.stopWork(); + long timeAfter = System.currentTimeMillis(); + final long timeMs = timeAfter - timeBefore; + + View.execInEventDispatch(() -> { + refreshTree(swf); + setStatus(translate("import.finishedin").replace("%time%", Helper.formatTimeSec(timeMs))); + + ViewMessages.showMessageDialog(MainPanel.this, translate("import.shape.result").replace("%count%", Integer.toString(count))); + if (count != 0) { + reload(true); + } + }); + } + }.execute(); + } + } + public void importImage(final SWF swf) { ViewMessages.showMessageDialog(MainPanel.this, translate("message.info.importImages"), translate("message.info"), JOptionPane.INFORMATION_MESSAGE, Configuration.showImportImageInfo); JFileChooser chooser = new JFileChooser(); diff --git a/src/com/jpexs/decompiler/flash/gui/graphics/importshape16.png b/src/com/jpexs/decompiler/flash/gui/graphics/importshape16.png new file mode 100644 index 000000000..cc3a18c46 Binary files /dev/null and b/src/com/jpexs/decompiler/flash/gui/graphics/importshape16.png differ diff --git a/src/com/jpexs/decompiler/flash/gui/graphics/importshape32.png b/src/com/jpexs/decompiler/flash/gui/graphics/importshape32.png new file mode 100644 index 000000000..dd98daa99 Binary files /dev/null and b/src/com/jpexs/decompiler/flash/gui/graphics/importshape32.png differ diff --git a/src/com/jpexs/decompiler/flash/gui/locales/AdvancedSettingsDialog.properties b/src/com/jpexs/decompiler/flash/gui/locales/AdvancedSettingsDialog.properties index cd15d1392..5689fbe6c 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/AdvancedSettingsDialog.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/AdvancedSettingsDialog.properties @@ -608,3 +608,6 @@ config.description.maxCachedTime = Maximum time in milliseconds before item (whi config.name.airLibLocation = 6) AIR library path (airglobal.swc) config.description.airLibLocation = Location of airglobal.swc AIR library. It can be used mostly for AS3 compilation. + +config.name.showImportShapeInfo = Show information before importing shapes +config.description.showImportShapeInfo = Displays some info about how importing shapes works after clicking Import shapes in the menu. diff --git a/src/com/jpexs/decompiler/flash/gui/locales/AdvancedSettingsDialog_cs.properties b/src/com/jpexs/decompiler/flash/gui/locales/AdvancedSettingsDialog_cs.properties index 06d8f76b8..db751089b 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/AdvancedSettingsDialog_cs.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/AdvancedSettingsDialog_cs.properties @@ -595,3 +595,6 @@ config.description.maxCachedTime = Maxim\u00e1ln\u00ed \u010das v milisekund\u00 config.name.airLibLocation = 6) Cesta ke knihovn\u011b AIR (airglobal.swc) config.description.airLibLocation = Um\u00edst\u011bn\u00ed knihovny AIR s n\u00e1zvem "airglobal.swc". M\u016f\u017ee b\u00fdt pou\u017eitou zejm\u00e9na pro AS3 kompilaci. + +config.name.showImportShapeInfo = Zobrazit informaci p\u0159ed importem tvar\u016f +config.description.showImportShapeInfo = Zobraz\u00ed n\u011bjak\u00e9 informace o tom jak import tvar\u016f funguje po kliku na import tvar\u016f v menu. diff --git a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties index 6c8369490..82a90e7f6 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties @@ -960,3 +960,12 @@ button.freeze = Freeze (Do not animate subsprites) library = Library: message.airpath.lib.notset = AirGlobal (.SWC) not found. Please configure its path in Advanced Settings / Paths (6). message.action.airglobal.title = AirGlobal library needed + +menu.file.import.shape = Import shapes +menu.file.import.shapeNoFill = Import shapes - update bounds + +message.info.importShapes = During importing shapes, you need to select a FOLDER.\r\n \ + The folder must contain "shapes" subfolder and filenames inside must match existing shapes in current selected SWF.\r\n \ + The best way to get the structure right is to export shapes in current SWF file first. + +import.shape.result = %count% shapes imported. 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 8d9df0652..7805ec12e 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_cs.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_cs.properties @@ -453,7 +453,7 @@ message.confirm.experimental.function = Tato funkce je EXPERIMENT\u00c1LN\u00cd. message.confirm.donotshowagain = P\u0159\u00ed\u0161t\u011b nezobrazovat menu.import = Import -menu.file.import.text = Import textu +menu.file.import.text = Importovat text import.select.directory = Vyberte adres\u00e1\u0159 pro import error.text.import = Chyba b\u011bhem importu textu. Chcete pokra\u010dovat? @@ -569,7 +569,7 @@ text.align.translatex.increase = Zv\u00fd\u0161it TranslateX selectPreviousTag = Vybrat p\u0159edchoz\u00ed tag selectNextTag = Vybrat dal\u0161\u00ed tag button.ignoreAll = Ignorovat V\u0161e -menu.file.import.symbolClass = Import Symbol-Class +menu.file.import.symbolClass = Importovat Symbol-Class text.toggleCase = P\u0159epnout mal\u00e1/velk\u00e1 #after version 5.0.2 @@ -814,7 +814,7 @@ message.info.importScripts = B\u011bhem importu skript\u016f mus\u00edte vybrat message.info.importTexts = B\u011bhem importu text\u016f mus\u00edte vybrat SLO\u017dKU.\r\n \ Slo\u017eka mus\u00ed obsahovat podslo\u017eku "texts" a n\u00e1zvy soubor\u016f v n\u00ed mus\u00ed souhlasit s existuj\u00edc\u00edmi texty v pr\u00e1v\u011b vybran\u00e9m SWF.\r\n \ - Nejlep\u0161\u00ed zp\u016fsob jak m\u00edt tuto strukturu spr\u00e1vn\u011b je nejprve exportovat texty v aktu\u00e1ln\u00edm SWF souboru. + Nejlep\u0161\u00ed zp\u016fsob jak m\u00edt tuto strukturu spr\u00e1vn\u011b je nejprve exportovat texty v aktu\u00e1ln\u00edm SWF souboru. message.info.importSymbolClass = B\u011bhem importu Symbol-Class mus\u00edte vybrat SLO\u017dKU obsahuj\u00edc\u00ed soubor "%file%".\r\n \ To je stejn\u00fd n\u00e1zev jako se pou\u017e\u00edv\u00e1 p\u0159i exportu. @@ -902,7 +902,7 @@ menu.file.import.image = Importovat obr\u00e1zky message.info.importImages = B\u011bhem importu text\u016f mus\u00edte vybrat SLO\u017dKU.\r\n \ Slo\u017eka mus\u00ed obsahovat podslo\u017eku "images" a n\u00e1zvy soubor\u016f v n\u00ed mus\u00ed souhlasit s existuj\u00edc\u00edmi obr\u00e1zky v pr\u00e1v\u011b vybran\u00e9m SWF.\r\n \ - Nejlep\u0161\u00ed zp\u016fsob jak m\u00edt tuto strukturu spr\u00e1vn\u011b je nejprve exportovat obr\u00e1zky v aktu\u00e1ln\u00edm SWF souboru. + Nejlep\u0161\u00ed zp\u016fsob jak m\u00edt tuto strukturu spr\u00e1vn\u011b je nejprve exportovat obr\u00e1zky v aktu\u00e1ln\u00edm SWF souboru. work.importing = Importov\u00e1n\u00ed @@ -932,3 +932,12 @@ button.freeze = Zmrazit (Neanimovat podsprity) library = Knihovna: message.airpath.lib.notset = AirGlobal (.SWC) nenalezen. Pros\u00edm nastavte cestu k n\u011bmu v Pokro\u010dil\u00e1 nastaven\u00ed / Cesty (6). message.action.airglobal.title = Vy\u017eadov\u00e1na knihovna AirGlobal + +menu.file.import.shape = Importovat tvary +menu.file.import.shapeNoFill = Importovat tvary - aktualizovat hranice + +message.info.importShapes = B\u011bhem importu tvar\u016f mus\u00edte vybrat SLO\u017dKU.\r\n \ + Slo\u017eka mus\u00ed obsahovat podslo\u017eku "shapes" a n\u00e1zvy soubor\u016f v n\u00ed mus\u00ed souhlasit s existuj\u00edc\u00edmi tvary v pr\u00e1v\u011b vybran\u00e9m SWF.\r\n \ + Nejlep\u0161\u00ed zp\u016fsob jak m\u00edt tuto strukturu spr\u00e1vn\u011b je nejprve exportovat tvary v aktu\u00e1ln\u00edm SWF souboru. + +import.shape.result = %count% tvar\u016f importov\u00e1no. \ 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 e5412f060..b374fc1e8 100644 --- a/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeContextMenu.java +++ b/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeContextMenu.java @@ -159,6 +159,10 @@ public class TagTreeContextMenu extends JPopupMenu { private JMenuItem importImagesMenuItem; + private JMenuItem importShapesMenuItem; + + private JMenuItem importShapesNoFillMenuItem; + private JMenuItem importSymbolClassMenuItem; private JMenuItem closeMenuItem; @@ -332,6 +336,16 @@ public class TagTreeContextMenu extends JPopupMenu { importImagesMenuItem.addActionListener(this::importImagesActionPerformed); importImagesMenuItem.setIcon(View.getIcon("importimage16")); add(importImagesMenuItem); + + importShapesMenuItem = new JMenuItem(mainPanel.translate("menu.file.import.shape")); + importShapesMenuItem.addActionListener(this::importShapesActionPerformed); + importShapesMenuItem.setIcon(View.getIcon("importshape16")); + add(importShapesMenuItem); + + importShapesNoFillMenuItem = new JMenuItem(mainPanel.translate("menu.file.import.shapeNoFill")); + importShapesNoFillMenuItem.addActionListener(this::importShapesNoFillActionPerformed); + importShapesNoFillMenuItem.setIcon(View.getIcon("importshape16")); + add(importShapesNoFillMenuItem); importSymbolClassMenuItem = new JMenuItem(mainPanel.translate("menu.file.import.symbolClass")); importSymbolClassMenuItem.addActionListener(this::importSymbolClassActionPerformed); @@ -2975,6 +2989,16 @@ public class TagTreeContextMenu extends JPopupMenu { SWF swf = getTree().getCurrentTreeItem().getSwf(); mainPanel.importImage(swf); } + + public void importShapesActionPerformed(ActionEvent evt) { + SWF swf = getTree().getCurrentTreeItem().getSwf(); + mainPanel.importShape(swf, false); + } + + public void importShapesNoFillActionPerformed(ActionEvent evt) { + SWF swf = getTree().getCurrentTreeItem().getSwf(); + mainPanel.importShape(swf, true); + } public void importSymbolClassActionPerformed(ActionEvent evt) { SWF swf = getTree().getCurrentTreeItem().getSwf();