/*******************************************************************************
* Rhythos Editor is a game editor and project management tool for making RPGs on top of the Rhythos Game system.
*
* Copyright (C) 2013 David Maletz
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
package mrpg.editor;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.KeyEvent;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map.Entry;
import javax.imageio.ImageIO;
import javax.swing.AbstractButton;
import javax.swing.ButtonGroup;
import javax.swing.ImageIcon;
import javax.swing.InputMap;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSpinner;
import javax.swing.JSplitPane;
import javax.swing.JToggleButton;
import javax.swing.KeyStroke;
import javax.swing.SpinnerNumberModel;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.TreePath;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import mrpg.display.WorldPanel;
import mrpg.editor.resource.AnimationSet;
import mrpg.editor.resource.AutoTile;
import mrpg.editor.resource.Sprite;
import mrpg.editor.resource.SpriteLayer;
import mrpg.editor.resource.Folder;
import mrpg.editor.resource.Image;
import mrpg.editor.resource.Map;
import mrpg.editor.resource.Media;
import mrpg.editor.resource.Project;
import mrpg.editor.resource.Resource;
import mrpg.editor.resource.Script;
import mrpg.editor.resource.Tileset;
import mrpg.editor.resource.Workspace;
import mrpg.editor.tools.EraserTool;
import mrpg.editor.tools.FillTool;
import mrpg.editor.tools.LineTool;
import mrpg.editor.tools.PencilTool;
import mrpg.editor.tools.RectTool;
import mrpg.editor.tools.SelectTool;
import mrpg.editor.tools.ZoomTool;
import mrpg.plugin.PluginManager;
import mrpg.script.ScriptEditor;
import mrpg.world.World;
public class MapEditor extends JFrame implements WindowListener, ActionListener, ChangeListener, TreeSelectionListener, History.Listener, SelectTool.Listener, ZoomTool.Listener, Clipboard.Listener {
private static final long serialVersionUID = 7934438411041874443L;
public static final String NEW = "new", OPEN = "open", SAVE = "save", SAVE_ALL = "save_all", IMPORT = "import",
EXPORT = "export", TEST = "test", BUILD = "build", SEARCH = "search", HELP = "help", ABOUT = "about", PLUGINS = "plugins";
public static final String CUT = "cut", COPY = "copy", PASTE = "paste", DELETE = "delete", REFRESH = "refresh", REVERT = "revert", REMOVE = "remove", SEL_ALL = "sel_all",
DSEL_ALL = "dsel_all", SHOW_ALL = "show_all", SHOW_GRID = "grid", ANIMATE="animate", NEXT_LAYER = "next_l", PREV_LAYER = "prev_l";
public static final String RENAME = "rename";
public static final String UNDO = "undo", REDO = "redo", PROPERTIES = "properties", LAYER = "layer";
public static final String OK = "ok", CANCEL = "cancel", SET = "set", CLEAR = "clear", M_PLAYER = "media_player";
public static final int MAX_LAYERS = 20;
public final ButtonGroup tools = new ButtonGroup(); private WorldOverlay world_overlay;
private static ToolItem SELECT_TOOL, ZOOM_TOOL;
private JLabel map_label; private JSpinner zoom_spinner, layer_spinner; private WorkspaceBrowser browser;
private String map_name = ""; private int map_x = 0, map_y = 0; public boolean browser_focus = false;
private static ToggleableMenuItem undo, redo, cut, copy, paste, delete, dsel, prev_layer,
next_layer, prop, save, save_map, saveall, refresh, revert;
private static CheckboxMenuItem showGrid, showAll, animate;
private static final ArrayList<ToggleableMenuItem> buttons = new ArrayList<ToggleableMenuItem>();
private Map current_map;
private final History history = new History(); private final Clipboard clipboard = new Clipboard();
private TilesetViewer tileset_viewer; private MediaPlayer media_player; private JPanel tileset;
private MapEditor(){
super("Rhythos! Game Builder");
InputMap im = (InputMap)UIManager.getDefaults().get("Button.focusInputMap");
Object pressedAction = im.get(KeyStroke.getKeyStroke("pressed SPACE"));
Object releasedAction = im.get(KeyStroke.getKeyStroke("released SPACE"));
im.put(KeyStroke.getKeyStroke("pressed ENTER"), pressedAction);
im.put(KeyStroke.getKeyStroke("released ENTER"), releasedAction);
history.listener = this; clipboard.listener = this;
browser = new WorkspaceBrowser(this); browser.addTreeSelectionListener(this);
}
public void init(){
WorldPanel w = new WorldPanel(TilesetViewer.TILE_SIZE,TilesetViewer.TILE_SIZE);
setJMenuBar(menu_bar.getMenuBar());
Container c = getContentPane(); c.setLayout(new BorderLayout());
c.add(toolbar.getToolbar(), BorderLayout.NORTH);
JSplitPane frame = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
c.add(frame, BorderLayout.CENTER);
JSplitPane pane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
//ProjectViewer pv = new ProjectViewer(w, tv);
//pane.add(tv);
//pane.add(pv);
pane.setResizeWeight(0.75);
tileset_viewer = new TilesetViewer();
world_overlay = new WorldOverlay(this, w); tileset_viewer.overlay = world_overlay;
w.overlay = tileset_viewer.overlay; w.showLevel(0);
tileset = new JPanel(new BorderLayout());
SelectTool select = (SelectTool)SELECT_TOOL.getTool(); select.listener = this;
ZoomTool zoom = (ZoomTool)ZOOM_TOOL.getTool(); zoom.listener = this;
tileset.add(tile_toolbar_right.addItemsTo(tile_toolbar.getToolbar()), BorderLayout.NORTH);
tileset.add(new JScrollPane(tileset_viewer), BorderLayout.CENTER);
pane.add(tileset);
pane.add(new JScrollPane(browser));
pane.setDividerLocation(TilesetViewer.TILE_SIZE*8);
JScrollPane sp = new JScrollPane(w); Insets i = sp.getInsets(); sp.getViewport().setBackground(new Color(30,30,30));
int sw = sp.getVerticalScrollBar().getPreferredSize().width+i.left+i.right;
i = pane.getInsets();
frame.setDividerLocation(TilesetViewer.TILE_SIZE*8+1+sw+i.left+i.right+1);
frame.add(pane);
sp.setMinimumSize(new Dimension(TilesetViewer.TILE_SIZE+sw,TilesetViewer.TILE_SIZE+sp.getHorizontalScrollBar().getPreferredSize().height));
JPanel edit = new JPanel(new BorderLayout());
edit.add(sp, BorderLayout.CENTER);
JPanel options = new JPanel(new BorderLayout());
JPanel inner = new JPanel();
JLabel l = new JLabel(getIcon(zoom.getIcon())); l.setToolTipText("Map Scale"); inner.add(l);
zoom_spinner = new JSpinner(new SpinnerNumberModel(100.0, 6.25, 800.0, 1.0));
zoom_spinner.addChangeListener(this); inner.add(zoom_spinner);
inner.add(new JLabel("%"));
options.add(inner, BorderLayout.WEST);
map_label = new JLabel("", getIcon(Map.MAP), JLabel.CENTER);
options.add(map_label, BorderLayout.CENTER);
inner = new JPanel();
l = new JLabel(getIcon(LAYER)); l.setToolTipText("Map Editing Layer"); inner.add(l);
layer_spinner = new JSpinner(new SpinnerNumberModel(0, 0, MAX_LAYERS, 1));
layer_spinner.addChangeListener(this); inner.add(layer_spinner);
options.add(inner, BorderLayout.EAST);
edit.add(options, BorderLayout.SOUTH);
frame.add(edit);
world_overlay.setTool(select);
deselect(); historyChanged(); clipboardChanged(); valueChanged(null);
w.startAnim();
addWindowListener(this); setIconImages(getWindowIcon());
setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); setSize(800,600);
loadSettings();
}
public WorldOverlay getOverlay(){return world_overlay;}
public History getHistory(){return history;}
private static Hashtable<String, ImageIcon> icons = new Hashtable<String, ImageIcon>();
public static ImageIcon getIcon(String s) {
if(s == null) return null;
if(icons.containsKey(s)) return icons.get(s);
try{
ImageIcon i = new ImageIcon(MapEditor.class.getResource("/icons/"+s+".png"));
icons.put(s, i); return i;
}catch(Exception e){return null;}
}
public static JMenuItem createMenuItemIcon(String text, String icon, ActionListener l){
JMenuItem i = new JMenuItem(text, getIcon(icon)); i.setActionCommand(icon); i.addActionListener(l); return i;
}
public static JMenuItem createMenuItemIcon(String text, String icon, int keyCode, int modifiers, ActionListener l){
JMenuItem i = createMenuItemIcon(text, icon, l);
if(keyCode != 0) i.setAccelerator(KeyStroke.getKeyStroke(keyCode, modifiers)); return i;
}
public static JMenuItem createMenuItemIcon(String text, String icon, String command, ActionListener l){
JMenuItem i = new JMenuItem(text, getIcon(icon)); i.setActionCommand(command); i.addActionListener(l); return i;
}
public static JMenuItem createMenuItem(String text, String command, ActionListener l){
JMenuItem i = new JMenuItem(text); i.setActionCommand(command); i.addActionListener(l); return i;
}
public static JMenuItem createMenuItem(String text, String command, int keyCode, int modifiers, ActionListener l){
JMenuItem i = createMenuItem(text, command, l);
if(keyCode != 0) i.setAccelerator(KeyStroke.getKeyStroke(keyCode, modifiers)); return i;
}
public static JCheckBoxMenuItem createCheckboxMenuItem(String text, String command, ActionListener l){
JCheckBoxMenuItem i = new JCheckBoxMenuItem(text); i.setActionCommand(command); i.addActionListener(l); return i;
}
public static JCheckBoxMenuItem createCheckboxMenuItem(String text, String command, int keyCode, int modifiers, ActionListener l){
JCheckBoxMenuItem i = createCheckboxMenuItem(text, command, l);
if(keyCode != 0) i.setAccelerator(KeyStroke.getKeyStroke(keyCode, modifiers)); return i;
}
public static JButton createToolbarButton(String icon, String tooltip, ActionListener l){
JButton b = new JButton(getIcon(icon)); b.setMargin(new Insets(0,0,0,0)); b.setActionCommand(icon);
b.addActionListener(l); b.setToolTipText(tooltip); return b;
}
public static JToggleButton createToolbarButton(String icon, String tooltip, ButtonGroup group, ActionListener l){return createToolbarButton(icon, tooltip, group, l, false);}
public static JToggleButton createToolbarButton(String icon, String tooltip, ButtonGroup group, ActionListener l, boolean selected){
JToggleButton b = new JToggleButton(getIcon(icon)); b.setMargin(new Insets(0,0,0,0)); b.setActionCommand(icon);
b.setToolTipText(tooltip); b.addActionListener(l); b.setSelected(selected); group.add(b); return b;
}
public static JCheckBox createToolbarCheckBox(String icon, String tooltip, ActionListener l){
JCheckBox b = new JCheckBox(getIcon(icon)); b.setMargin(new Insets(0,0,0,0)); b.setActionCommand(icon);
b.addActionListener(l); b.setToolTipText(tooltip); return b;
}
public static final Menu menu_bar = new Menu(), toolbar = new Menu(), tile_toolbar = new Menu(), tile_toolbar_right = new Menu();
public void updateMenuBar(){setJMenuBar(menu_bar.getMenuBar()); validate();}
public void updateToolbar(){
Container c = getContentPane(); c.remove(0); c.add(toolbar.getToolbar(), BorderLayout.NORTH, 0); c.validate();
}
public void updateTileToolbar(){
tileset.remove(0); tileset.add(tile_toolbar_right.addItemsTo(tile_toolbar.getToolbar()), BorderLayout.NORTH, 0); tileset.validate();
}
public void actionPerformed(ActionEvent e) {
String command = e.getActionCommand(); SelectTool select = (SelectTool)SELECT_TOOL.getTool();
if(command == SAVE){
if(current_map != null && current_map.isModified()) try{current_map.save(); updateSaveButtons();}catch(Exception ex){}
} else if(command == CUT){if(!browser_focus){select.copy(clipboard); select.deleteSelection();}
} else if(command == COPY){if(browser_focus){browser.copy(); paste.setEnabled(browser.hasClipboardData());} else select.copy(clipboard);
} else if(command == PASTE){if(browser_focus) try{browser.paste();}catch(Exception ex){}
else {
world_overlay.setTool(select);
tools.setSelected(((JToggleButton)SELECT_TOOL.getToolbarItem()).getModel(), true);
select.paste(clipboard);
}
} else if(command == DELETE){if(browser_focus) browser.deleteSelection(); else select.deleteSelection();
} else if(command == SEL_ALL){
if(browser_focus){browser.addSelectionInterval(0, browser.getRowCount()-1);}
else {
if(world_overlay.getPanel().getWorld() != null){
world_overlay.setTool(select);
tools.setSelected(((JToggleButton)SELECT_TOOL.getToolbarItem()).getModel(), true);
select.selectAll();
}
}
} else if(command == DSEL_ALL){if(browser_focus) browser.clearSelection(); else select.deselectAll();
} else if(command == SHOW_ALL){
WorldPanel world = world_overlay.getPanel();
world.showLevel((showAll.isSelected())?-1:world.getEditLevel());
} else if(command == ANIMATE){
WorldPanel world = world_overlay.getPanel();
world.animate = animate.isSelected();
} else if(command == SHOW_GRID){world_overlay.getPanel().setShowGrid(((AbstractButton)e.getSource()).isSelected());
} else if(command == PREV_LAYER){layer_spinner.setValue(Math.max(0, (Integer)layer_spinner.getValue()-1));
} else if(command == NEXT_LAYER){layer_spinner.setValue(Math.min(MAX_LAYERS, (Integer)layer_spinner.getValue()+1));
} else if(command == UNDO){
if(world_overlay.getTool() == select) select.activate();
history.undo(); world_overlay.getPanel().repaint();
} else if(command == REDO){
if(world_overlay.getTool() == select) select.activate();
history.redo(); world_overlay.getPanel().repaint();
} else if(command == PROPERTIES){if(current_map != null) current_map.properties();
} else if(command == Folder.ADD_FOLDER){
browser.addFolder();
} else if(command == M_PLAYER){
if(media_player == null) media_player = new MediaPlayer(getBrowser());
TreePath p = browser.getSelectionPath(); if(p != null) media_player.setProject(WorkspaceBrowser.getProject(p));
media_player.setVisible(true);
} else if(command == PLUGINS) PluginManager.openManager();
if(!browser_focus){
if(world_overlay.getTool() != select) deselect();
removeFocus();
}
}
private int lastSave;
public void historyChanged(){
boolean hasUndo = history.hasUndo(); undo.setEnabled(hasUndo);
boolean hasRedo = history.hasRedo(); redo.setEnabled(hasRedo);
if(current_map != null){current_map.setModified(lastSave != history.redoPos()); updateSaveButtons();}
}
public void select(){
dsel.setEnabled(true);
if(browser_focus){delete.setEnabled(browser.canDeleteSelection()); refresh.setEnabled(browser.canRefreshSelection());}
else{delete.setEnabled(true); refresh.setEnabled(false);}
cut.setEnabled(!browser_focus); copy.setEnabled(delete.isEnabled());
}
public void deselect(){
delete.setEnabled(false); dsel.setEnabled(false); refresh.setEnabled(false);
cut.setEnabled(delete.isEnabled()); copy.setEnabled(delete.isEnabled());
}
public void gainBrowserFocus(){
browser_focus = true; if(browser.getSelectionCount() > 0) select(); else deselect();
paste.setEnabled(browser.hasClipboardData());
}
public void loseBrowserFocus(){
browser_focus = false; if(((SelectTool)SELECT_TOOL.getTool()).hasSelection()) select(); else deselect();
paste.setEnabled(clipboard.hasData());
}
private void updateSaveRevert(){
save.setEnabled(browser.selectionModified());
save_map.setEnabled(current_map != null && current_map.isModified());
revert.setEnabled(save.isEnabled());
}
public void updateSaveButtons(){
updateSaveRevert(); saveall.setEnabled(browser.anyModified());
}
public void valueChanged(TreeSelectionEvent e) {
gainBrowserFocus();
int ct = browser.getSelectionCount();
for(ToggleableMenuItem b : buttons){
String command = b.getMenuItem(true).getActionCommand();
if(command == PROPERTIES) b.setEnabled(ct == 1 && WorkspaceBrowser.getResource(browser.getSelectionPath()).hasProperties());
else if(command == RENAME) b.setEnabled(ct == 1);
else b.setEnabled(ct > 0);
}
if(ct > 0) select(); else deselect();
updateSaveRevert();
}
public TilesetViewer getTilesetViewer(){return tileset_viewer;}
private boolean refresh_tileset=false;
private class TilesetRefresher implements Runnable {
public void run(){
tileset_viewer.refresh(); if(current_map != null) current_map.getWorld().refresh(WorkspaceBrowser.getProject(current_map));
refresh_tileset = false;
}
} private TilesetRefresher tileset_refresh = new TilesetRefresher();
public void refreshTilesets(){
if(!refresh_tileset){SwingUtilities.invokeLater(tileset_refresh); refresh_tileset = true;}
}
public boolean hasMap(){return current_map != null;}
public World getWorld(){return world_overlay.getPanel().getWorld();}
public void setMap(Map m){
World w = m.getWorld();
WorldPanel p = world_overlay.getPanel();
if(p.getWorld() == w) return;
current_map = m; lastSave = (m.isModified())?-1:0;
prop.setEnabled(true); Project project = WorkspaceBrowser.getProject(m);
p.setWorld(w, project.tile_size); p.worldSize(w.getWidth()*project.tile_size, w.getHeight()*project.tile_size);
history.clearHistory(); history.world = w; setMapName(m.getName());
tileset_viewer.setProject(project); w.refresh(project);
}
public void updateMap(Map m){
World w = m.getWorld();
WorldPanel p = world_overlay.getPanel();
if(p.getWorld() != w) return;
p.worldSize(w.getWidth()*p.tile_size, w.getHeight()*p.tile_size);
}
public boolean removeMap(Map m){
World w = m.getWorld();
WorldPanel p = world_overlay.getPanel();
if(p.getWorld() != w) return false; current_map = null; lastSave = (m.isModified())?-1:0;
prop.setEnabled(false); p.setWorld(null, 0); p.worldSize(p.tile_size, p.tile_size);
history.clearHistory(); history.world = null; map_label.setText("");
tileset_viewer.setProject(null); return true;
}
public void saveMap(Map m){if(m == current_map){lastSave = history.redoPos();}}
public void clipboardChanged(){if(!browser_focus) paste.setEnabled(clipboard.hasData());}
private void updateMapName(){map_label.setText(map_name+" ("+map_x+", "+map_y+")");}
public void setMapName(String name){map_name = name; updateMapName();}
public void setMapLocation(int x, int y){map_x = x; map_y = y; updateMapName();}
public void stateChanged(ChangeEvent e) {
WorldPanel world = world_overlay.getPanel();
if(e.getSource() == zoom_spinner){
world.setScale(((Number)zoom_spinner.getValue()).doubleValue()*0.01);
}
else if(e.getSource() == layer_spinner){
int layer = (Integer)layer_spinner.getValue();
prev_layer.setEnabled(layer != 0);
next_layer.setEnabled(layer != MAX_LAYERS);
world.setEditLevel(layer);
if(!showAll.isSelected()) world.showLevel(world.getEditLevel());
if(world_overlay.getTool() == SELECT_TOOL) ((SelectTool)SELECT_TOOL.getTool()).deselectAll();
}
loseBrowserFocus();
requestFocus();
}
public void zoomChange(){zoom_spinner.setValue(world_overlay.getPanel().getScale()*100);}
public void removeFocus(){
try{zoom_spinner.commitEdit();}catch(Exception e){zoom_spinner.updateUI();}
try{layer_spinner.commitEdit();}catch(Exception e){layer_spinner.updateUI();}
loseBrowserFocus();
requestFocus();
}
public WorkspaceBrowser getBrowser(){return browser;}
public void windowActivated(WindowEvent e) {}
public void windowClosed(WindowEvent e) {}
private static class FrameHandler implements ComponentListener {
private JFrame frame; private Rectangle bounds; private boolean maximize;
private FrameHandler(){}
private FrameHandler(JFrame f){
frame = f; bounds = frame.getBounds(); maximize = frame.getExtendedState() == JFrame.MAXIMIZED_BOTH;
frame.addComponentListener(this);
}
private void loadElement(Element e){
bounds = new Rectangle();
try{
String ar[] = e.getAttribute("pos").split("x");
bounds.x = Integer.parseInt(ar[0].trim()); bounds.y = Integer.parseInt(ar[1].trim());
}catch(Exception ex){}
try{
String ar[] = e.getAttribute("size").split("x");
bounds.width = Integer.parseInt(ar[0].trim()); bounds.height = Integer.parseInt(ar[1].trim());
}catch(Exception ex){}
maximize = e.hasAttribute("maximize");
if(frame != null) updateFrame();
}
private void updateFrame(){
frame.setBounds(bounds); if(maximize) frame.setExtendedState(JFrame.MAXIMIZED_BOTH);
}
public void setFrame(JFrame f){
if(frame != null) return; frame = f; updateFrame(); frame.addComponentListener(this);
}
public void updateElement(Element e){
e.setAttribute("pos", bounds.x+"x"+bounds.y);
e.setAttribute("size", bounds.width+"x"+bounds.height);
if(frame != null && frame.getExtendedState() == JFrame.MAXIMIZED_BOTH) e.setAttribute("maximize", "true"); else e.removeAttribute("maximize");
}
public void dispose(){if(frame != null && frame != instance){frame.removeComponentListener(this); frame.dispose();}}
public void componentHidden(ComponentEvent e) {}
public void componentMoved(ComponentEvent e) {
if(frame != null && (frame.getExtendedState()&JFrame.MAXIMIZED_BOTH)==0){
Rectangle r = frame.getBounds(); bounds.x = r.x; bounds.y = r.y;
}
}
public void componentResized(ComponentEvent e) {
if(frame != null && (frame.getExtendedState()&JFrame.MAXIMIZED_BOTH)==0){
Rectangle r = frame.getBounds(); bounds.width = r.width; bounds.height = r.height;
}
}
public void componentShown(ComponentEvent e) {}
}
private static final HashMap<String, FrameHandler> frames = new HashMap<String, FrameHandler>();
public static void addFrame(String name, JFrame f){
FrameHandler h = frames.get(name); if(h == null){
frames.put(name, new FrameHandler(f));
} else h.setFrame(f);
}
public static interface OnClose {public void onClose();}
public static final ArrayList<OnClose> on_close = new ArrayList<OnClose>();
public void dispose(){
for(FrameHandler f : frames.values()) f.dispose();
for(OnClose c : on_close) c.onClose();
super.dispose();
}
public void windowClosing(WindowEvent e) {
if(saveall.isEnabled()){
int i = JOptionPane.showConfirmDialog(this, "Save all resources before closing?");
if(i == JOptionPane.NO_OPTION){writeWorkspace(); dispose(); instance = null;}
else if(i == JOptionPane.YES_OPTION){
browser.saveAll(); writeWorkspace(); dispose(); instance = null;
}
} else{writeWorkspace(); dispose(); instance = null;}
}
public void windowDeactivated(WindowEvent e) {}
public void windowDeiconified(WindowEvent e) {}
public void windowIconified(WindowEvent e) {}
public void windowOpened(WindowEvent e) {}
public static MapEditor instance;
public static final int DEF_MEDIA=0, DEF_IMG_RESOURCE=1, DEF_PROJECT=2, DEF_TILEMAP=3, DEF_MAP=4, N_DEF=5;
@SuppressWarnings("unchecked")
private static ArrayList<Resource> deferred[] = new ArrayList[N_DEF];
static {for(int i=0; i<N_DEF; i++) deferred[i] = new ArrayList<Resource>();}
public static void deferRead(Resource r, int type){deferred[type].add(r);}
public static void doDeferredRead(boolean delete){
for(int i=0; i<N_DEF; i++){
ArrayList<Resource> ar = deferred[i]; int end = ar.size(); for(int j=0; j<end; j++){
Resource r = ar.get(j); try{
r.deferredRead(r.getFile());
} catch(Exception e){r.editor.browser.removeResource(r,delete);}
} ar.clear();
}
}
private static final DocumentBuilderFactory document_factory = DocumentBuilderFactory.newInstance();
private static final TransformerFactory transformer_factory = TransformerFactory.newInstance();
public static int getSelectedIndex(ButtonGroup g) throws Exception {
Enumeration<AbstractButton> e = g.getElements(); int i = 0;
while(e.hasMoreElements()){if(g.isSelected(e.nextElement().getModel())) return i; i++;} throw new Exception();
}
private void writeWorkspace(){
try{
Document doc = document_factory.newDocumentBuilder().newDocument();
Element root = doc.createElement("workspace"); doc.appendChild(root);
for(Entry<String, FrameHandler> entry : frames.entrySet()){
Element e = doc.createElement("window"); e.setAttribute("name", entry.getKey());
entry.getValue().updateElement(e); root.appendChild(e);
} Element element = doc.createElement("projectDirectory");
element.setTextContent(Project.folderChooser.getCurrentDirectory().toString()); root.appendChild(element);
element = doc.createElement("imageDirectory");
element.setTextContent(WorkspaceBrowser.imgChooser.getCurrentDirectory().toString()); root.appendChild(element);
element = doc.createElement("mediaDirectory");
element.setTextContent(WorkspaceBrowser.sndChooser.getCurrentDirectory().toString()); root.appendChild(element);
element = doc.createElement("resourceDirectory");
element.setTextContent(Resource.resourceChooser.getCurrentDirectory().toString()); root.appendChild(element);
if(showGrid.isSelected()) root.appendChild(doc.createElement("showGrid"));
if(showAll.isSelected()) root.appendChild(doc.createElement("showAllLayers"));
if(animate.isSelected()) root.appendChild(doc.createElement("showMapAnimations"));
element = doc.createElement("layer");
element.setTextContent(layer_spinner.getValue().toString()); root.appendChild(element);
element = doc.createElement("zoom");
element.setTextContent(zoom_spinner.getValue().toString()); root.appendChild(element);
try{element = doc.createElement("tool");
element.setTextContent(Integer.toString(getSelectedIndex(tools))); root.appendChild(element);}catch(Exception ex){}
Workspace w = browser.getWorkspace();
for(int i=0; i<w.getProjectCount(); i++){
element = doc.createElement("project"); Project p = w.getProject(i);
Attr attr = doc.createAttribute("directory");
attr.setValue(p.getFile().toString()); element.setAttributeNode(attr);
if(current_map != null && WorkspaceBrowser.getProject(current_map) == p){
Element elem = doc.createElement("map"); elem.setTextContent(Long.toHexString(current_map.getId()));
element.appendChild(elem);
} tileset_viewer.addElements(doc, element, p); root.appendChild(element);
} Enumeration<TreePath> e = browser.getExpandedDescendants(new TreePath(browser.getWorkspace()));
while(e.hasMoreElements()){
File f = ((Resource)e.nextElement().getLastPathComponent()).getFile(); if(f == null) continue;
element = doc.createElement("expanded"); element.setTextContent(f.getAbsolutePath()); root.appendChild(element);
} PluginManager.getInstalledPlugins(doc, root);
Transformer transformer = transformer_factory.newTransformer(); transformer.setOutputProperty(OutputKeys.INDENT, "yes");
StreamResult result = new StreamResult(new File(".workspace"));
transformer.transform(new DOMSource(doc), result);
} catch(Exception e){e.printStackTrace();}
}
public static <T> T getByIndex(Enumeration<T> e, int i) throws Exception {
while(i > 0){e.nextElement(); i--;} return e.nextElement();
}
public void loadSettings(){
PluginManager.setInitStatus("Loading Settings..."); File f = new File(".workspace");
if(f.exists()) try{
Document doc = document_factory.newDocumentBuilder().parse(f);
Element root = doc.getDocumentElement();
NodeList list = root.getElementsByTagName("window");
for(int i=0; i<list.getLength(); i++){
try{
Element element = (Element)list.item(i); String n = element.getAttribute("name");
FrameHandler h = frames.get(n); if(h == null){h = new FrameHandler(); frames.put(n, h);}
h.loadElement(element);
}catch(Exception ex){}
} try{Project.folderChooser.setCurrentDirectory(new File(root.getElementsByTagName("projectDirectory").item(0).getTextContent()));}catch(Exception ex){}
try{WorkspaceBrowser.imgChooser.setCurrentDirectory(new File(root.getElementsByTagName("imageDirectory").item(0).getTextContent()));}catch(Exception ex){}
try{WorkspaceBrowser.sndChooser.setCurrentDirectory(new File(root.getElementsByTagName("mediaDirectory").item(0).getTextContent()));}catch(Exception ex){}
try{Resource.resourceChooser.setCurrentDirectory(new File(root.getElementsByTagName("resourceDirectory").item(0).getTextContent()));}catch(Exception ex){}
if(root.getElementsByTagName("showGrid").getLength() > 0) showGrid.getMenuItem(true).doClick();
if(root.getElementsByTagName("showAllLayers").getLength() > 0) showAll.getMenuItem(true).doClick();
if(root.getElementsByTagName("showMapAnimations").getLength() > 0) animate.getMenuItem(true).doClick();
try{layer_spinner.setValue(Integer.parseInt(root.getElementsByTagName("layer").item(0).getTextContent()));}catch(Exception ex){}
try{zoom_spinner.setValue(Double.parseDouble(root.getElementsByTagName("zoom").item(0).getTextContent()));}catch(Exception ex){}
try{getByIndex(tools.getElements(), Integer.parseInt(root.getElementsByTagName("tool").item(0).getTextContent())).doClick();}catch(Exception ex){}
list = root.getElementsByTagName("project");
for(int i=0; i<list.getLength(); i++){
try{
Element element = (Element)list.item(i);
Project p = browser.addProject(new File(element.getAttribute("directory")));
try{p.getById(Map.TYPE, new BigInteger(element.getElementsByTagName("map").item(0).getTextContent(),16).longValue()).edit();}catch(Exception exx){}
try{
Element e = (Element)element.getElementsByTagName("tileset").item(0);
p.getById(e.getAttribute("type"), new BigInteger(e.getTextContent(),16).longValue()).edit();
}catch(Exception exx){}
NodeList n = element.getElementsByTagName("autotile");
for(int j=0; j<n.getLength(); j++)
try{
Element e = (Element)n.item(j);
p.getById(e.getAttribute("type"), new BigInteger(e.getTextContent(),16).longValue()).edit();
}catch(Exception exx){}
}catch(Exception ex){}
} list = root.getElementsByTagName("expanded");
for(int i=0; i<list.getLength(); i++){
try{
File file = new File(((Element)list.item(i)).getTextContent()); browser.expand(file);
}catch(Exception ex){}
} PluginManager.installPlugins(doc, root);
} catch(Exception e){e.printStackTrace();} addFrame("main", this); setVisible(true);
}
private static void setupMenuBar(){
Menu menu = menu_bar.addMenu("File", null);
menu.addItem("Create New Project", Project.PROJECT, KeyEvent.VK_N, ActionEvent.CTRL_MASK, null);
menu.addItem("Open Existing Project", OPEN, KeyEvent.VK_O, ActionEvent.CTRL_MASK, null);
menu.addItem(Folder.new_options); menu.addSeparator();
save_map = new ToggleableMenuItem("Save Current Map", SAVE, KeyEvent.VK_S, ActionEvent.CTRL_MASK, actionListener, false);
save = new ToggleableMenuItem("Save Selected", SAVE, null, false);
saveall = new ToggleableMenuItem("Save All", SAVE_ALL, KeyEvent.VK_S, ActionEvent.CTRL_MASK | ActionEvent.SHIFT_MASK, null, false);
revert = new ToggleableMenuItem("Revert", REVERT, null, false);
menu.addItem(save_map); menu.addItem(save); menu.addItem(saveall); menu.addItem(revert); menu.addSeparator();
menu.addItem(Folder.import_options); menu.addSeparator();
refresh = new ToggleableMenuItem("Refresh", REFRESH, KeyEvent.VK_F5, 0, null, false); menu.addItem(refresh);
ToggleableMenuItem item = new ToggleableMenuItem("Rename", RENAME, null);
buttons.add(item); menu.addItem(item);
item = new ToggleableMenuItem("Properties", PROPERTIES, KeyEvent.VK_P, ActionEvent.CTRL_MASK, null);
buttons.add(item); menu.addItem(item);
menu = menu_bar.addMenu("Edit", null);
undo = new ToggleableMenuItem("Undo", UNDO, KeyEvent.VK_Z, ActionEvent.CTRL_MASK, actionListener);
redo = new ToggleableMenuItem("Redo", REDO, KeyEvent.VK_Y, ActionEvent.CTRL_MASK, actionListener);
menu.addItem(undo); menu.addItem(redo); menu.addSeparator();
cut = new ToggleableMenuItem("Cut", CUT, KeyEvent.VK_X, ActionEvent.CTRL_MASK, actionListener);
copy = new ToggleableMenuItem("Copy", COPY, KeyEvent.VK_C, ActionEvent.CTRL_MASK, actionListener);
paste = new ToggleableMenuItem("Paste", PASTE, KeyEvent.VK_V, ActionEvent.CTRL_MASK, actionListener);
delete = new ToggleableMenuItem("Delete", DELETE, KeyEvent.VK_DELETE, 0, actionListener);
menu.addItem(cut); menu.addItem(copy); menu.addItem(paste); menu.addItem(delete); menu.addSeparator();
dsel = new ToggleableMenuItem("Deselect All", DSEL_ALL, KeyEvent.VK_D, ActionEvent.CTRL_MASK, actionListener);
menu.addItem("Select All", SEL_ALL, KeyEvent.VK_A, ActionEvent.CTRL_MASK, actionListener); menu.addItem(dsel);
menu = menu_bar.addMenu("View", null);
showGrid = new CheckboxMenuItem("Show Grid", SHOW_GRID, KeyEvent.VK_G, ActionEvent.CTRL_MASK, actionListener); menu.addItem(showGrid);
showAll = new CheckboxMenuItem("Show All Layers", SHOW_ALL, KeyEvent.VK_A, ActionEvent.CTRL_MASK | ActionEvent.SHIFT_MASK, actionListener); menu.addItem(showAll);
animate = new CheckboxMenuItem("Show Map Animations", ANIMATE, actionListener); menu.addItem(animate);
menu.addSeparator();
prev_layer = new ToggleableMenuItem("Previous Layer", PREV_LAYER, KeyEvent.VK_L, ActionEvent.CTRL_MASK | ActionEvent.SHIFT_MASK, actionListener, false);
next_layer = new ToggleableMenuItem("Next Layer", NEXT_LAYER, KeyEvent.VK_L, ActionEvent.CTRL_MASK, actionListener);
menu.addItem(prev_layer); menu.addItem(next_layer);
menu = menu_bar.addMenu("Tools", null);
item = new ToggleableMenuItem("Test Game", TEST, KeyEvent.VK_ENTER, ActionEvent.CTRL_MASK, null);
buttons.add(item); menu.addItem(item);
item = new ToggleableMenuItem("Build Game", BUILD, KeyEvent.VK_ENTER, ActionEvent.ALT_MASK, null);
buttons.add(item); menu.addItem(item); menu.addSeparator();
menu.addItem("Media Player", M_PLAYER, KeyEvent.VK_M, ActionEvent.CTRL_MASK | ActionEvent.SHIFT_MASK, actionListener);
menu.addItem("Search", SEARCH, KeyEvent.VK_F, ActionEvent.CTRL_MASK, actionListener);
menu = menu_bar.addMenu("Help", null);
menu.addItem("Help Contents", HELP, KeyEvent.VK_F1, 0, actionListener);
menu.addItem("About", ABOUT, actionListener);
menu.addSeparator();
menu.addItem("Plugins", PLUGINS, actionListener);
}
public static void setupToolbar(){
Menu file = (Menu)menu_bar.children.get(0);
toolbar.addItem(file.children.get(0)); //Create New Project
toolbar.addItem(file.children.get(1)); //Open Existing Project
toolbar.addSeparator();
toolbar.addItem(save_map);
toolbar.addItem(buttons.get(0)); //Rename
toolbar.addSeparator();
toolbar.addItem(prev_layer);
toolbar.addItem(next_layer);
toolbar.addSeparator();
toolbar.addItem(buttons.get(2)); //Test Game
toolbar.addItem(buttons.get(3)); //Build Game
toolbar.addSeparator();
Menu tools = (Menu)menu_bar.children.get(3);
toolbar.addItem(tools.children.get(3)); //Search
toolbar.addSeparator();
Menu help = (Menu)menu_bar.children.get(4);
toolbar.addItem(help.children.get(0)); //Help Contents
}
public static void setupTileToolbar(){
tile_toolbar.addItem(undo);
tile_toolbar.addItem(redo);
tile_toolbar.addSeparator();
SELECT_TOOL = new ToolItem(SelectTool.class); tile_toolbar.addItem(SELECT_TOOL);
ZOOM_TOOL = new ToolItem(ZoomTool.class); tile_toolbar.addItem(ZOOM_TOOL);
tile_toolbar_right.addSeparator();
prop = new ToggleableMenuItem("Map properties", PROPERTIES, actionListener, false);
tile_toolbar_right.addItem(prop);
}
public static String safeName(String name){return name.replaceAll("[\u0000-\u001f\\/\\n\\r\\t\\f\\`\\?\\*\\\\\\<\\>\\|\\\"\\:]", "");}
public static void main(String[] args){
PluginManager.showInitWindow(); PluginManager.setInitStatus("Initializing..."); ScriptEditor.init();
setupMenuBar(); setupToolbar(); setupTileToolbar();
try{
Resource.register(); Folder.register(); Image.register(); Media.register();
Map.register(); Tileset.register(); AutoTile.register(); Sprite.register();
AnimationSet.register(); SpriteLayer.register(); Script.register();
}catch(Exception e){System.out.println("Resource Conflict"); System.exit(-1);}
tile_toolbar.addItem(new ToolItem(PencilTool.class));
tile_toolbar.addItem(new ToolItem(EraserTool.class));
tile_toolbar.addItem(new ToolItem(LineTool.class));
tile_toolbar.addItem(new ToolItem(RectTool.class));
tile_toolbar.addItem(new ToolItem(FillTool.class));
//TODO: Save tileset selection to workspace.
//TODO: Events, Plugin system
//TODO: Map wrap and background don't work client side.
//TODO: Clean up client side API to make it easy to manage resources, right now it's very messy.
//TODO: For the demo, I want to have map editing and events working, with the ability to create and preview maps. Maybe battle system too.
//TODO: Offsets for sprites to arrange the positions of different layers?
//TODO: player twitch when hitting two arrow keys rapidly?
//TODO: debugging info
//TODO: GUI UI Form Builder
//TODO: fix up autotile management to work with the new system.
//TODO: when target changes, script editor has to refresh, as monsters might reference DIFFERENT database entries. Register script quick commands too!
//TODO: built in error checking and exception handling in the client - so we know what went wrong when something goes wrong.
//TODO: Image Editing based on the map editor? Or perhaps based on jhlabs image editor?
//TODO: Animation Editing based on VIDE?
//TODO: Sound Editing based on ??
instance = new MapEditor(); instance.init();
}
private static class Listener implements ActionListener {
public void actionPerformed(ActionEvent e){MapEditor.instance.actionPerformed(e);}
} public static final Listener actionListener = new Listener();
private static ArrayList<BufferedImage> ico;
public static ArrayList<BufferedImage> getWindowIcon(){
if(ico == null){
ico = new ArrayList<BufferedImage>();
try{ico.add(ImageIO.read(MapEditor.class.getResource("/data/icon16.png")));}catch(Exception e){}
try{ico.add(ImageIO.read(MapEditor.class.getResource("/data/icon24.png")));}catch(Exception e){}
try{ico.add(ImageIO.read(MapEditor.class.getResource("/data/icon32.png")));}catch(Exception e){}
try{ico.add(ImageIO.read(MapEditor.class.getResource("/data/icon48.png")));}catch(Exception e){}
try{ico.add(ImageIO.read(MapEditor.class.getResource("/data/icon64.png")));}catch(Exception e){}
} return ico;
}
}