diff --git a/src/main/java/tm/TMFileResources.java b/src/main/java/tm/TMFileResources.java index 8fb80b5..42b5499 100644 --- a/src/main/java/tm/TMFileResources.java +++ b/src/main/java/tm/TMFileResources.java @@ -43,6 +43,7 @@ public class TMFileResources { private FolderNode bookmarkRoot; private FolderNode paletteRoot; + private ViewSettings viewSettings = new ViewSettings(); private FileImage fileImage; private TMUI ui; @@ -95,6 +96,8 @@ public TMFileResources(File file, FileImage fileImage, TMUI ui) Element root = doc.getDocumentElement(); bookmarkRoot = parseBookmarks(root); paletteRoot = parsePalettes(root); + ViewSettings parsed = ViewSettings.parse(root); + if (parsed != null) viewSettings = parsed; fileImage.setResources(this); } @@ -272,6 +275,17 @@ public FolderNode getPalettesRoot() { return paletteRoot; } + /** + * + * Gets the per-file view settings (codec, mode, zoom, canvas size, + * last externally-imported palette). + * + **/ + + public ViewSettings getViewSettings() { + return viewSettings; + } + /** * * Returns XML representation of resources. @@ -281,11 +295,11 @@ public FolderNode getPalettesRoot() { public String toXML() { StringBuffer sb = new StringBuffer(); sb.append("\n"); - sb.append("\n"); sb.append("\n"); sb.append(bookmarksToXML()); sb.append(palettesToXML()); + sb.append(viewSettings.toXML()); sb.append("\n"); return sb.toString(); diff --git a/src/main/java/tm/ViewSettings.java b/src/main/java/tm/ViewSettings.java new file mode 100644 index 0000000..dd84a96 --- /dev/null +++ b/src/main/java/tm/ViewSettings.java @@ -0,0 +1,167 @@ +/* +* +* Per-file view settings (codec, mode, zoom, canvas size, +* last externally-imported palette). Persisted alongside +* bookmarks/palettes in the resources XML, applied on open, +* captured on close. +* +*/ + +package tm; + +import tm.colorcodecs.ColorCodec; +import tm.tilecodecs.TileCodec; +import tm.ui.TMUI; +import tm.ui.TMView; +import org.w3c.dom.Element; + +import java.io.File; +import java.io.RandomAccessFile; + +public class ViewSettings { + + private String codecID; + private Integer mode; + private Double zoom; + private Integer cols; + private Integer rows; + private Integer offset; + + private boolean hasExternalPalette; + private String palettePath; + private int paletteOffset; + private int paletteSize; + private String paletteCodecID; + private int paletteEndianness; + + public ViewSettings() {} + + public static ViewSettings parse(Element root) { + Element e = (Element) root.getElementsByTagName("viewsettings").item(0); + if (e == null) return null; + + ViewSettings vs = new ViewSettings(); + if (e.hasAttribute("codec")) { + vs.codecID = e.getAttribute("codec"); + } + if (e.hasAttribute("mode")) { + vs.mode = e.getAttribute("mode").equals("2D") ? TileCodec.MODE_2D : TileCodec.MODE_1D; + } + if (e.hasAttribute("zoom")) { + try { vs.zoom = Double.parseDouble(e.getAttribute("zoom")); } + catch (NumberFormatException ignored) {} + } + if (e.hasAttribute("cols")) { + try { vs.cols = Integer.parseInt(e.getAttribute("cols")); } + catch (NumberFormatException ignored) {} + } + if (e.hasAttribute("rows")) { + try { vs.rows = Integer.parseInt(e.getAttribute("rows")); } + catch (NumberFormatException ignored) {} + } + if (e.hasAttribute("offset")) { + try { vs.offset = Integer.parseInt(e.getAttribute("offset")); } + catch (NumberFormatException ignored) {} + } + + Element ep = (Element) e.getElementsByTagName("externalpalette").item(0); + if (ep != null && ep.hasAttribute("path") && ep.hasAttribute("offset") + && ep.hasAttribute("size") && ep.hasAttribute("codec")) { + try { + vs.palettePath = ep.getAttribute("path"); + vs.paletteOffset = Integer.parseInt(ep.getAttribute("offset")); + vs.paletteSize = Integer.parseInt(ep.getAttribute("size")); + vs.paletteCodecID = ep.getAttribute("codec"); + vs.paletteEndianness = ep.getAttribute("endianness").equals("big") + ? ColorCodec.BIG_ENDIAN : ColorCodec.LITTLE_ENDIAN; + vs.hasExternalPalette = true; + } catch (NumberFormatException ignored) {} + } + + return vs; + } + + public String toXML() { + StringBuilder sb = new StringBuilder(); + sb.append(" \n"); + sb.append(" \n"); + sb.append(" \n"); + } else { + sb.append("/>\n"); + } + return sb.toString(); + } + + public void captureFrom(TMView view) { + TileCodec tc = view.getTileCodec(); + if (tc != null) codecID = tc.getID(); + mode = view.getMode(); + zoom = view.getScale(); + cols = view.getCols(); + rows = view.getRows(); + offset = view.getOffset(); + } + + public void applyTo(TMView view, TMUI ui) { + if (codecID != null) { + TileCodec tc = ui.getTileCodecByID(codecID); + if (tc != null) view.setTileCodec(tc); + } + if (mode != null) view.setMode(mode); + if (cols != null && rows != null) view.setGridSize(cols, rows); + if (zoom != null) view.setScale(zoom); + if (hasExternalPalette) applyExternalPalette(view, ui); + // offset must be applied last: setGridSize/setScale recompute maxOffset + if (offset != null) view.setAbsoluteOffset(offset); + } + + private void applyExternalPalette(TMView view, TMUI ui) { + File file = new File(palettePath); + if (!file.exists()) return; + ColorCodec codec = ui.getColorCodecByID(paletteCodecID); + if (codec == null) return; + + byte[] data = new byte[paletteSize * codec.getBytesPerPixel()]; + try (RandomAccessFile raf = new RandomAccessFile(file, "r")) { + raf.seek(paletteOffset); + raf.read(data); + } catch (Exception ex) { + return; + } + + TMPalette palette = new TMPalette("ID", data, 0, paletteSize, codec, paletteEndianness, true, false); + view.setPalette(palette); + } + + public void setExternalPalette(String path, int offset, int size, String codecID, int endianness) { + this.palettePath = path; + this.paletteOffset = offset; + this.paletteSize = size; + this.paletteCodecID = codecID; + this.paletteEndianness = endianness; + this.hasExternalPalette = true; + } + + private static String escape(String s) { + return s.replace("&", "&") + .replace("\"", """) + .replace("<", "<") + .replace(">", ">"); + } +} diff --git a/src/main/java/tm/ui/TMUI.java b/src/main/java/tm/ui/TMUI.java index f49998f..dc1b22f 100644 --- a/src/main/java/tm/ui/TMUI.java +++ b/src/main/java/tm/ui/TMUI.java @@ -1901,6 +1901,7 @@ public void doCloseCommand() { // check if it's the last view if (img.getViews().length == 1) { + captureViewSettings(view); saveResources(img); // TODO // check if saving required/desired if (img.isModified()) { @@ -1956,6 +1957,23 @@ public void doCloseCommand() { * **/ + /** + * + * Captures the current view's codec/mode/zoom/canvas size into the + * fileimage's resources, so they get persisted by saveResources(). + * The external-palette block is updated separately, when the user + * runs Palette > Import From > Another File. + * + **/ + + public void captureViewSettings(TMView view) { + if (view == null) return; + FileImage img = view.getFileImage(); + if (img == null || img.getResources() == null) return; + ViewSettings vs = img.getResources().getViewSettings(); + if (vs != null) vs.captureFrom(view); + } + public void saveResources(FileImage img) { // TODO: should only save if # bookmarks | # of palettes > 0? File resourceFile = TMFileResources.getResourceFileFor(img.getFile()); @@ -2025,6 +2043,7 @@ public void doCloseAllCommand() { TMView view = (TMView) frames[i]; FileImage img = view.getFileImage(); + captureViewSettings(view); saveResources(img); // TODO addToRecentFiles(new File(img.getFile().getAbsolutePath())); @@ -3259,6 +3278,15 @@ public void doImportExternalPaletteCommand() { // set the new palette view.setPalette(palette); + + // record the import so it can be reapplied when the file is reopened + FileImage img = view.getFileImage(); + if (img != null && img.getResources() != null + && img.getResources().getViewSettings() != null) { + img.getResources().getViewSettings().setExternalPalette( + file.getAbsolutePath(), offset, size, pf.getCodecID(), endianness); + } + refreshPalettePane(); refreshPalettesMenu(); } @@ -3783,6 +3811,7 @@ public void addViewToDesktop(TMView view) { desktop.add(view); try { view.setSelected(true); + view.setMaximum(true); } catch (java.beans.PropertyVetoException x) { x.printStackTrace(); } @@ -3905,7 +3934,7 @@ private void initFileOpenChooser() { TMFileFilter supportedFilter = new TMFileFilter(extlist, xlate("All_Supported_Formats")); fileOpenChooser.addChoosableFileFilter(supportedFilter); fileOpenChooser.addChoosableFileFilter(allFilter); - fileOpenChooser.setFileFilter(supportedFilter); + fileOpenChooser.setFileFilter(allFilter); } /** @@ -4447,6 +4476,11 @@ public void openFile(File file) { "Tile Molester", JOptionPane.ERROR_MESSAGE); } + // if loading failed (no resources attached), fall back to defaults + // so saving on close still produces a usable XML next time. + if (img.getResources() == null) { + new TMFileResources(img, this); + } } else { // create default resources new TMFileResources(img, this); @@ -4461,7 +4495,14 @@ public void openFile(File file) { TMPalette pal = new TMPalette("PAL000", TMPalette.defaultPalette, getColorCodecByID("CF01"), ColorCodec.LITTLE_ENDIAN, true); - addViewToDesktop(createView(img, tc, pal, mode)); + TMView view = createView(img, tc, pal, mode); + addViewToDesktop(view); + + // Apply persisted per-file view settings (overrides file-filter defaults). + if (img.getResources() != null && img.getResources().getViewSettings() != null) { + img.getResources().getViewSettings().applyTo(view, this); + viewSelected(view); + } Vector recentFiles = TileMolester.settings.getRecentFiles(); // Remove file from recentFiles, if it's there diff --git a/src/main/java/tm/ui/TMView.java b/src/main/java/tm/ui/TMView.java index 59ca5c9..1ab85ba 100644 --- a/src/main/java/tm/ui/TMView.java +++ b/src/main/java/tm/ui/TMView.java @@ -110,6 +110,7 @@ public void internalFrameClosing(InternalFrameEvent e) { addComponentListener(new ComponentAdapter() { public void componentResized(ComponentEvent e) { + if (editorCanvas == null) return; slider.setSize(slider.getWidth(), editorCanvas.getHeight()); // slider.setSize(slider.getWidth(), // getHeight()-((BasicInternalFrameUI)getUI()).getNorthPane().getHeight()); diff --git a/src/main/java/tm/utils/XMLParser.java b/src/main/java/tm/utils/XMLParser.java index 4d52cc4..af686d6 100644 --- a/src/main/java/tm/utils/XMLParser.java +++ b/src/main/java/tm/utils/XMLParser.java @@ -45,9 +45,16 @@ public static Document parse(File file) throws SAXException, SAXParseException, ParserConfigurationException, IOException { Document document = null; DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); - factory.setValidating(true); + factory.setValidating(false); + try { + factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); + } catch (Exception ignored) {} try { DocumentBuilder builder = factory.newDocumentBuilder(); + // suppress any external entity (e.g. DTD) lookup — the resources XML + // references a relative DTD path that doesn't resolve on every OS. + builder.setEntityResolver((publicId, systemId) -> + new InputSource(new java.io.StringReader(""))); InputStream inputStream = new FileInputStream(file); InputSource is = new InputSource(inputStream); is.setEncoding("UTF-8");