diff --git a/.classpath b/.classpath index 2709d4f..cb7b775 100644 --- a/.classpath +++ b/.classpath @@ -11,5 +11,6 @@ + diff --git a/lib/bsh-2.0b4.jar b/lib/bsh-2.0b4.jar new file mode 100644 index 0000000..36fe03d Binary files /dev/null and b/lib/bsh-2.0b4.jar differ diff --git a/res/LICENSE.LGPLv3.txt b/res/LICENSE.LGPLv3.txt new file mode 100644 index 0000000..65c5ca8 --- /dev/null +++ b/res/LICENSE.LGPLv3.txt @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/src/com/gpl/rpg/atcontentstudio/ATContentStudio.java b/src/com/gpl/rpg/atcontentstudio/ATContentStudio.java index 60947e5..8c77d7e 100644 --- a/src/com/gpl/rpg/atcontentstudio/ATContentStudio.java +++ b/src/com/gpl/rpg/atcontentstudio/ATContentStudio.java @@ -18,7 +18,7 @@ import com.gpl.rpg.atcontentstudio.ui.WorkspaceSelector; public class ATContentStudio { public static final String APP_NAME = "Andor's Trail Content Studio"; - public static final String APP_VERSION = "v0.3.4.dev"; + public static final String APP_VERSION = "v0.4.0"; public static boolean STARTED = false; public static StudioFrame frame = null; diff --git a/src/com/gpl/rpg/atcontentstudio/model/Project.java b/src/com/gpl/rpg/atcontentstudio/model/Project.java index 4279bc7..494ded4 100644 --- a/src/com/gpl/rpg/atcontentstudio/model/Project.java +++ b/src/com/gpl/rpg/atcontentstudio/model/Project.java @@ -773,6 +773,26 @@ public class Project implements ProjectTreeNode, Serializable { ((JSONElement) target).jsonFile = new File(baseContent.gameData.getGameDataElement(((JSONElement)target).getClass(), target.id).jsonFile.getAbsolutePath()); alteredContent.gameData.addElement((JSONElement) target); } + + public void createWorldmapSegment(WorldmapSegment node) { + node.writable = true; + if (getWorldmapSegment(node.id) != null) { + WorldmapSegment existingNode = getWorldmapSegment(node.id); + for (GameDataElement backlink : existingNode.getBacklinks()) { + backlink.elementChanged(existingNode, node); + } + existingNode.getBacklinks().clear(); + node.writable = true; + node.state = GameDataElement.State.created; + alteredContent.worldmap.addSegment(node); + node.link(); + } else { + createdContent.worldmap.addSegment(node); + node.state = GameDataElement.State.created; + node.link(); + } + fireElementAdded(node, getNodeIndex(node)); + } @Override diff --git a/src/com/gpl/rpg/atcontentstudio/model/maps/TMXMapSet.java b/src/com/gpl/rpg/atcontentstudio/model/maps/TMXMapSet.java index 63f0b33..9229f76 100644 --- a/src/com/gpl/rpg/atcontentstudio/model/maps/TMXMapSet.java +++ b/src/com/gpl/rpg/atcontentstudio/model/maps/TMXMapSet.java @@ -4,6 +4,7 @@ import java.awt.Image; import java.io.File; import java.util.ArrayList; import java.util.Collections; +import java.util.Comparator; import java.util.Enumeration; import java.util.List; @@ -48,6 +49,12 @@ public class TMXMapSet implements ProjectTreeNode { } } } + Collections.sort(tmxMaps, new Comparator() { + @Override + public int compare(TMXMap o1, TMXMap o2) { + return o1.id.compareTo(o2.id); + } + }); } @Override diff --git a/src/com/gpl/rpg/atcontentstudio/model/maps/WorldmapSegment.java b/src/com/gpl/rpg/atcontentstudio/model/maps/WorldmapSegment.java index 9b26b27..c3287bd 100644 --- a/src/com/gpl/rpg/atcontentstudio/model/maps/WorldmapSegment.java +++ b/src/com/gpl/rpg/atcontentstudio/model/maps/WorldmapSegment.java @@ -3,6 +3,8 @@ package com.gpl.rpg.atcontentstudio.model.maps; import java.awt.Image; import java.awt.Point; import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -21,8 +23,9 @@ public class WorldmapSegment extends GameDataElement { public int segmentX; public int segmentY; - public Map mapLocations = new HashMap(); - public Map labelLocations = new HashMap(); + public Map mapLocations = new LinkedHashMap(); + public Map> labelledMaps = new LinkedHashMap>(); + public Map labels = new LinkedHashMap(); public Element xmlNode; public WorldmapSegment(Worldmap parent, String name, Element xmlNode) { @@ -49,11 +52,18 @@ public class WorldmapSegment extends GameDataElement { for (int j = 0; j < mapsList.getLength(); j++) { Element mapNode = (Element) mapsList.item(j); mapLocations.put(mapNode.getAttribute("id"), new Point(Integer.parseInt(mapNode.getAttribute("x")) - segmentX, Integer.parseInt(mapNode.getAttribute("y")) - segmentY)); + String area; + if ((area = mapNode.getAttribute("area")) != null && !"".equals(area)) { + if (labelledMaps.get(area) == null) { + labelledMaps.put(area, new LinkedList()); + } + labelledMaps.get(area).add(mapNode.getAttribute("id")); + } } NodeList namedAreasNodeList = xmlNode.getElementsByTagName("namedarea"); for (int j = 0; j < namedAreasNodeList.getLength(); j++) { Element namedAreaNode = (Element) namedAreasNodeList.item(j); - labelLocations.put(namedAreaNode.getAttribute("id"), new NamedArea(namedAreaNode.getAttribute("id"), namedAreaNode.getAttribute("name"), namedAreaNode.getAttribute("type"))); + labels.put(namedAreaNode.getAttribute("id"), new NamedArea(namedAreaNode.getAttribute("id"), namedAreaNode.getAttribute("name"), namedAreaNode.getAttribute("type"))); } this.state = State.parsed; } @@ -104,10 +114,15 @@ public class WorldmapSegment extends GameDataElement { map.setAttribute("id", s); map.setAttribute("x", Integer.toString(mapLocations.get(s).x + segmentX)); map.setAttribute("y", Integer.toString(mapLocations.get(s).y + segmentY)); + for (String label : labelledMaps.keySet()) { + if (labelledMaps.get(label).contains(s)) { + map.setAttribute("area", label); + } + } element.appendChild(map); } - for (NamedArea area : labelLocations.values()) { + for (NamedArea area : labels.values()) { Element namedArea = doc.createElement("namedarea"); namedArea.setAttribute("id", area.id); namedArea.setAttribute("name", area.name); @@ -117,6 +132,7 @@ public class WorldmapSegment extends GameDataElement { return element; } + @Override public List attemptSave() { @@ -126,9 +142,9 @@ public class WorldmapSegment extends GameDataElement { } public static class NamedArea { - String id; - String name; - String type; + public String id; + public String name; + public String type; public NamedArea(String id, String name, String type) { this.id = id; diff --git a/src/com/gpl/rpg/atcontentstudio/ui/AboutEditor.java b/src/com/gpl/rpg/atcontentstudio/ui/AboutEditor.java index d330f17..63a94d5 100644 --- a/src/com/gpl/rpg/atcontentstudio/ui/AboutEditor.java +++ b/src/com/gpl/rpg/atcontentstudio/ui/AboutEditor.java @@ -69,6 +69,9 @@ public class AboutEditor extends Editor { "Prefuse by the Berkeley Institue of Design.
" + "License: Modified BSD License (a.k.a 3-Clause BSD)
" + "
" + + "BeanShell by Pat Niemeyer.
" + + "License: LGPL v3
" + + "
" + "See the tabs below to find the full license text for each of these.
" + "
" + "The Windows installer was created with:
" + @@ -116,6 +119,7 @@ public class AboutEditor extends Editor { editorTabsHolder.add("JIDE Common Layer License", getInfoPane(new Scanner(ATContentStudio.class.getResourceAsStream("/LICENSE.JIDE.txt"), "UTF-8").useDelimiter("\\A").next(), "text/text")); editorTabsHolder.add("libtiled-java License", getInfoPane(new Scanner(ATContentStudio.class.getResourceAsStream("/LICENSE.libtiled.txt"), "UTF-8").useDelimiter("\\A").next(), "text/text")); editorTabsHolder.add("prefuse License", getInfoPane(new Scanner(ATContentStudio.class.getResourceAsStream("/license-prefuse.txt"), "UTF-8").useDelimiter("\\A").next(), "text/text")); + editorTabsHolder.add("BeanShell License", getInfoPane(new Scanner(ATContentStudio.class.getResourceAsStream("/LICENSE.LGPLv3.txt"), "UTF-8").useDelimiter("\\A").next(), "text/text")); editorTabsHolder.add("ATCS License", getInfoPane(new Scanner(ATContentStudio.class.getResourceAsStream("/LICENSE.GPLv3.txt"), "UTF-8").useDelimiter("\\A").next(), "text/text")); } diff --git a/src/com/gpl/rpg/atcontentstudio/ui/JSONCreationWizard.java b/src/com/gpl/rpg/atcontentstudio/ui/JSONCreationWizard.java index c41b4f9..47099ca 100644 --- a/src/com/gpl/rpg/atcontentstudio/ui/JSONCreationWizard.java +++ b/src/com/gpl/rpg/atcontentstudio/ui/JSONCreationWizard.java @@ -262,6 +262,7 @@ public class JSONCreationWizard extends JDialog { buttonPane.add(cancel, JideBoxLayout.FIX); ok = new JButton("Ok"); buttonPane.add(ok, JideBoxLayout.FIX); + pane.add(new JPanel(), JideBoxLayout.VARY); pane.add(buttonPane, JideBoxLayout.FIX); ok.addActionListener(new ActionListener() { diff --git a/src/com/gpl/rpg/atcontentstudio/ui/ProjectsTree.java b/src/com/gpl/rpg/atcontentstudio/ui/ProjectsTree.java index 4524009..ba306b7 100644 --- a/src/com/gpl/rpg/atcontentstudio/ui/ProjectsTree.java +++ b/src/com/gpl/rpg/atcontentstudio/ui/ProjectsTree.java @@ -201,6 +201,10 @@ public class ProjectsTree extends JPanel { addNextSeparator = true; popupMenu.add(new JMenuItem(actions.importJSON)); } + if (actions.createWorldmap.isEnabled()) { + addNextSeparator = true; + popupMenu.add(new JMenuItem(actions.createWorldmap)); + } if (actions.loadSave.isEnabled()) { addNextSeparator = true; popupMenu.add(new JMenuItem(actions.loadSave)); @@ -219,6 +223,10 @@ public class ProjectsTree extends JPanel { addNextSeparator = true; popupMenu.add(new JMenuItem(actions.compareNPCs)); } + if (actions.runBeanShell.isEnabled()) { + addNextSeparator = true; + popupMenu.add(new JMenuItem(actions.runBeanShell)); + } if (actions.exportProject.isEnabled()) { addNextSeparator = true; popupMenu.add(new JMenuItem(actions.exportProject)); diff --git a/src/com/gpl/rpg/atcontentstudio/ui/StudioFrame.java b/src/com/gpl/rpg/atcontentstudio/ui/StudioFrame.java index d09045a..55e812a 100644 --- a/src/com/gpl/rpg/atcontentstudio/ui/StudioFrame.java +++ b/src/com/gpl/rpg/atcontentstudio/ui/StudioFrame.java @@ -136,6 +136,7 @@ public class StudioFrame extends JFrame { projectMenu.add(new JSeparator()); projectMenu.add(new JMenuItem(actions.createGDE)); projectMenu.add(new JMenuItem(actions.importJSON)); + projectMenu.add(new JMenuItem(actions.createWorldmap)); projectMenu.add(new JMenuItem(actions.loadSave)); getJMenuBar().add(projectMenu); @@ -143,6 +144,8 @@ public class StudioFrame extends JFrame { toolsMenu.add(new JMenuItem(actions.compareItems)); toolsMenu.add(new JMenuItem(actions.compareNPCs)); toolsMenu.add(new JSeparator()); + toolsMenu.add(new JMenuItem(actions.runBeanShell)); + toolsMenu.add(new JSeparator()); toolsMenu.add(new JMenuItem(actions.exportProject)); getJMenuBar().add(toolsMenu); diff --git a/src/com/gpl/rpg/atcontentstudio/ui/WorkspaceActions.java b/src/com/gpl/rpg/atcontentstudio/ui/WorkspaceActions.java index 3483be4..eb10efe 100644 --- a/src/com/gpl/rpg/atcontentstudio/ui/WorkspaceActions.java +++ b/src/com/gpl/rpg/atcontentstudio/ui/WorkspaceActions.java @@ -19,6 +19,8 @@ import javax.swing.JOptionPane; import javax.swing.KeyStroke; import javax.swing.tree.TreePath; +import bsh.util.JConsole; + import com.gpl.rpg.atcontentstudio.ATContentStudio; import com.gpl.rpg.atcontentstudio.model.ClosedProject; import com.gpl.rpg.atcontentstudio.model.GameDataElement; @@ -32,6 +34,7 @@ import com.gpl.rpg.atcontentstudio.model.gamedata.JSONElement; import com.gpl.rpg.atcontentstudio.model.maps.TMXMap; import com.gpl.rpg.atcontentstudio.model.maps.TMXMapSet; import com.gpl.rpg.atcontentstudio.model.saves.SavedGamesSet; +import com.gpl.rpg.atcontentstudio.ui.tools.BeanShellView; import com.gpl.rpg.atcontentstudio.ui.tools.ItemsTableView; import com.gpl.rpg.atcontentstudio.ui.tools.NPCsTableView; @@ -213,6 +216,17 @@ public class WorkspaceActions { } }; + + public ATCSAction createWorldmap = new ATCSAction("Create Worldmap segment", "Opens the worldmap segment creation wizard") { + public void actionPerformed(ActionEvent e) { + if (selectedNode == null || selectedNode.getProject() == null) return; + new WorldmapCreationWizard(selectedNode.getProject()).setVisible(true); + } + public void selectionChanged(ProjectTreeNode selectedNode, TreePath[] selectedPaths) { + setEnabled(selectedNode != null && selectedNode.getProject() != null); + } + }; + public ATCSAction importJSON = new ATCSAction("Import JSON data", "Opens the JSON import wizard") { public void actionPerformed(ActionEvent e) { if (selectedNode == null || selectedNode.getProject() == null) return; @@ -281,6 +295,12 @@ public class WorkspaceActions { }; }; + public ATCSAction runBeanShell = new ATCSAction("Run Beanshell console", "Opens a beanshell scripting pad."){ + public void actionPerformed(ActionEvent e) { + new BeanShellView(); + }; + }; + public ATCSAction showAbout = new ATCSAction("About...", "Displays credits and other informations about ATCS"){ public void actionPerformed(ActionEvent e) { ATContentStudio.frame.showAbout(); diff --git a/src/com/gpl/rpg/atcontentstudio/ui/WorldmapCreationWizard.java b/src/com/gpl/rpg/atcontentstudio/ui/WorldmapCreationWizard.java new file mode 100644 index 0000000..399bf9e --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/ui/WorldmapCreationWizard.java @@ -0,0 +1,158 @@ +package com.gpl.rpg.atcontentstudio.ui; + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.Toolkit; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.ArrayList; +import java.util.List; + +import javax.swing.JButton; +import javax.swing.JDialog; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextField; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; + +import com.gpl.rpg.atcontentstudio.ATContentStudio; +import com.gpl.rpg.atcontentstudio.model.GameSource; +import com.gpl.rpg.atcontentstudio.model.Project; +import com.gpl.rpg.atcontentstudio.model.maps.WorldmapSegment; +import com.jidesoft.swing.JideBoxLayout; + +public class WorldmapCreationWizard extends JDialog { + + private static final long serialVersionUID = 6491044105090917567L; + + private WorldmapSegment creation = new WorldmapSegment(null, null, null); + + final JLabel message; + final JTextField idField; + final JButton ok; + final Project proj; + + + public WorldmapCreationWizard(final Project proj) { + super(ATContentStudio.frame); + this.proj = proj; + setTitle("Create Worldmap segment"); + + JPanel pane = new JPanel(); + pane.setLayout(new JideBoxLayout(pane, JideBoxLayout.PAGE_AXIS, 6)); + + pane.add(new JLabel("Create a new worldmap segment."), JideBoxLayout.FIX); + + message = new JLabel("Enter a map segment ID below: "); + pane.add(message, JideBoxLayout.FIX); + + final JPanel idPane = new JPanel(); + idPane.setLayout(new BorderLayout()); + JLabel idLabel = new JLabel("Internal ID: "); + idPane.add(idLabel, BorderLayout.WEST); + idField = new JTextField(""); + idField.setEditable(true); + idPane.add(idField, BorderLayout.CENTER); + pane.add(idPane, JideBoxLayout.FIX); + + JPanel buttonPane = new JPanel(); + buttonPane.setLayout(new JideBoxLayout(buttonPane, JideBoxLayout.LINE_AXIS, 6)); + buttonPane.add(new JPanel(), JideBoxLayout.VARY); + JButton cancel = new JButton("Cancel"); + buttonPane.add(cancel, JideBoxLayout.FIX); + ok = new JButton("Ok"); + buttonPane.add(ok, JideBoxLayout.FIX); + pane.add(new JPanel(), JideBoxLayout.VARY); + pane.add(buttonPane, JideBoxLayout.FIX); + + ok.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + creation.id = idField.getText(); + WorldmapCreationWizard.this.setVisible(false); + WorldmapCreationWizard.this.dispose(); + proj.createWorldmapSegment(creation); + notifyCreated(); + ATContentStudio.frame.selectInTree(creation); + ATContentStudio.frame.openEditor(creation); + } + }); + + cancel.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + creation = null; + WorldmapCreationWizard.this.setVisible(false); + WorldmapCreationWizard.this.dispose(); + } + }); + + DocumentListener statusUpdater = new DocumentListener() { + @Override + public void removeUpdate(DocumentEvent e) { + updateStatus(); + } + @Override + public void insertUpdate(DocumentEvent e) { + updateStatus(); + } + @Override + public void changedUpdate(DocumentEvent e) { + updateStatus(); + } + }; + idField.getDocument().addDocumentListener(statusUpdater); + + getContentPane().setLayout(new BorderLayout()); + getContentPane().add(pane, BorderLayout.CENTER); + + setMinimumSize(new Dimension(350,120)); + updateStatus(); + pack(); + + Dimension sdim = Toolkit.getDefaultToolkit().getScreenSize(); + Dimension wdim = getSize(); + setLocation((sdim.width - wdim.width)/2, (sdim.height - wdim.height)/2); + } + + public void updateStatus() { + boolean trouble = false; + message.setText("Looks OK to me."); + if (idField.getText() == null || idField.getText().length() <= 0) { + message.setText("Internal ID must not be empty."); + trouble = true; + } else if (proj.getWorldmapSegment(idField.getText()) != null) { + if (proj.getWorldmapSegment(idField.getText()).getDataType() == GameSource.Type.created) { + message.setText("A worldmap segment with the same ID was already created in this project."); + trouble = true; + } else if (proj.getWorldmapSegment(idField.getText()).getDataType() == GameSource.Type.altered) { + message.setText("A worldmap segment with the same ID exists in the game and is already altered in this project."); + trouble = true; + } else if (proj.getWorldmapSegment(idField.getText()).getDataType() == GameSource.Type.source) { + message.setText("A worldmap segment with the same ID exists in the game. It will be added under \"altered\"."); + } + } + + ok.setEnabled(!trouble); + + message.revalidate(); + message.repaint(); + } + + public static interface CreationCompletedListener { + public void segmentCreated(WorldmapSegment created); + } + + private List listeners = new ArrayList(); + + public void addCreationListener(CreationCompletedListener l) { + listeners.add(l); + } + + public void notifyCreated() { + for (CreationCompletedListener l : listeners) { + l.segmentCreated(creation); + } + } +} diff --git a/src/com/gpl/rpg/atcontentstudio/ui/WorldmapLabelEditionWizard.java b/src/com/gpl/rpg/atcontentstudio/ui/WorldmapLabelEditionWizard.java new file mode 100644 index 0000000..50bcb09 --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/ui/WorldmapLabelEditionWizard.java @@ -0,0 +1,190 @@ +package com.gpl.rpg.atcontentstudio.ui; + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.Toolkit; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.ArrayList; +import java.util.List; + +import javax.swing.JButton; +import javax.swing.JDialog; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextField; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; + +import com.gpl.rpg.atcontentstudio.ATContentStudio; +import com.gpl.rpg.atcontentstudio.model.maps.WorldmapSegment; +import com.jidesoft.swing.JideBoxLayout; + +public class WorldmapLabelEditionWizard extends JDialog { + + private static final long serialVersionUID = 4911946705579386332L; + + final JLabel message; + final JTextField idField; + final JTextField labelField; + final JTextField typeField; + final JButton ok; + final WorldmapSegment segment; + final WorldmapSegment.NamedArea label; + + boolean createMode = false; + + public WorldmapLabelEditionWizard(WorldmapSegment segment) { + this(segment, new WorldmapSegment.NamedArea(null, null, null), true); + } + + public WorldmapLabelEditionWizard(WorldmapSegment segment, WorldmapSegment.NamedArea label) { + this(segment, label, false); + } + + public WorldmapLabelEditionWizard(WorldmapSegment segment, WorldmapSegment.NamedArea namedArea, boolean createMode) { + super(ATContentStudio.frame); + this.createMode = createMode; + this.segment = segment; + this.label = namedArea; + + setTitle(createMode ? "Create Worldmap Label" : "Edit Worldmap Label"); + + JPanel pane = new JPanel(); + pane.setLayout(new JideBoxLayout(pane, JideBoxLayout.PAGE_AXIS, 6)); + + pane.add(new JLabel(createMode ? "Create a worldmap label." : "Edit a worldmap label."), JideBoxLayout.FIX); + + message = new JLabel("Enter a label ID below: "); + pane.add(message, JideBoxLayout.FIX); + + final JPanel idPane = new JPanel(); + idPane.setLayout(new BorderLayout()); + JLabel idLabel = new JLabel("Internal ID: "); + idPane.add(idLabel, BorderLayout.WEST); + idField = new JTextField(label.id); + idField.setEditable(true); + idPane.add(idField, BorderLayout.CENTER); + pane.add(idPane, JideBoxLayout.FIX); + + final JPanel labelPane = new JPanel(); + labelPane.setLayout(new BorderLayout()); + JLabel labelLabel = new JLabel("Label: "); + labelPane.add(labelLabel, BorderLayout.WEST); + labelField = new JTextField(label.name); + labelField.setEditable(true); + labelPane.add(labelField, BorderLayout.CENTER); + pane.add(labelPane, JideBoxLayout.FIX); + + final JPanel typePane = new JPanel(); + typePane.setLayout(new BorderLayout()); + JLabel typeLabel = new JLabel("Type: "); + typePane.add(typeLabel, BorderLayout.WEST); + typeField = new JTextField(label.type); + if (typeField.getText().equals("")) { + typeField.setText("settlement"); + } + typeField.setEditable(true); + typePane.add(typeField, BorderLayout.CENTER); + pane.add(typePane, JideBoxLayout.FIX); + + JPanel buttonPane = new JPanel(); + buttonPane.setLayout(new JideBoxLayout(buttonPane, JideBoxLayout.LINE_AXIS, 6)); + buttonPane.add(new JPanel(), JideBoxLayout.VARY); + JButton cancel = new JButton("Cancel"); + buttonPane.add(cancel, JideBoxLayout.FIX); + ok = new JButton("Ok"); + buttonPane.add(ok, JideBoxLayout.FIX); + pane.add(new JPanel(), JideBoxLayout.VARY); + pane.add(buttonPane, JideBoxLayout.FIX); + + ok.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + label.id = idField.getText(); + label.name = labelField.getText(); + label.type = labelField.getText(); + WorldmapLabelEditionWizard.this.setVisible(false); + WorldmapLabelEditionWizard.this.dispose(); + if (WorldmapLabelEditionWizard.this.createMode) { + WorldmapLabelEditionWizard.this.segment.labels.put(label.id, label); + } + notifyCreated(); + } + }); + + cancel.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + WorldmapLabelEditionWizard.this.setVisible(false); + WorldmapLabelEditionWizard.this.dispose(); + } + }); + + DocumentListener statusUpdater = new DocumentListener() { + @Override + public void removeUpdate(DocumentEvent e) { + updateStatus(); + } + @Override + public void insertUpdate(DocumentEvent e) { + updateStatus(); + } + @Override + public void changedUpdate(DocumentEvent e) { + updateStatus(); + } + }; + idField.getDocument().addDocumentListener(statusUpdater); + labelField.getDocument().addDocumentListener(statusUpdater); + typeField.getDocument().addDocumentListener(statusUpdater); + + getContentPane().setLayout(new BorderLayout()); + getContentPane().add(pane, BorderLayout.CENTER); + + setMinimumSize(new Dimension(350,170)); + updateStatus(); + pack(); + + Dimension sdim = Toolkit.getDefaultToolkit().getScreenSize(); + Dimension wdim = getSize(); + setLocation((sdim.width - wdim.width)/2, (sdim.height - wdim.height)/2); + } + + public void updateStatus() { + boolean trouble = false; + message.setText("Looks OK to me."); + if (idField.getText() == null || idField.getText().length() <= 0) { + message.setText("Internal ID must not be empty."); + trouble = true; + } else if (segment.labels.get(idField.getText()) != null && segment.labels.get(idField.getText()) != label) { + message.setText("A worldmap label with the same ID already exists in this worldmap."); + trouble = true; + } else if (labelField.getText() == null || labelField.getText().length() <= 0) { + message.setText("Label must not be empty."); + trouble = true; + } +// message.setText("This is a Warning example"); + + ok.setEnabled(!trouble); + + message.revalidate(); + message.repaint(); + } + + public static interface CreationCompletedListener { + public void labelCreated(WorldmapSegment.NamedArea created); + } + + private List listeners = new ArrayList(); + + public void addCreationListener(CreationCompletedListener l) { + listeners.add(l); + } + + public void notifyCreated() { + for (CreationCompletedListener l : listeners) { + l.labelCreated(label); + } + } +} diff --git a/src/com/gpl/rpg/atcontentstudio/ui/map/WorldMapEditor.java b/src/com/gpl/rpg/atcontentstudio/ui/map/WorldMapEditor.java index 83f1d83..7045398 100644 --- a/src/com/gpl/rpg/atcontentstudio/ui/map/WorldMapEditor.java +++ b/src/com/gpl/rpg/atcontentstudio/ui/map/WorldMapEditor.java @@ -5,9 +5,12 @@ import java.awt.Point; import java.awt.Rectangle; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.util.ArrayList; +import java.util.LinkedList; import java.util.List; import javax.swing.ButtonGroup; @@ -30,9 +33,11 @@ import com.gpl.rpg.atcontentstudio.model.SaveEvent; import com.gpl.rpg.atcontentstudio.model.maps.TMXMap; import com.gpl.rpg.atcontentstudio.model.maps.Worldmap; import com.gpl.rpg.atcontentstudio.model.maps.WorldmapSegment; +import com.gpl.rpg.atcontentstudio.model.maps.WorldmapSegment.NamedArea; import com.gpl.rpg.atcontentstudio.ui.DefaultIcons; import com.gpl.rpg.atcontentstudio.ui.Editor; import com.gpl.rpg.atcontentstudio.ui.SaveItemsWizard; +import com.gpl.rpg.atcontentstudio.ui.WorldmapLabelEditionWizard; import com.jidesoft.swing.ComboBoxSearchable; import com.jidesoft.swing.JideBoxLayout; import com.jidesoft.swing.JideTabbedPane; @@ -47,10 +52,12 @@ public class WorldMapEditor extends Editor { moveViewSelect, moveMaps, deleteMaps, - addMap + addMap, + editLabelCoverage } public String mapBeingAddedID = null; + public String selectedLabel = null; public WorldMapEditor(WorldmapSegment worldmap) { target = worldmap; @@ -94,6 +101,11 @@ public class WorldMapEditor extends Editor { zoomSliderPane.add(zoomSlider, JideBoxLayout.VARY); zoomSliderPane.add(new JLabel(new ImageIcon(DefaultIcons.getZoomIcon())), JideBoxLayout.FIX); + final JRadioButton editLabelCoverage = new JRadioButton("Edit label coverage"); + final JButton editLabel = new JButton("Edit map label"); + final JButton createLabel = new JButton("Create map label"); + final JButton deleteLabel = new JButton("Delete map label"); + if (target.writable) { JPanel mapToolsPane = new JPanel(); mapToolsPane.setLayout(new JideBoxLayout(mapToolsPane, JideBoxLayout.LINE_AXIS)); @@ -136,7 +148,23 @@ public class WorldMapEditor extends Editor { mapToolsPane.add(new JPanel(), JideBoxLayout.VARY); moveView.setSelected(true); pane.add(mapToolsPane, JideBoxLayout.FIX); - + + JPanel labelToolsPane = new JPanel(); + labelToolsPane.setLayout(new JideBoxLayout(labelToolsPane, JideBoxLayout.LINE_AXIS)); + mapToolsGroup.add(editLabelCoverage); + editLabelCoverage.setEnabled(false); + labelToolsPane.add(editLabelCoverage, JideBoxLayout.FIX); + editLabel.setEnabled(false); + labelToolsPane.add(editLabel, JideBoxLayout.FIX); + deleteLabel.setEnabled(false); + labelToolsPane.add(deleteLabel, JideBoxLayout.FIX); + createLabel.setEnabled(false); + labelToolsPane.add(createLabel, JideBoxLayout.FIX); + + labelToolsPane.add(new JPanel(), JideBoxLayout.VARY); + pane.add(labelToolsPane, JideBoxLayout.FIX); + + moveView.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { @@ -196,10 +224,114 @@ public class WorldMapEditor extends Editor { if (mapBox.getSelectedItem() == null) { mapBeingAddedID = null; } else { + if (mapBeingAddedID != null) { + mapView.updateFromModel(); + } mapBeingAddedID = ((TMXMap)mapBox.getSelectedItem()).id; + if (mapView.mapLocations.isEmpty()) { + TMXMap map = target.getProject().getMap(mapBeingAddedID); + int w = map.tmxMap.getWidth() * WorldMapView.TILE_SIZE; + int h = map.tmxMap.getHeight() * WorldMapView.TILE_SIZE; + mapView.mapLocations.put(mapBeingAddedID, new Rectangle(0, 0, w, h)); + mapView.recomputeSize(); + mapView.revalidate(); + mapView.repaint(); + } } } }); + + editLabelCoverage.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + editMode = EditMode.editLabelCoverage; + mapBox.setEnabled(false); + mapView.selected.clear(); + mapView.selected.addAll(((WorldmapSegment)target).labelledMaps.get(selectedLabel)); + if (mapBeingAddedID != null) { + mapView.mapLocations.remove(mapBeingAddedID); + mapBeingAddedID = null; + } + mapView.revalidate(); + mapView.repaint(); + } + }); + + editLabelCoverage.addItemListener(new ItemListener() { + @Override + public void itemStateChanged(ItemEvent e) { + if (e.getStateChange() == ItemEvent.DESELECTED) { + WorldmapSegment map = (WorldmapSegment)target; + if (map.labelledMaps.get(selectedLabel) != null) { + map.labelledMaps.get(selectedLabel).clear(); + } else { + map.labelledMaps.put(selectedLabel, new LinkedList()); + } + for (String s : mapView.selected) { + map.labelledMaps.get(selectedLabel).add(s); + } + mapView.revalidate(); + mapView.repaint(); + } + } + }); + + editLabel.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + mapView.selected.clear(); + mapView.selected.addAll(((WorldmapSegment)target).labelledMaps.get(selectedLabel)); + mapView.revalidate(); + mapView.repaint(); + WorldmapLabelEditionWizard wiz = new WorldmapLabelEditionWizard(worldmap, worldmap.labels.get(selectedLabel)); + wiz.addCreationListener(new WorldmapLabelEditionWizard.CreationCompletedListener() { + @Override + public void labelCreated(NamedArea created) { + if (!created.id.equals(selectedLabel)) { + worldmap.labelledMaps.put(created.id, worldmap.labelledMaps.get(selectedLabel)); + worldmap.labelledMaps.remove(selectedLabel); + worldmap.labels.put(created.id, created); + worldmap.labels.remove(selectedLabel); + selectedLabel = created.id; + mapView.revalidate(); + mapView.repaint(); + } + } + }); + wiz.setVisible(true); + } + }); + + deleteLabel.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + worldmap.labelledMaps.remove(selectedLabel); + worldmap.labels.remove(selectedLabel); + selectedLabel = null; + mapView.revalidate(); + mapView.repaint(); + } + }); + + createLabel.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + WorldmapLabelEditionWizard wiz = new WorldmapLabelEditionWizard(worldmap); + wiz.addCreationListener(new WorldmapLabelEditionWizard.CreationCompletedListener() { + @Override + public void labelCreated(NamedArea created) { + worldmap.labelledMaps.put(created.id, new LinkedList()); + worldmap.labelledMaps.get(created.id).addAll(mapView.selected); + mapView.revalidate(); + mapView.repaint(); + } + }); + wiz.setVisible(true); + } + }); + + + } @@ -247,20 +379,25 @@ public class WorldMapEditor extends Editor { break; } } - if (editMode == EditMode.moveViewSelect) { + if (editMode == EditMode.moveViewSelect || editMode == EditMode.editLabelCoverage) { if (selectedMap == null) return; if (e.getButton() == MouseEvent.BUTTON1) { if (e.isControlDown() || e.isShiftDown()) { if (mapView.selected.contains(selectedMap)) { - mapView.selected.remove(selectedMap); - update = true; + if (editMode != EditMode.editLabelCoverage || mapView.selected.size() > 1) { + mapView.selected.remove(selectedMap); + mapSelectionChanged(); + update = true; + } } else { mapView.selected.add(selectedMap); + mapSelectionChanged(); update = true; } } else { mapView.selected.clear(); mapView.selected.add(selectedMap); + mapSelectionChanged(); update = true; } if (e.getClickCount() == 2) { @@ -269,8 +406,9 @@ public class WorldMapEditor extends Editor { } } else if (editMode == EditMode.deleteMaps) { worldmap.mapLocations.remove(selectedMap); - worldmap.labelLocations.remove(selectedMap); + worldmap.labels.remove(selectedMap); mapView.selected.remove(selectedMap); + mapSelectionChanged(); mapView.updateFromModel(); update = true; } else if (editMode == EditMode.addMap && mapBeingAddedID != null) { @@ -360,6 +498,47 @@ public class WorldMapEditor extends Editor { mapView.repaint(); } } + + public void mapSelectionChanged() { + if (mapView.selected.isEmpty()) { + editLabelCoverage.setEnabled(false); + editLabel.setEnabled(false); + createLabel.setEnabled(false); + selectedLabel = null; + } else { + String label = null; + boolean multiLabel = false; + for (String map : mapView.selected) { + for (String existingLabel : ((WorldmapSegment)target).labelledMaps.keySet()) { + if (((WorldmapSegment)target).labelledMaps.get(existingLabel).contains(map)) { + if (label != null && !label.equals(existingLabel)) { + multiLabel = true; + } + label = existingLabel; + } + } + } + if (multiLabel) { + editLabelCoverage.setEnabled(false); + editLabel.setEnabled(false); + createLabel.setEnabled(false); + deleteLabel.setEnabled(false); + selectedLabel = null; + } else if (label != null) { + editLabelCoverage.setEnabled(true); + editLabel.setEnabled(true); + deleteLabel.setEnabled(true); + createLabel.setEnabled(false); + selectedLabel = label; + } else { + editLabelCoverage.setEnabled(false); + editLabel.setEnabled(false); + deleteLabel.setEnabled(false); + createLabel.setEnabled(true); + selectedLabel = null; + } + } + } }; mapView.addMouseListener(mouseListener); diff --git a/src/com/gpl/rpg/atcontentstudio/ui/map/WorldMapView.java b/src/com/gpl/rpg/atcontentstudio/ui/map/WorldMapView.java index 2d34b36..2bcf3de 100644 --- a/src/com/gpl/rpg/atcontentstudio/ui/map/WorldMapView.java +++ b/src/com/gpl/rpg/atcontentstudio/ui/map/WorldMapView.java @@ -3,10 +3,16 @@ package com.gpl.rpg.atcontentstudio.ui.map; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Dimension; +import java.awt.Font; +import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Point; import java.awt.Rectangle; +import java.awt.RenderingHints; +import java.awt.font.FontRenderContext; +import java.awt.font.GlyphVector; +import java.awt.geom.Rectangle2D; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; @@ -87,6 +93,30 @@ public class WorldMapView extends JComponent implements Scrollable { g2.translate(-x, -y); } + + + Font f = g2.getFont(); + f = f.deriveFont(70f).deriveFont(Font.BOLD); + g2.setFont(f); + g2.setStroke(new BasicStroke(3)); + g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + FontMetrics fm = g2.getFontMetrics(); + FontRenderContext frc = g2.getFontRenderContext(); + + for (String s : worldmap.labels.keySet()) { + String label = worldmap.labels.get(s).name; + Rectangle areaCovered = new Rectangle(0, 0, -1, -1); + for (String map : worldmap.labelledMaps.get(s)) { + areaCovered.add(mapLocations.get(map)); + } + + Rectangle2D stringBounds = fm.getStringBounds(label, g2); + GlyphVector gv = f.createGlyphVector(frc, label); + g2.setColor(Color.WHITE); + g2.fill(gv.getOutline((int)(areaCovered.getCenterX() - stringBounds.getCenterX()), (int)(areaCovered.getCenterY() - stringBounds.getCenterY()))); + g2.setColor(Color.BLACK); + g2.draw(gv.getOutline((int)(areaCovered.getCenterX() - stringBounds.getCenterX()), (int)(areaCovered.getCenterY() - stringBounds.getCenterY()))); + } } @Override @@ -202,6 +232,8 @@ public class WorldMapView extends JComponent implements Scrollable { return originMoved; } + + public void updateFromModel() { mapLocations.clear(); sizeX = sizeY = 0; @@ -232,13 +264,13 @@ public class WorldMapView extends JComponent implements Scrollable { } List toRemove = new ArrayList(); - for (String s : worldmap.labelLocations.keySet()) { + for (String s : worldmap.labels.keySet()) { if (!mapLocations.containsKey(s)) { toRemove.add(s); } } for (String s : toRemove) { - worldmap.labelLocations.remove(s); + worldmap.labels.remove(s); } }