From 2a4cfb06845ae5d8655eec74a58bd1e288f27e0e Mon Sep 17 00:00:00 2001 From: Zukero Date: Wed, 1 Mar 2017 19:02:00 +0100 Subject: [PATCH] Enhanced Tiled integration. Can now reload a map that has changed externaly. Still some rough edges, notably the number of nitification sto skip when saving in ATCS. --- .../model/gamedata/Requirement.java | 3 +- .../model/maps/ContainerArea.java | 1 + .../atcontentstudio/model/maps/KeyArea.java | 1 + .../atcontentstudio/model/maps/MapChange.java | 1 + .../model/maps/ScriptArea.java | 1 + .../atcontentstudio/model/maps/SignArea.java | 1 + .../atcontentstudio/model/maps/SpawnArea.java | 1 + .../atcontentstudio/model/maps/TMXMap.java | 78 +++++++++++++++++++ .../atcontentstudio/model/maps/TMXMapSet.java | 43 ++++++++++ .../atcontentstudio/ui/map/TMXMapEditor.java | 53 +++++++++++-- 10 files changed, 177 insertions(+), 6 deletions(-) diff --git a/src/com/gpl/rpg/atcontentstudio/model/gamedata/Requirement.java b/src/com/gpl/rpg/atcontentstudio/model/gamedata/Requirement.java index fdb8fe2..bb1fe8c 100644 --- a/src/com/gpl/rpg/atcontentstudio/model/gamedata/Requirement.java +++ b/src/com/gpl/rpg/atcontentstudio/model/gamedata/Requirement.java @@ -145,8 +145,9 @@ public class Requirement extends JSONElement { @Override public void elementChanged(GameDataElement oldOne, GameDataElement newOne) { if (this.required_obj == oldOne) { + oldOne.removeBacklink((GameDataElement) this.parent); this.required_obj = newOne; - if (newOne != null) newOne.addBacklink(this); + if (newOne != null) newOne.addBacklink((GameDataElement) this.parent); } } @Override diff --git a/src/com/gpl/rpg/atcontentstudio/model/maps/ContainerArea.java b/src/com/gpl/rpg/atcontentstudio/model/maps/ContainerArea.java index 5876e7d..175ef90 100644 --- a/src/com/gpl/rpg/atcontentstudio/model/maps/ContainerArea.java +++ b/src/com/gpl/rpg/atcontentstudio/model/maps/ContainerArea.java @@ -29,6 +29,7 @@ public class ContainerArea extends MapObject { @Override public void elementChanged(GameDataElement oldOne, GameDataElement newOne) { if (oldOne == droplist) { + oldOne.removeBacklink(parentMap); droplist = (Droplist) newOne; newOne.addBacklink(parentMap); } diff --git a/src/com/gpl/rpg/atcontentstudio/model/maps/KeyArea.java b/src/com/gpl/rpg/atcontentstudio/model/maps/KeyArea.java index 4af78fb..feeaf96 100644 --- a/src/com/gpl/rpg/atcontentstudio/model/maps/KeyArea.java +++ b/src/com/gpl/rpg/atcontentstudio/model/maps/KeyArea.java @@ -60,6 +60,7 @@ public class KeyArea extends MapObject { @Override public void elementChanged(GameDataElement oldOne, GameDataElement newOne) { if (oldOne == dialogue) { + oldOne.removeBacklink(parentMap); dialogue = (Dialogue) newOne; newOne.addBacklink(parentMap); } diff --git a/src/com/gpl/rpg/atcontentstudio/model/maps/MapChange.java b/src/com/gpl/rpg/atcontentstudio/model/maps/MapChange.java index 5dd672f..1f77d5d 100644 --- a/src/com/gpl/rpg/atcontentstudio/model/maps/MapChange.java +++ b/src/com/gpl/rpg/atcontentstudio/model/maps/MapChange.java @@ -37,6 +37,7 @@ public class MapChange extends MapObject { @Override public void elementChanged(GameDataElement oldOne, GameDataElement newOne) { if (oldOne == map) { + oldOne.removeBacklink(parentMap); map = (TMXMap) newOne; newOne.addBacklink(parentMap); } diff --git a/src/com/gpl/rpg/atcontentstudio/model/maps/ScriptArea.java b/src/com/gpl/rpg/atcontentstudio/model/maps/ScriptArea.java index 14530d7..34e904d 100644 --- a/src/com/gpl/rpg/atcontentstudio/model/maps/ScriptArea.java +++ b/src/com/gpl/rpg/atcontentstudio/model/maps/ScriptArea.java @@ -42,6 +42,7 @@ public class ScriptArea extends MapObject { @Override public void elementChanged(GameDataElement oldOne, GameDataElement newOne) { if (oldOne == dialogue) { + oldOne.removeBacklink(parentMap); dialogue = (Dialogue) newOne; newOne.addBacklink(parentMap); } diff --git a/src/com/gpl/rpg/atcontentstudio/model/maps/SignArea.java b/src/com/gpl/rpg/atcontentstudio/model/maps/SignArea.java index 4a9f201..d97d2e6 100644 --- a/src/com/gpl/rpg/atcontentstudio/model/maps/SignArea.java +++ b/src/com/gpl/rpg/atcontentstudio/model/maps/SignArea.java @@ -31,6 +31,7 @@ public class SignArea extends MapObject { @Override public void elementChanged(GameDataElement oldOne, GameDataElement newOne) { if (oldOne == dialogue) { + oldOne.removeBacklink(parentMap); dialogue = (Dialogue) newOne; newOne.addBacklink(parentMap); } diff --git a/src/com/gpl/rpg/atcontentstudio/model/maps/SpawnArea.java b/src/com/gpl/rpg/atcontentstudio/model/maps/SpawnArea.java index e0f8e33..0a31904 100644 --- a/src/com/gpl/rpg/atcontentstudio/model/maps/SpawnArea.java +++ b/src/com/gpl/rpg/atcontentstudio/model/maps/SpawnArea.java @@ -64,6 +64,7 @@ public class SpawnArea extends MapObject { } } if (replacedIndex >= 0) { + oldOne.removeBacklink(parentMap); spawnGroup.set(replacedIndex, (NPC) newOne); newOne.addBacklink(parentMap); } diff --git a/src/com/gpl/rpg/atcontentstudio/model/maps/TMXMap.java b/src/com/gpl/rpg/atcontentstudio/model/maps/TMXMap.java index 8aa5efb..bd38d86 100644 --- a/src/com/gpl/rpg/atcontentstudio/model/maps/TMXMap.java +++ b/src/com/gpl/rpg/atcontentstudio/model/maps/TMXMap.java @@ -26,6 +26,7 @@ import com.gpl.rpg.atcontentstudio.model.Project; import com.gpl.rpg.atcontentstudio.model.ProjectTreeNode; import com.gpl.rpg.atcontentstudio.model.SaveEvent; import com.gpl.rpg.atcontentstudio.model.gamedata.GameDataSet; +import com.gpl.rpg.atcontentstudio.model.gamedata.NPC; import com.gpl.rpg.atcontentstudio.model.sprites.Spritesheet; import com.gpl.rpg.atcontentstudio.ui.DefaultIcons; @@ -61,6 +62,8 @@ public class TMXMap extends GameDataElement { public ColorFilter colorFilter = null; public boolean writable = false; + public boolean changedOnDisk = false; + public int dismissNextChangeNotif = 0; public TMXMap(TMXMapSet parent, File f) { this.parent = parent; @@ -270,10 +273,13 @@ public class TMXMap extends GameDataElement { public void save() { if (writable) { try { + //TODO: check in fileutils, to test the workspace's filesystem once at startup, and figure out how many of these can occur, instead of hard-coded '2' + dismissNextChangeNotif += 2; FileWriter w = new FileWriter(tmxFile); w.write(toXml()); w.close(); this.state = State.saved; + changedOnDisk = false; Notification.addSuccess("TMX file "+tmxFile.getAbsolutePath()+" saved."); } catch (IOException e) { Notification.addError("Error while writing TMX file "+tmxFile.getAbsolutePath()+" : "+e.getMessage()); @@ -392,5 +398,77 @@ public class TMXMap extends GameDataElement { ABOVE_LAYER_NAME.equalsIgnoreCase(name) || WALKABLE_LAYER_NAME.equalsIgnoreCase(name); } + + + public void reload() { + tmxMap = null; + for (Spritesheet s : usedSpritesheets) { + s.elementChanged(this, null); + } + usedSpritesheets.clear(); + for (MapObjectGroup g : groups) { + for (MapObject o : g.mapObjects) { + if (o instanceof ContainerArea) { + if (((ContainerArea)o).droplist != null) ((ContainerArea)o).droplist.elementChanged(this, null); + } else if (o instanceof KeyArea) { + if (((KeyArea)o).dialogue != null) ((KeyArea)o).dialogue.elementChanged(this, null); + if (((KeyArea)o).requirement != null && ((KeyArea)o).requirement.required_obj != null) ((KeyArea)o).requirement.required_obj.elementChanged(this, null); + } else if (o instanceof MapChange) { + if (((MapChange)o).map != null) ((MapChange)o).map.elementChanged(this, null); + } else if (o instanceof ReplaceArea) { + if (((ReplaceArea)o).requirement != null && ((ReplaceArea)o).requirement.required_obj != null) ((ReplaceArea)o).requirement.required_obj.elementChanged(this, null); + } else if (o instanceof RestArea) { + } else if (o instanceof ScriptArea) { + if (((ScriptArea)o).dialogue != null) ((ScriptArea)o).dialogue.elementChanged(this, null); + } else if (o instanceof SignArea) { + if (((SignArea)o).dialogue != null) ((SignArea)o).dialogue.elementChanged(this, null); + } else if (o instanceof SpawnArea) { + if (((SpawnArea)o).spawnGroup != null) { + for (NPC n : ((SpawnArea)o).spawnGroup) { + n.elementChanged(this, null); + } + } + } + } + } + groups.clear(); + outside = null; + colorFilter = null; + + state = GameDataElement.State.init; + this.link(); + + changedOnDisk = false; + for (MapChangedOnDiskListener l : new ArrayList(listeners)) { + l.mapReloaded(); + } + } + + public void mapChangedOnDisk() { + if (dismissNextChangeNotif > 0) { + dismissNextChangeNotif--; + } else { + changedOnDisk = true; + for (MapChangedOnDiskListener l : new ArrayList(listeners)) { + l.mapChanged(); + } + } + } + + public interface MapChangedOnDiskListener { + public void mapChanged(); + public void mapReloaded(); + } + + private List listeners = new ArrayList(); + + public void addMapChangedOnDiskListener(MapChangedOnDiskListener l) { + listeners.add(l); + } + + public void removeMapChangedOnDiskListener(MapChangedOnDiskListener l) { + listeners.remove(l); + } + } diff --git a/src/com/gpl/rpg/atcontentstudio/model/maps/TMXMapSet.java b/src/com/gpl/rpg/atcontentstudio/model/maps/TMXMapSet.java index 6ade66a..5598faa 100644 --- a/src/com/gpl/rpg/atcontentstudio/model/maps/TMXMapSet.java +++ b/src/com/gpl/rpg/atcontentstudio/model/maps/TMXMapSet.java @@ -3,9 +3,14 @@ package com.gpl.rpg.atcontentstudio.model.maps; import java.awt.Image; import java.io.File; import java.io.IOException; +import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.nio.file.StandardWatchEventKinds; +import java.nio.file.WatchEvent; +import java.nio.file.WatchKey; +import java.nio.file.WatchService; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -87,6 +92,44 @@ public class TMXMapSet implements ProjectTreeNode { return o1.id.compareTo(o2.id); } }); + if (source.type == GameSource.Type.created | source.type == GameSource.Type.altered) { + final Path folderPath = Paths.get(mapFolder.getAbsolutePath()); + Thread watcher = new Thread("Map folder watcher for "+source.type) { + public void run() { + WatchService watchService; + + while(getProject().open) { + try { + watchService = FileSystems.getDefault().newWatchService(); + WatchKey watchKey = folderPath.register(watchService, StandardWatchEventKinds.ENTRY_MODIFY); + WatchKey wk; + validService: while(getProject().open) { + wk = watchService.take(); + for (WatchEvent event : wk.pollEvents()) { + Path changed = (Path) event.context(); + String name = changed.getFileName().toString(); + System.out.println("Changed: "+name); + String id = name.substring(0, name.length() - 4); + TMXMap map = getMap(id); + if (map != null) { + map.mapChangedOnDisk(); + } + } + if(!wk.reset()) { + watchService.close(); + break validService; + } + } + } catch (IOException e) { + e.printStackTrace(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + }; + }; + watcher.start(); + } } @Override diff --git a/src/com/gpl/rpg/atcontentstudio/ui/map/TMXMapEditor.java b/src/com/gpl/rpg/atcontentstudio/ui/map/TMXMapEditor.java index 769cff0..d90d97e 100644 --- a/src/com/gpl/rpg/atcontentstudio/ui/map/TMXMapEditor.java +++ b/src/com/gpl/rpg/atcontentstudio/ui/map/TMXMapEditor.java @@ -18,7 +18,6 @@ import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseMotionAdapter; import java.awt.event.MouseMotionListener; -import java.io.IOException; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; @@ -35,6 +34,7 @@ import javax.swing.JComboBox; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JList; +import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JSpinner; @@ -103,13 +103,15 @@ import com.gpl.rpg.atcontentstudio.utils.DesktopIntegration; import com.jidesoft.swing.JideBoxLayout; import com.jidesoft.swing.JideTabbedPane; -public class TMXMapEditor extends Editor { +public class TMXMapEditor extends Editor implements TMXMap.MapChangedOnDiskListener{ private static final long serialVersionUID = -3079451876618342442L; Map editorTabs = new LinkedHashMap(); JideTabbedPane editorTabsHolder; + + private JButton reload; private RSyntaxTextArea editorPane; @@ -189,6 +191,7 @@ public class TMXMapEditor extends Editor { this.name = map.getDesc(); this.icon = new ImageIcon(DefaultIcons.getTiledIconIcon()); + map.addMapChangedOnDiskListener(this); setLayout(new BorderLayout()); editorTabsHolder = new JideTabbedPane(JideTabbedPane.BOTTOM); @@ -1601,10 +1604,10 @@ public class TMXMapEditor extends Editor { } public JButton createButtonPane(JPanel pane, final Project proj, final TMXMap map, final FieldUpdateListener listener) { - final JButton gdeIcon = new JButton(new ImageIcon(DefaultIcons.getTiledIconImage())); JPanel savePane = new JPanel(); - savePane.add(gdeIcon, JideBoxLayout.FIX); savePane.setLayout(new JideBoxLayout(savePane, JideBoxLayout.LINE_AXIS, 6)); + final JButton gdeIcon = new JButton(new ImageIcon(DefaultIcons.getTiledIconImage())); + savePane.add(gdeIcon, JideBoxLayout.FIX); if (map.writable) { gdeIcon.addActionListener(new ActionListener() { @Override @@ -1612,7 +1615,25 @@ public class TMXMapEditor extends Editor { DesktopIntegration.openTmxMap(map.tmxFile); } }); - + reload = new JButton("Reload"); + reload.setEnabled(map.changedOnDisk); + savePane.add(reload, JideBoxLayout.FIX); + reload.addActionListener(new ActionListener() { + + @Override + public void actionPerformed(ActionEvent e) { + if (map.state == GameDataElement.State.modified) { + int confirm = JOptionPane.showConfirmDialog(TMXMapEditor.this, "You modified this map in ATCS. All ATCS-made changes will be lost if you confirm.\n On the other hand, if you save using ATCS, all external changes will be lost.\n Do you want to reload?", "Confirm reload?", JOptionPane.OK_CANCEL_OPTION); + if (confirm == JOptionPane.CANCEL_OPTION) return; + } + reload.setEnabled(false); + (new Thread(){ + public void run() { + map.reload(); + } + }).start(); + } + }); if (map.getDataType() == GameSource.Type.altered) { savePane.add(message = new JLabel(ALTERED_MESSAGE), JideBoxLayout.FIX); } else if (map.getDataType() == GameSource.Type.created) { @@ -1623,6 +1644,10 @@ public class TMXMapEditor extends Editor { @Override public void actionPerformed(ActionEvent e) { if (map.state != TMXMap.State.saved) { + if (map.changedOnDisk) { + int confirm = JOptionPane.showConfirmDialog(TMXMapEditor.this, "You modified this map in an external tool. All external changes will be lost if you confirm.\n On the other hand, if you reload in ATCS, all ATCS-made changes will be lost.\n Do you want to save?", "Confirm save?", JOptionPane.OK_CANCEL_OPTION); + if (confirm == JOptionPane.CANCEL_OPTION) return; + } map.save(); ATContentStudio.frame.nodeChanged(map); } @@ -2291,5 +2316,23 @@ public class TMXMapEditor extends Editor { g2d.fillRect(object.x + 2, object.y + 2, img.getWidth(null), img.getHeight(null)); g2d.drawImage(object.getIcon(), object.x + 2, object.y + 2, null); } + + + + @Override + public void mapChanged() { + if (reload != null) reload.setEnabled(true); + } + + + + @Override + public void mapReloaded() { + ATContentStudio.frame.nodeChanged(target); + ((TMXMap)target).removeMapChangedOnDiskListener(this); + ATContentStudio.frame.closeEditor(target); + ATContentStudio.frame.openEditor(target); + + } }