Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion src/main/java/tm/TMFileResources.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ public class TMFileResources {

private FolderNode bookmarkRoot;
private FolderNode paletteRoot;
private ViewSettings viewSettings = new ViewSettings();

private FileImage fileImage;
private TMUI ui;
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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.
Expand All @@ -281,11 +295,11 @@ public FolderNode getPalettesRoot() {
public String toXML() {
StringBuffer sb = new StringBuffer();
sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
sb.append("<!DOCTYPE tmres SYSTEM \"resources\\tmres.dtd\">\n");
sb.append("<tmres>\n");

sb.append(bookmarksToXML());
sb.append(palettesToXML());
sb.append(viewSettings.toXML());

sb.append("</tmres>\n");
return sb.toString();
Expand Down
167 changes: 167 additions & 0 deletions src/main/java/tm/ViewSettings.java
Original file line number Diff line number Diff line change
@@ -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(" <viewsettings");
if (codecID != null) sb.append(" codec=\"").append(escape(codecID)).append("\"");
if (mode != null) sb.append(" mode=\"").append(mode == TileCodec.MODE_2D ? "2D" : "1D").append("\"");
if (zoom != null) sb.append(" zoom=\"").append(zoom).append("\"");
if (cols != null) sb.append(" cols=\"").append(cols).append("\"");
if (rows != null) sb.append(" rows=\"").append(rows).append("\"");
if (offset != null) sb.append(" offset=\"").append(offset).append("\"");

if (hasExternalPalette) {
sb.append(">\n");
sb.append(" <externalpalette");
sb.append(" path=\"").append(escape(palettePath)).append("\"");
sb.append(" offset=\"").append(paletteOffset).append("\"");
sb.append(" size=\"").append(paletteSize).append("\"");
sb.append(" codec=\"").append(escape(paletteCodecID)).append("\"");
sb.append(" endianness=\"")
.append(paletteEndianness == ColorCodec.BIG_ENDIAN ? "big" : "little")
.append("\"/>\n");
sb.append(" </viewsettings>\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("&", "&amp;")
.replace("\"", "&quot;")
.replace("<", "&lt;")
.replace(">", "&gt;");
}
}
45 changes: 43 additions & 2 deletions src/main/java/tm/ui/TMUI.java
Original file line number Diff line number Diff line change
Expand Up @@ -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()) {
Expand Down Expand Up @@ -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());
Expand Down Expand Up @@ -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()));
Expand Down Expand Up @@ -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();
}
Expand Down Expand Up @@ -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();
}
Expand Down Expand Up @@ -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);
}

/**
Expand Down Expand Up @@ -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);
Expand All @@ -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
Expand Down
1 change: 1 addition & 0 deletions src/main/java/tm/ui/TMView.java
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down
9 changes: 8 additions & 1 deletion src/main/java/tm/utils/XMLParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down