From c8f395b623b176cb8a47c6f44e12131487220ec8 Mon Sep 17 00:00:00 2001 From: Robert Colton Date: Thu, 23 May 2019 08:07:28 -0400 Subject: [PATCH] Included File Editing Support (#434) Completely, brand new, included file editor. The basic decision made here is that we are going with a revamped GameMaker 5 version of data files. This is like GMSv1.4 in that the included file hierarchy is present in the main tree. It differs in that I decided not to open up LGM's main tree to having file extensions in resource names for fear of regression. However, since the new editor, like GM5's, allows you to edit the file name property separately, it should still remain GMSv1.4 compatible. I had to make a lot of fixes to the file readers and writers. We were ignoring that the data files in GMD are zlib compressed. We were ignoring the store option for GMK 800 and 810 include files. We were throwing out the data files hierarchy in the GMD, which we now reuse for includes. In GMX, we needed an enum mapping for the export action in order to use the property link factory. --- org/lateralgm/file/GMXFileReader.java | 24 ++- org/lateralgm/file/GMXFileWriter.java | 5 +- org/lateralgm/file/GmFileReader.java | 124 ++++++++---- org/lateralgm/file/GmFileWriter.java | 60 +++--- org/lateralgm/file/ProjectFile.java | 15 +- org/lateralgm/main/LGM.java | 3 +- org/lateralgm/main/Util.java | 14 +- org/lateralgm/main/icons.properties | 10 +- org/lateralgm/messages/messages.properties | 44 +++-- org/lateralgm/resources/Include.java | 47 ++--- org/lateralgm/subframes/IncludeFrame.java | 207 +++++++++++++++++---- 11 files changed, 367 insertions(+), 186 deletions(-) diff --git a/org/lateralgm/file/GMXFileReader.java b/org/lateralgm/file/GMXFileReader.java index 23075cd52..a57485483 100644 --- a/org/lateralgm/file/GMXFileReader.java +++ b/org/lateralgm/file/GMXFileReader.java @@ -41,6 +41,7 @@ import java.util.LinkedList; import java.util.List; import java.util.Queue; + import javax.imageio.ImageIO; import javax.swing.JProgressBar; import javax.xml.parsers.DocumentBuilder; @@ -106,10 +107,10 @@ import org.lateralgm.resources.sub.View.PView; import org.lateralgm.util.PropertyMap; import org.w3c.dom.Document; +import org.w3c.dom.Element; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; -import org.w3c.dom.Element; import org.xml.sax.SAXException; import com.sun.org.apache.xerces.internal.impl.dv.util.HexBin; @@ -337,7 +338,7 @@ private static void readGroup(ProjectFileContext c, ResNode root, Class kind) Document in = c.in; ResNode node = new ResNode(Resource.kindNamesPlural.get(kind),ResNode.STATUS_PRIMARY,kind,null); root.add(node); - + String kindTagName = GMXFileWriter.tagNames.get(kind); if (kindTagName == null) return; NodeList list = in.getElementsByTagName(kindTagName); @@ -865,7 +866,7 @@ private static void readScript(ProjectFileContext c, ResNode node, Node cNode) node.add(rnode); String path = f.getDirectory() + '/' + Util.getPOSIXPath(cNode.getTextContent()); - try (BufferedReader reader = new BufferedReader(new FileReader(path))) + try (BufferedReader reader = new BufferedReader(new FileReader(path))) { String code = ""; //$NON-NLS-1$ String line = reader.readLine(); @@ -875,7 +876,7 @@ private static void readScript(ProjectFileContext c, ResNode node, Node cNode) line = reader.readLine(); if (line == null) return; } - do + do { if (line.startsWith("#define")) //$NON-NLS-1$ { @@ -917,7 +918,7 @@ private static void readShader(ProjectFileContext c, ResNode node, Node cNode) String code = ""; //$NON-NLS-1$ String path = f.getDirectory() + '/' + Util.getPOSIXPath(cNode.getTextContent()); - try (BufferedReader reader = new BufferedReader(new FileReader(path))) + try (BufferedReader reader = new BufferedReader(new FileReader(path))) { String line = ""; //$NON-NLS-1$ while ((line = reader.readLine()) != null) @@ -1587,7 +1588,9 @@ private static void readInclude(ProjectFileContext c, ResNode node, Node cNode) final Include inc = f.resMap.getList(Include.class).add(); String name = el.getElementsByTagName("name").item(0).getTextContent(); //$NON-NLS-1$ - inc.setName(name); + //NOTE: we don't yet allow file extensions in the resource tree at all + //and it really might not be a good idea to allow that anyway + inc.setName(Util.fileNameWithoutExtension(name)); ResNode rnode = new ResNode(inc.getName(),ResNode.STATUS_SECONDARY,Include.class,inc.reference); node.add(rnode); @@ -1604,17 +1607,18 @@ private static void readInclude(ProjectFileContext c, ResNode node, Node cNode) String exportFolder = el.getElementsByTagName("exportDir").item(0).getTextContent(); //$NON-NLS-1$ inc.put(PInclude.EXPORTFOLDER,exportFolder); int exportAction = Integer.parseInt(el.getElementsByTagName("exportAction").item(0).getTextContent()); //$NON-NLS-1$ - inc.put(PInclude.EXPORTACTION,exportAction); + inc.put(PInclude.EXPORTACTION,ProjectFile.INCLUDE_EXPORT_ACTION[exportAction]); String filename = el.getElementsByTagName("filename").item(0).getTextContent(); //$NON-NLS-1$ inc.put(PInclude.FILENAME,filename); String filePath = filename; ResNode parent = node; while (parent != null && parent.status == ResNode.STATUS_GROUP) { - filePath = parent.toString() + '/' + filePath; + filePath = parent.toString() + File.separatorChar + filePath; parent = (ResNode) parent.getParent(); } - filePath = f.getDirectory() + "/datafiles/" + filePath; //$NON-NLS-1$ + filePath = f.getDirectory() + File.separatorChar + "datafiles" + File.separatorChar + filePath; //$NON-NLS-1$ + inc.put(PInclude.FILEPATH,filePath); File dataFile = new File(filePath); try { @@ -1699,7 +1703,7 @@ private static void readGameInformation(ProjectFileContext c, ResNode root) String text = ""; //$NON-NLS-1$ - try (BufferedReader reader = new BufferedReader(new FileReader(path))) + try (BufferedReader reader = new BufferedReader(new FileReader(path))) { String line = ""; //$NON-NLS-1$ while ((line = reader.readLine()) != null) diff --git a/org/lateralgm/file/GMXFileWriter.java b/org/lateralgm/file/GMXFileWriter.java index 363bfeb33..ac55e386c 100644 --- a/org/lateralgm/file/GMXFileWriter.java +++ b/org/lateralgm/file/GMXFileWriter.java @@ -1158,7 +1158,7 @@ private static void writeRoom(ProjectFileContext c, ResNode resNode, Element dom // Write Backgrounds Element backroot = doc.createElement("backgrounds"); //$NON-NLS-1$ roomroot.appendChild(backroot); - for (BackgroundDef back : room.backgroundDefs) + for (BackgroundDef back : room.backgroundDefs) { PropertyMap props = back.properties; Element bckelement = doc.createElement("background"); //$NON-NLS-1$ @@ -1298,8 +1298,9 @@ private static void writeInclude(ProjectFileContext c, ResNode resNode, Element include.get(PInclude.SIZE).toString())); incRoot.appendChild(createElement(dom,"exportDir", //$NON-NLS-1$ include.get(PInclude.EXPORTFOLDER).toString())); + int exportCode = ProjectFile.INCLUDE_EXPORT_CODE.get(include.get(PInclude.EXPORTACTION)); incRoot.appendChild(createElement(dom,"exportAction", //$NON-NLS-1$ - include.get(PInclude.EXPORTACTION).toString())); + Integer.toString(exportCode))); incRoot.appendChild(createElement(dom,"overwrite", //$NON-NLS-1$ boolToString((Boolean)include.get(PInclude.OVERWRITE)))); incRoot.appendChild(createElement(dom,"store", //$NON-NLS-1$ diff --git a/org/lateralgm/file/GmFileReader.java b/org/lateralgm/file/GmFileReader.java index a684e95bc..5b7397813 100644 --- a/org/lateralgm/file/GmFileReader.java +++ b/org/lateralgm/file/GmFileReader.java @@ -24,6 +24,7 @@ import java.util.zip.DataFormatException; import javax.swing.JProgressBar; +import javax.swing.tree.TreeNode; import org.lateralgm.components.impl.ResNode; import org.lateralgm.file.ProjectFile.ResourceHolder; @@ -47,9 +48,10 @@ import org.lateralgm.resources.GmObject; import org.lateralgm.resources.GmObject.PGmObject; import org.lateralgm.resources.Include; +import org.lateralgm.resources.Include.ExportAction; +import org.lateralgm.resources.Include.PInclude; import org.lateralgm.resources.InstantiableResource; import org.lateralgm.resources.Path; -import org.lateralgm.resources.Shader; import org.lateralgm.resources.Path.PPath; import org.lateralgm.resources.Resource; import org.lateralgm.resources.ResourceReference; @@ -57,6 +59,7 @@ import org.lateralgm.resources.Room.PRoom; import org.lateralgm.resources.Script; import org.lateralgm.resources.Script.PScript; +import org.lateralgm.resources.Shader; import org.lateralgm.resources.Sound; import org.lateralgm.resources.Sound.PSound; import org.lateralgm.resources.Sprite; @@ -478,18 +481,23 @@ private static void readSettingsIncludes(ProjectFile f, GmStreamDecoder in, Game for (int i = 0; i < no; i++) { Include inc = f.resMap.getList(Include.class).add(); - inc.filepath = in.readStr(); - inc.filename = new File(inc.filepath).getName(); + String filepath = in.readStr(); + String filename = new File(filepath).getName(); + inc.put(PInclude.FILEPATH,filepath); + inc.put(PInclude.FILENAME,filename); } gs.put(PGameSettings.INCLUDE_FOLDER,ProjectFile.GS_INCFOLDERS[in.read4()]); // f.gameSettings.includeFolder = in.read4(); //0 = main, 1 = temp in.readBool(gs.properties,PGameSettings.OVERWRITE_EXISTING, PGameSettings.REMOVE_AT_GAME_END); + //1 = temp, 2 = main + ExportAction exportAction = gs.get(PGameSettings.INCLUDE_FOLDER) == IncludeFolder.TEMP + ? ExportAction.TEMP_DIRECTORY : ExportAction.SAME_FOLDER; for (Include inc : f.resMap.getList(Include.class)) { - inc.export = gs.get(PGameSettings.INCLUDE_FOLDER) == IncludeFolder.TEMP ? 1 : 2; //1 = temp, 2 = main - inc.overwriteExisting = gs.get(PGameSettings.OVERWRITE_EXISTING); - inc.removeAtGameEnd = gs.get(PGameSettings.REMOVE_AT_GAME_END); + inc.put(PInclude.EXPORTACTION,exportAction); + inc.put(PInclude.OVERWRITE,gs.get(PGameSettings.OVERWRITE_EXISTING)); + inc.put(PInclude.REMOVEATGAMEEND,gs.get(PGameSettings.REMOVE_AT_GAME_END)); } } @@ -819,7 +827,7 @@ private static void readScripts(ProjectFileContext c) throws IOException,GmForma } } - private static void readFonts(ProjectFileContext c, int ver) throws IOException,GmFormatException + private static void readFonts(ProjectFileContext c, int ver) throws IOException,GmFormatException,DataFormatException { ProjectFile f = c.f; GmStreamDecoder in = c.in; @@ -838,20 +846,22 @@ private static void readFonts(ProjectFileContext c, int ver) throws IOException, throw new GmFormatException(f,Messages.format("ProjectFileReader.ERROR_UNSUPPORTED", //$NON-NLS-1$ Messages.getString("ProjectFileReader.INDATAFILES"),ver)); //$NON-NLS-1$ Include inc = f.resMap.getList(Include.class).add(); - inc.filepath = in.readStr(); - inc.filename = new File(inc.filepath).getName(); + String filepath = in.readStr(); + String filename = new File(filepath).getName(); + inc.put(PInclude.FILEPATH,filepath); + inc.put(PInclude.FILENAME,filename); if (in.readBool()) //file data exists? { - inc.size = in.read4(); - inc.data = new byte[inc.size]; - in.read(inc.data,0,inc.size); + inc.data = in.decompress(in.read4()); + if (inc.data != null) + inc.put(PInclude.SIZE,inc.data.length); } - inc.export = in.read4(); + inc.put(PInclude.EXPORTACTION,ProjectFile.INCLUDE_EXPORT_ACTION[in.read4()]); //FIXME: Deal with Font Includes //if (inc.export == 3) inc.exportFolder = Font Folder? - inc.overwriteExisting = in.readBool(); - inc.freeMemAfterExport = in.readBool(); - inc.removeAtGameEnd = in.readBool(); + inc.put(PInclude.OVERWRITE,in.readBool()); + inc.put(PInclude.FREEMEMORY,in.readBool()); + inc.put(PInclude.REMOVEATGAMEEND,in.readBool()); } return; } @@ -1113,21 +1123,24 @@ private static void readIncludedFiles(ProjectFileContext c) throws IOException,G throw new GmFormatException(f,Messages.format("ProjectFileReader.ERROR_UNSUPPORTED", //$NON-NLS-1$ Messages.getString("ProjectFileReader.ININCLUDEDFILES"),ver)); //$NON-NLS-1$ Include inc = f.resMap.getList(Include.class).add(); - inc.filename = in.readStr(); - inc.filepath = in.readStr(); - inc.isOriginal = in.readBool(); - inc.size = in.read4(); - if (in.readBool()) //store in editable? + inc.put(PInclude.FILENAME,in.readStr()); + inc.put(PInclude.FILEPATH,in.readStr()); + inc.put(PInclude.ORIGINAL,in.readBool()); + int size = in.read4(); + inc.put(PInclude.SIZE,size); + boolean store = in.readBool(); + inc.put(PInclude.STORE,store); + if (store) { int s = in.read4(); inc.data = new byte[s]; in.read(inc.data,0,s); } - inc.export = in.read4(); - inc.exportFolder = in.readStr(); - inc.overwriteExisting = in.readBool(); - inc.freeMemAfterExport = in.readBool(); - inc.removeAtGameEnd = in.readBool(); + inc.put(PInclude.EXPORTACTION,ProjectFile.INCLUDE_EXPORT_ACTION[in.read4()]); + inc.put(PInclude.EXPORTFOLDER,in.readStr()); + inc.put(PInclude.OVERWRITE,in.readBool()); + inc.put(PInclude.FREEMEMORY,in.readBool()); + inc.put(PInclude.REMOVEATGAMEEND,in.readBool()); in.endInflate(); } } @@ -1189,6 +1202,9 @@ private static void readTree(ProjectFileContext c, ResNode root, int ver) throws { byte status = (byte) in.read4(); Class type = ProjectFile.RESOURCE_KIND[in.read4()]; + // It's "Data Files" in GM5, some of which are fonts. + if (ver == 500 && type == Font.class) + type = Include.class; int ind = in.read4(); String name = in.readStr(); boolean hasRef; @@ -1199,11 +1215,18 @@ private static void readTree(ProjectFileContext c, ResNode root, int ver) throws hasRef = false; ResourceList rl = hasRef ? (ResourceList) f.resMap.get(type) : null; ResNode node = new ResNode(name,status,type,hasRef ? rl.getUnsafe(ind).reference : null); + if (ver == 500 && type == Include.class) + { + // Included files don't redundantly store the name with the metadata + // so we need to sync the resource name with the tree name. + if (hasRef) rl.getUnsafe(ind).setName(name); - if (ver == 500 && status == ResNode.STATUS_PRIMARY && type == Font.class) - path.peek().addChild(Messages.getString("LGM.FNT"),status,type); //$NON-NLS-1$ - else - path.peek().add(node); + // GameMaker 5 did not have a dedicated primary fonts group, let's add one. + if (status == ResNode.STATUS_PRIMARY) + path.peek().addChild(Messages.getString("LGM.FNT"),status,Font.class); //$NON-NLS-1$ + } + + path.peek().add(node); int contents = in.read4(); if (contents > 0) { @@ -1216,25 +1239,46 @@ private static void readTree(ProjectFileContext c, ResNode root, int ver) throws rootnodes = left.pop().intValue(); path.pop(); } + } + ResNode incRoot = null; + if (ver == 500) + { + // For GameMaker 5 we need to move the "Data Files" folder + // to the place of the "Includes" folder in LGM's default tree + TreeNode dataFileNode = root.getChildAt(6); + if (dataFileNode instanceof ResNode) + { + incRoot = (ResNode)dataFileNode; + incRoot.setUserObject("Includes"); + root.remove(incRoot); + } } - if (ver <= 540) root.addChild(Messages.getString("LGM.EXT"), //$NON-NLS-1$ + else + { + // All newer GameMaker versions don't have a primary node for included files. + // Therefore we need to create a primary node for them and construct a tree. + incRoot = new ResNode("Includes",ResNode.STATUS_PRIMARY,Include.class); + for (Include inc : f.resMap.getList(Include.class)) + { + String filename = inc.get(PInclude.FILENAME).toString(); + if (!filename.isEmpty()) + inc.setName(Util.fileNameWithoutExtension(filename)); + incRoot.add(new ResNode(inc.getName(),ResNode.STATUS_SECONDARY,Include.class,inc.reference)); + } + } + root.insert(incRoot,9); + + if (ver <= 540) root.addChild("Extension Packages", ResNode.STATUS_SECONDARY,ExtensionPackages.class); - //TODO: This just makes the GMK arrange to the modern version of the IDE + //This just makes the GMK arrange to the modern version of the IDE ResNode node = new ResNode("Shaders",ResNode.STATUS_PRIMARY,Shader.class); root.insert(node,5); node = new ResNode("Extensions",ResNode.STATUS_PRIMARY,Extension.class); - root.insert(node,10); - node = new ResNode("Includes",ResNode.STATUS_PRIMARY,Include.class); - root.insert(node,10); - for (Include inc : f.resMap.getList(Include.class)) - { - node.add(new ResNode(inc.getName(),ResNode.STATUS_SECONDARY,Include.class,inc.reference)); - } + root.insert(node,11); node = new ResNode("Constants",ResNode.STATUS_SECONDARY,Constants.class); root.insert(node,12); - } private static void readActions(ProjectFileContext c, ActionContainer container, String errorKey, diff --git a/org/lateralgm/file/GmFileWriter.java b/org/lateralgm/file/GmFileWriter.java index 7df3e08fb..505e7b89d 100644 --- a/org/lateralgm/file/GmFileWriter.java +++ b/org/lateralgm/file/GmFileWriter.java @@ -16,8 +16,8 @@ import java.awt.image.BufferedImage; import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.nio.charset.Charset; import java.io.OutputStream; +import java.nio.charset.Charset; import java.util.Enumeration; import java.util.List; @@ -29,53 +29,54 @@ import org.lateralgm.main.Util; import org.lateralgm.messages.Messages; import org.lateralgm.resources.Background; +import org.lateralgm.resources.Background.PBackground; import org.lateralgm.resources.Constants; import org.lateralgm.resources.Extension; import org.lateralgm.resources.Font; +import org.lateralgm.resources.Font.PFont; import org.lateralgm.resources.GameInformation; +import org.lateralgm.resources.GameInformation.PGameInformation; import org.lateralgm.resources.GameSettings; +import org.lateralgm.resources.GameSettings.PGameSettings; +import org.lateralgm.resources.GameSettings.ProgressBar; import org.lateralgm.resources.GmObject; +import org.lateralgm.resources.GmObject.PGmObject; import org.lateralgm.resources.Include; +import org.lateralgm.resources.Include.PInclude; import org.lateralgm.resources.InstantiableResource; import org.lateralgm.resources.Path; +import org.lateralgm.resources.Path.PPath; import org.lateralgm.resources.Resource; import org.lateralgm.resources.ResourceReference; import org.lateralgm.resources.Room; +import org.lateralgm.resources.Room.PRoom; import org.lateralgm.resources.Script; +import org.lateralgm.resources.Script.PScript; import org.lateralgm.resources.Shader; import org.lateralgm.resources.Sound; -import org.lateralgm.resources.Sprite; -import org.lateralgm.resources.Timeline; -import org.lateralgm.resources.Background.PBackground; -import org.lateralgm.resources.Font.PFont; -import org.lateralgm.resources.GameInformation.PGameInformation; -import org.lateralgm.resources.GameSettings.PGameSettings; -import org.lateralgm.resources.GameSettings.ProgressBar; -import org.lateralgm.resources.GmObject.PGmObject; -import org.lateralgm.resources.Path.PPath; -import org.lateralgm.resources.Room.PRoom; -import org.lateralgm.resources.Script.PScript; import org.lateralgm.resources.Sound.PSound; +import org.lateralgm.resources.Sprite; import org.lateralgm.resources.Sprite.PSprite; +import org.lateralgm.resources.Timeline; import org.lateralgm.resources.library.LibAction; import org.lateralgm.resources.sub.Action; import org.lateralgm.resources.sub.ActionContainer; import org.lateralgm.resources.sub.Argument; import org.lateralgm.resources.sub.BackgroundDef; -import org.lateralgm.resources.sub.CharacterRange.PCharacterRange; +import org.lateralgm.resources.sub.BackgroundDef.PBackgroundDef; import org.lateralgm.resources.sub.CharacterRange; +import org.lateralgm.resources.sub.CharacterRange.PCharacterRange; import org.lateralgm.resources.sub.Constant; import org.lateralgm.resources.sub.Event; import org.lateralgm.resources.sub.Instance; +import org.lateralgm.resources.sub.Instance.PInstance; import org.lateralgm.resources.sub.MainEvent; import org.lateralgm.resources.sub.Moment; import org.lateralgm.resources.sub.PathPoint; import org.lateralgm.resources.sub.Tile; +import org.lateralgm.resources.sub.Tile.PTile; import org.lateralgm.resources.sub.Trigger; import org.lateralgm.resources.sub.View; -import org.lateralgm.resources.sub.BackgroundDef.PBackgroundDef; -import org.lateralgm.resources.sub.Instance.PInstance; -import org.lateralgm.resources.sub.Tile.PTile; import org.lateralgm.resources.sub.View.PView; import org.lateralgm.util.PropertyMap; @@ -278,7 +279,7 @@ public static void writeSettings(ProjectFile f, GmStreamEncoder out, int ver, lo ResourceList includes = f.resMap.getList(Include.class); out.write4(includes.size()); for (Include inc : includes) - out.writeStr(inc.filepath); + out.writeStr(inc.properties,PInclude.FILEPATH); out.write4(ProjectFile.GS_INCFOLDER_CODE.get(p.get(PGameSettings.INCLUDE_FOLDER))); out.writeBool(p,PGameSettings.OVERWRITE_EXISTING,PGameSettings.REMOVE_AT_GAME_END); } @@ -755,23 +756,22 @@ public static void writeIncludedFiles(ProjectFile f, GmStreamEncoder out, int ve out.writeD(gs.getLastChanged()); } out.write4(ver); - out.writeStr(i.filename); - out.writeStr(i.filepath); - out.writeBool(i.isOriginal); - out.write4(i.size); - if (i.data != null) + out.writeStr(i.properties,PInclude.FILENAME); + out.writeStr(i.properties,PInclude.FILEPATH); + out.writeBool(i.properties,PInclude.ORIGINAL); + out.write4(i.properties,PInclude.SIZE); + boolean store = i.get(PInclude.STORE); + out.writeBool(store); + if (store) { - out.writeBool(true); out.write4(i.data.length); out.write(i.data); } - else - out.writeBool(false); - out.write4(i.export); - out.writeStr(i.exportFolder); - out.writeBool(i.overwriteExisting); - out.writeBool(i.freeMemAfterExport); - out.writeBool(i.removeAtGameEnd); + out.write4(ProjectFile.INCLUDE_EXPORT_CODE.get(i.get(PInclude.EXPORTACTION))); + out.writeStr(i.properties,PInclude.EXPORTFOLDER); + out.writeBool(i.properties,PInclude.OVERWRITE); + out.writeBool(i.properties,PInclude.FREEMEMORY); + out.writeBool(i.properties,PInclude.REMOVEATGAMEEND); out.endDeflate(); } } diff --git a/org/lateralgm/file/ProjectFile.java b/org/lateralgm/file/ProjectFile.java index 3b4687de8..e87852a6f 100644 --- a/org/lateralgm/file/ProjectFile.java +++ b/org/lateralgm/file/ProjectFile.java @@ -59,6 +59,7 @@ import org.lateralgm.resources.GameSettings.Resolution; import org.lateralgm.resources.GmObject; import org.lateralgm.resources.GmObject.PhysicsShape; +import org.lateralgm.resources.Include.ExportAction; import org.lateralgm.resources.InstantiableResource; import org.lateralgm.resources.Path; import org.lateralgm.resources.Resource; @@ -199,6 +200,16 @@ public class ProjectFile implements UpdateListener m.put(MaskShape.POLYGON,m.get(MaskShape.RECTANGLE)); SPRITE_MASK_CODE = Collections.unmodifiableMap(m); } + public static final ExportAction[] INCLUDE_EXPORT_ACTION = { ExportAction.DONT_EXPORT, ExportAction.TEMP_DIRECTORY, + ExportAction.SAME_FOLDER, ExportAction.CUSTOM_FOLDER }; + public static final Map INCLUDE_EXPORT_CODE; + static + { + EnumMap m = new EnumMap(ExportAction.class); + for (int i = 0; i < INCLUDE_EXPORT_ACTION.length; i++) + m.put(INCLUDE_EXPORT_ACTION[i],i); + INCLUDE_EXPORT_CODE = Collections.unmodifiableMap(m); + } public String getPath() { @@ -289,8 +300,8 @@ public void addList(Class kind) public static class FormatFlavor { - public static final String GM_OWNER = "GM"; - public static final String GMX_OWNER = "GMX"; + public static final String GM_OWNER = "GM"; //$NON-NLS-1$ + public static final String GMX_OWNER = "GMX"; //$NON-NLS-1$ public static final FormatFlavor GM_530 = new FormatFlavor(GM_OWNER,530); public static final FormatFlavor GM_600 = new FormatFlavor(GM_OWNER,600); public static final FormatFlavor GM_701 = new FormatFlavor(GM_OWNER,701); diff --git a/org/lateralgm/main/LGM.java b/org/lateralgm/main/LGM.java index 721c1418b..8e8c141e7 100644 --- a/org/lateralgm/main/LGM.java +++ b/org/lateralgm/main/LGM.java @@ -56,6 +56,7 @@ import java.util.Scanner; import java.util.jar.JarFile; import java.util.jar.Manifest; + import javax.imageio.spi.IIORegistry; import javax.swing.AbstractButton; import javax.swing.Box; @@ -134,7 +135,7 @@ public final class LGM { - public static final String version = "1.8.72"; //$NON-NLS-1$ + public static final String version = "1.8.73"; //$NON-NLS-1$ // TODO: This list holds the class loader for any loaded plugins which should be // cleaned up and closed when the application closes. diff --git a/org/lateralgm/main/Util.java b/org/lateralgm/main/Util.java index a53ec91a1..77aa7c1c5 100644 --- a/org/lateralgm/main/Util.java +++ b/org/lateralgm/main/Util.java @@ -66,6 +66,7 @@ import javax.swing.JPanel; import javax.swing.SwingUtilities; import javax.swing.border.Border; + import org.lateralgm.components.CustomFileChooser; import org.lateralgm.components.impl.CustomFileFilter; import org.lateralgm.components.visual.FileChooserImagePreview; @@ -238,7 +239,7 @@ public static byte[] readFully(String path) throws FileNotFoundException, IOExce return Util.readFully(new File(path)); } - public static void writeFully(File file, byte[] data) throws FileNotFoundException, IOException + public static void writeFully(File file, byte[] data) throws FileNotFoundException, IOException { try (FileOutputStream fos = new FileOutputStream(file); BufferedOutputStream bos = new BufferedOutputStream(fos)) @@ -477,6 +478,15 @@ private static String getFileExtension(File file) return name.substring(lastIndexOf + 1); } + public static String fileNameWithoutExtension(String fileName) + { + if (fileName.indexOf('.') > 0) + { + return fileName.substring(0,fileName.indexOf('.')); + } + return fileName; + } + //TODO: JPEG Writing is bugged in some newer JVM versions causing the RGB color channels to be mixed up. // This is a bug Oracle claims to have fixed but they actually haven't. // http://bugs.java.com/view_bug.do?bug_id=4712797 @@ -1224,7 +1234,7 @@ else if (width == 16) ico.getDescriptors().add(bmd); } } - + public static void OpenDesktopEditor(File file) { try diff --git a/org/lateralgm/main/icons.properties b/org/lateralgm/main/icons.properties index 09f4a7191..278d11f67 100644 --- a/org/lateralgm/main/icons.properties +++ b/org/lateralgm/main/icons.properties @@ -267,14 +267,8 @@ ActionList.DELETE=actions/delete.png ConstantsFrame.IMPORT=actions/open.png ConstantsFrame.EXPORT=actions/save.png -IncludeFrame.IMPORT=actions/open.png -IncludeFrame.EXPORT=actions/save.png -IncludeFrame.OVERWRITE=Overwrite existing file -IncludeFrame.FREEDATA=Free data after export -IncludeFrame.REMOVEATGAMEEND=Remove at game end -IncludeFrame.EXPORTTO=Export to Targets -IncludeFrame.SIZE=Size -IncludeFrame.ORIGINAL=Original +IncludeFrame.LOAD_DATA=actions/open.png +IncludeFrame.SAVE_DATA=actions/save.png EnigmaPlugin.STOP=actions/stop.png EnigmaPlugin.EXECUTE=actions/execute.png diff --git a/org/lateralgm/messages/messages.properties b/org/lateralgm/messages/messages.properties index db0e1701b..e008cca2b 100644 --- a/org/lateralgm/messages/messages.properties +++ b/org/lateralgm/messages/messages.properties @@ -636,6 +636,27 @@ RevertableMDIFrame.KEEPCHANGES_TITLE=Unsaved Changes ## Include Frame IncludeFrame.TITLE=Included File +IncludeFrame.NAME=Name: +IncludeFrame.SAVE=Save +IncludeFrame.LOAD_DATA=Load Data +IncludeFrame.SAVE_DATA=Save Data +IncludeFrame.LOAD_TIP=Load data from file +IncludeFrame.SAVE_TIP=Save data to file +IncludeFrame.FILE_NAME=File Name: +IncludeFrame.SIZE=Size: {0} bytes +IncludeFrame.ORIGINAL_FILE=Original File: {0} +IncludeFrame.COPIES_TO=Copies to: +IncludeFrame.PLATFORMS= +IncludeFrame.OVERWRITE_EXISTING=Overwrite existing files +IncludeFrame.REMOVE_FILES_AT_END=Remove files at end of game +IncludeFrame.STORE_EDITABLE=Store in the project editable +IncludeFrame.FREE_MEMORY=Free memory after export + +IncludeFrame.EXPORT_ACTION=Export Action +IncludeFrame.DONT_EXPORT=Don't export by default +IncludeFrame.TEMP_DIRECTORY=Temporary directory +IncludeFrame.SAME_FOLDER=Same folder as executable +IncludeFrame.CUSTOM_FOLDER=Custom folder: ## Extension Frame ExtensionFrame.TITLE=Extension @@ -850,19 +871,6 @@ GameSettingFrame.PRODUCT=Product: GameSettingFrame.COPYRIGHT=Copyright: GameSettingFrame.DESCRIPTION=Description: -#Include tab -GameSettingFrame.TAB_INCLUDE=Include -GameSettingFrame.HINT_INCLUDE=Configure Includes -GameSettingFrame.FILES_TO_INCLUDE=Files to include in the Executable -GameSettingFrame.ADD_INCLUDE=Add -GameSettingFrame.DELETE_INCLUDE=Delete -GameSettingFrame.CLEAR_INCLUDES=Clear -GameSettingFrame.EXPORT_TO=Folder to export to -GameSettingFrame.SAME_FOLDER=Same folder as executable -GameSettingFrame.TEMP_DIRECTORY=Temporary directory -GameSettingFrame.OVERWRITE_EXISTING=Overwrite existing files -GameSettingFrame.REMOVE_FILES_AT_END=Remove files at end of game - #Errors tab GameSettingFrame.TAB_ERRORS=Errors GameSettingFrame.HINT_ERRORS=Configure Error handling @@ -1493,16 +1501,6 @@ Targets.WINDOWSYYC=Windows (YYC) Targets.ANDROIDYYC=Android (YYC) Targets.IOSYYC=iOS (YYC) -#Included File Frame -IncludeFrame.NAME=Name -IncludeFrame.IMPORT=Import data from file -IncludeFrame.EXPORT=Export data to file -IncludeFrame.SIZE=Size: -IncludeFrame.BYTES={0} bytes -IncludeFrame.ORIGINAL_FILE=Original File: -IncludeFrame.COPIES_TO=Copies to: -IncludeFrame.PLATFORMS= - ##JoshEdit JoshText.TYPE_TXT=Plain Text File JoshText.UNDO=Undo diff --git a/org/lateralgm/resources/Include.java b/org/lateralgm/resources/Include.java index 031346b8a..c74e194c6 100644 --- a/org/lateralgm/resources/Include.java +++ b/org/lateralgm/resources/Include.java @@ -16,61 +16,46 @@ public class Include extends InstantiableResource { public byte[] data = new byte[0]; + public enum ExportAction + { + DONT_EXPORT,TEMP_DIRECTORY,SAME_FOLDER,CUSTOM_FOLDER + } + public enum PInclude { FILENAME,FILEPATH,ORIGINAL,SIZE,EXPORTACTION,EXPORTFOLDER,OVERWRITE,FREEMEMORY,REMOVEATGAMEEND, STORE } - private static final EnumMap DEF = PropertyMap.makeDefaultMap(PInclude.class,"", - "",true,0,2,"",false,true,true,false); + private static final EnumMap DEFS = PropertyMap.makeDefaultMap(PInclude.class,"", //$NON-NLS-1$ + "",true,0,ExportAction.SAME_FOLDER,"",false,true,true,false); //$NON-NLS-1$//$NON-NLS-2$ - public String filename = ""; //$NON-NLS-1$ - public String filepath = ""; //$NON-NLS-1$ - public boolean isOriginal; - public int size = 0; - public int export = 2; - public String exportFolder = ""; //$NON-NLS-1$ - public boolean overwriteExisting = false; - public boolean freeMemAfterExport = true; - public boolean removeAtGameEnd = true; - - public Include copy() + public Include() { - Include inc = new Include(); - inc.filename = filename; - inc.filepath = filepath; - inc.isOriginal = isOriginal; - inc.size = size; - inc.data = data; - inc.export = export; - inc.exportFolder = exportFolder; - inc.overwriteExisting = overwriteExisting; - inc.freeMemAfterExport = freeMemAfterExport; - inc.removeAtGameEnd = removeAtGameEnd; - return inc; + this(null); } - public String toString() + public Include(ResourceReference ref) { - return filepath; + super(ref); } @Override public Include makeInstance(ResourceReference ref) { - return new Include(); + return new Include(ref); } @Override protected PropertyMap makePropertyMap() { - return new PropertyMap(PInclude.class,this,DEF); + return new PropertyMap(PInclude.class,this,DEFS); } @Override protected void postCopy(Include dest) - { //Nothing else to copy - + { + super.postCopy(dest); + dest.data = data.clone(); } } diff --git a/org/lateralgm/subframes/IncludeFrame.java b/org/lateralgm/subframes/IncludeFrame.java index 96d8839df..df6208e93 100644 --- a/org/lateralgm/subframes/IncludeFrame.java +++ b/org/lateralgm/subframes/IncludeFrame.java @@ -4,7 +4,7 @@ * * @section License * -* Copyright (C) 2014 Robert B. Colton +* Copyright (C) 2014,2019 Robert B. Colton * This file is a part of the LateralGM IDE. * * This program is free software: you can redistribute it and/or modify @@ -23,63 +23,84 @@ package org.lateralgm.subframes; +import static java.lang.Integer.MAX_VALUE; +import static javax.swing.GroupLayout.DEFAULT_SIZE; +import static javax.swing.GroupLayout.PREFERRED_SIZE; + import java.awt.BorderLayout; import java.awt.event.ActionEvent; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import javax.swing.BorderFactory; +import javax.swing.BoxLayout; +import javax.swing.ButtonGroup; import javax.swing.GroupLayout; +import javax.swing.GroupLayout.Alignment; import javax.swing.JButton; +import javax.swing.JCheckBox; import javax.swing.JFileChooser; import javax.swing.JLabel; import javax.swing.JPanel; -import javax.swing.JToolBar; +import javax.swing.JRadioButton; +import javax.swing.JTextField; import org.lateralgm.components.CustomFileChooser; -import org.lateralgm.components.impl.DocumentUndoManager; import org.lateralgm.components.impl.ResNode; import org.lateralgm.main.LGM; import org.lateralgm.messages.Messages; import org.lateralgm.resources.ExtensionPackages; import org.lateralgm.resources.Include; +import org.lateralgm.resources.Include.ExportAction; +import org.lateralgm.resources.Include.PInclude; public class IncludeFrame extends InstantiableResourceFrame { - private static final long serialVersionUID = 1L; - protected DocumentUndoManager undoManager = new DocumentUndoManager(); - private CustomFileChooser fc = new CustomFileChooser("/org/lateralgm","LAST_FILE_DIR"); - private JButton exportBut; - private JButton importBut; - - public IncludeFrame(Include res) - { - this(res,null); - } + /** + * NOTE: Default UID generated, changed if necessary. + */ + private static final long serialVersionUID = -2341007814035473389L; + private CustomFileChooser fc = new CustomFileChooser("/org/lateralgm","LAST_INCLUDE_FILE_DIR"); //$NON-NLS-1$//$NON-NLS-2$ + private JButton saveDataBut, loadDataBut; + private JLabel originalNameLabel, sizeLabel; public IncludeFrame(Include r, ResNode node) { - //,Messages.getString("IncludeFrame.TITLE"),true super(r,node); setDefaultCloseOperation(HIDE_ON_CLOSE); - setSize(300,350); this.setLayout(new BorderLayout()); - JToolBar toolbar = new JToolBar(); - toolbar.setFloatable(false); - toolbar.add(save); - toolbar.addSeparator(); - exportBut = new JButton(LGM.getIconForKey("IncludeFrame.EXPORT")); //$NON-NLS-1$ - exportBut.setToolTipText(Messages.getString("IncludeFrame.EXPORT")); - exportBut.addActionListener(this); - toolbar.add(exportBut); - importBut = new JButton(LGM.getIconForKey("IncludeFrame.IMPORT")); //$NON-NLS-1$ - importBut.setToolTipText(Messages.getString("IncludeFrame.IMPORT")); - importBut.addActionListener(this); - toolbar.add(importBut); - toolbar.addSeparator(); - toolbar.add(new JLabel("Name:")); - name.setColumns(13); - name.setMaximumSize(name.getPreferredSize()); - toolbar.add(name); + saveDataBut = new JButton(LGM.getIconForKey("IncludeFrame.SAVE_DATA")); //$NON-NLS-1$ + saveDataBut.setText(Messages.getString("IncludeFrame.SAVE_DATA")); //$NON-NLS-1$ + saveDataBut.setToolTipText(Messages.getString("IncludeFrame.SAVE_TIP")); //$NON-NLS-1$ + saveDataBut.addActionListener(this); + + loadDataBut = new JButton(LGM.getIconForKey("IncludeFrame.LOAD_DATA")); //$NON-NLS-1$ + loadDataBut.setText(Messages.getString("IncludeFrame.LOAD_DATA")); //$NON-NLS-1$ + loadDataBut.setToolTipText(Messages.getString("IncludeFrame.LOAD_TIP")); //$NON-NLS-1$ + loadDataBut.addActionListener(this); + + JLabel nameLabel = new JLabel(Messages.getString("IncludeFrame.NAME")); //$NON-NLS-1$ + originalNameLabel = new JLabel(); + sizeLabel = new JLabel(); + updateStatusLabels(); + + JLabel fileNameLabel = new JLabel(Messages.getString("IncludeFrame.FILE_NAME")); //$NON-NLS-1$ + JTextField fileNameField = new JTextField(); + plf.make(fileNameField.getDocument(),PInclude.FILENAME); + + JCheckBox store, removeEnd, freeMemory, overwrite; + store = new JCheckBox(Messages.getString("IncludeFrame.STORE_EDITABLE")); //$NON-NLS-1$ + plf.make(store,PInclude.STORE); + removeEnd = new JCheckBox(Messages.getString("IncludeFrame.REMOVE_FILES_AT_END")); //$NON-NLS-1$ + plf.make(removeEnd,PInclude.REMOVEATGAMEEND); + freeMemory = new JCheckBox(Messages.getString("IncludeFrame.FREE_MEMORY")); //$NON-NLS-1$ + plf.make(freeMemory,PInclude.FREEMEMORY); + overwrite = new JCheckBox(Messages.getString("IncludeFrame.OVERWRITE_EXISTING")); //$NON-NLS-1$ + plf.make(overwrite,PInclude.OVERWRITE); JPanel p = new JPanel(); GroupLayout gl = new GroupLayout(p); @@ -87,9 +108,100 @@ public IncludeFrame(Include r, ResNode node) gl.setAutoCreateGaps(true); gl.setAutoCreateContainerGaps(true); + JPanel exportActionPanel = new JPanel(); + exportActionPanel.setLayout(new BoxLayout(exportActionPanel,BoxLayout.Y_AXIS)); + exportActionPanel.setBorder(BorderFactory.createTitledBorder( + Messages.getString("IncludeFrame.EXPORT_ACTION"))); //$NON-NLS-1$ + + final JTextField customFolderField = new JTextField(); + customFolderField.setEnabled(r.get(PInclude.EXPORTACTION) == ExportAction.CUSTOM_FOLDER); + plf.make(customFolderField.getDocument(),PInclude.EXPORTFOLDER); + + JRadioButton dontExport, tempDirectory, sameFolder; + final JRadioButton customFolder; + dontExport = new JRadioButton(Messages.getString("IncludeFrame.DONT_EXPORT")); //$NON-NLS-1$ + tempDirectory = new JRadioButton(Messages.getString("IncludeFrame.TEMP_DIRECTORY")); //$NON-NLS-1$ + sameFolder = new JRadioButton(Messages.getString("IncludeFrame.SAME_FOLDER")); //$NON-NLS-1$ + customFolder = new JRadioButton(Messages.getString("IncludeFrame.CUSTOM_FOLDER")); //$NON-NLS-1$ + customFolder.addItemListener(new ItemListener() { + @Override + public void itemStateChanged(ItemEvent e) + { + customFolderField.setEnabled(e.getStateChange() == ItemEvent.SELECTED); + } + }); + + exportActionPanel.add(dontExport); + exportActionPanel.add(tempDirectory); + exportActionPanel.add(sameFolder); + exportActionPanel.add(customFolder); + exportActionPanel.add(customFolderField); + + ButtonGroup bg = new ButtonGroup(); + bg.add(dontExport); + bg.add(tempDirectory); + bg.add(sameFolder); + bg.add(customFolder); + plf.make(bg,PInclude.EXPORTACTION,Include.ExportAction.class); + + save.setText(Messages.getString("IncludeFrame.SAVE")); //$NON-NLS-1$ + + gl.setHorizontalGroup(gl.createParallelGroup() + /**/.addGroup(gl.createSequentialGroup() + /* */.addComponent(save,DEFAULT_SIZE,PREFERRED_SIZE,MAX_VALUE) + /* */.addComponent(loadDataBut,DEFAULT_SIZE,PREFERRED_SIZE,MAX_VALUE) + /* */.addComponent(saveDataBut,DEFAULT_SIZE,PREFERRED_SIZE,MAX_VALUE)) + /**/.addGroup(gl.createSequentialGroup() + /* */.addComponent(nameLabel) + /* */.addComponent(name,DEFAULT_SIZE,PREFERRED_SIZE,MAX_VALUE)) + // we want these two labels to have an ellipsis if too long + // and the tooltip can be used to see the full name/value + /**/.addComponent(originalNameLabel,0,0,DEFAULT_SIZE) + /**/.addComponent(sizeLabel,0,0,DEFAULT_SIZE) + /**/.addGroup(gl.createSequentialGroup() + /* */.addComponent(fileNameLabel) + /* */.addComponent(fileNameField,DEFAULT_SIZE,PREFERRED_SIZE,MAX_VALUE)) + /**/.addComponent(store) + /**/.addComponent(removeEnd) + /**/.addComponent(freeMemory) + /**/.addComponent(overwrite) + /**/.addComponent(exportActionPanel,DEFAULT_SIZE,PREFERRED_SIZE,MAX_VALUE)); + + gl.setVerticalGroup(gl.createSequentialGroup() + /**/.addGroup(gl.createParallelGroup(Alignment.BASELINE) + /* */.addComponent(save) + /* */.addComponent(loadDataBut) + /* */.addComponent(saveDataBut)) + /**/.addGroup(gl.createParallelGroup(Alignment.BASELINE) + /* */.addComponent(nameLabel) + /* */.addComponent(name)) + /**/.addComponent(originalNameLabel) + /**/.addComponent(sizeLabel) + /**/.addGroup(gl.createParallelGroup(Alignment.BASELINE) + /* */.addComponent(fileNameLabel) + /* */.addComponent(fileNameField)) + /**/.addComponent(store) + /**/.addComponent(removeEnd) + /**/.addComponent(freeMemory) + /**/.addComponent(overwrite) + /**/.addComponent(exportActionPanel,DEFAULT_SIZE,DEFAULT_SIZE,PREFERRED_SIZE)); + this.add(p,BorderLayout.CENTER); - this.add(toolbar,BorderLayout.NORTH); + this.pack(); + this.setMinimumSize(this.getSize()); + } + private void updateStatusLabels() + { + saveDataBut.setEnabled(res.data != null && res.data.length > 0); + + String filePathText = Messages.format("IncludeFrame.ORIGINAL_FILE",res.get(PInclude.FILEPATH)); //$NON-NLS-1$ + originalNameLabel.setText(filePathText); + String sizeText = Messages.format("IncludeFrame.SIZE",res.get(PInclude.SIZE)); //$NON-NLS-1$ + sizeLabel.setText(sizeText); + // set the tooltip text too so the full path can be seen without resizing the editor + originalNameLabel.setToolTipText(filePathText); + sizeLabel.setToolTipText(sizeText); } public Object getUserObject() @@ -107,17 +219,38 @@ public void actionPerformed(ActionEvent ev) { super.actionPerformed(ev); Object source = ev.getSource(); - if (source == importBut) + if (source == loadDataBut) { if (fc.showOpenDialog(LGM.frame) != JFileChooser.APPROVE_OPTION) return; File f = fc.getSelectedFile(); if (!f.exists()) return; - //TODO: can't handle .ext - res.setName(f.getName()); + res.put(PInclude.FILENAME,f.getName()); + res.put(PInclude.FILEPATH,f.getAbsolutePath()); + try + { + res.data = Files.readAllBytes(f.toPath()); + } + catch (IOException e) + { + LGM.showDefaultExceptionHandler(e); + } + res.put(PInclude.SIZE,res.data.length); + updateStatusLabels(); return; } - if (source == exportBut) + if (source == saveDataBut) { + fc.setSelectedFile(new File(res.get(PInclude.FILENAME).toString())); + if (fc.showSaveDialog(LGM.frame) != JFileChooser.APPROVE_OPTION) return; + File f = fc.getSelectedFile(); + try + { + Files.write(f.toPath(),res.data); + } + catch (IOException e) + { + LGM.showDefaultExceptionHandler(e); + } return; } }