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");