Gridarta Editor
MapMenu.java
Go to the documentation of this file.
1 /*
2  * Gridarta MMORPG map editor for Crossfire, Daimonin and similar games.
3  * Copyright (C) 2000-2015 The Gridarta Developers.
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License along
16  * with this program; if not, write to the Free Software Foundation, Inc.,
17  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18  */
19 
20 package net.sf.gridarta.gui.mapmenu;
21 
22 import java.io.IOException;
23 import java.util.ArrayDeque;
24 import java.util.Deque;
25 import javax.swing.JTree;
26 import javax.swing.event.TreeModelEvent;
27 import javax.swing.event.TreeModelListener;
28 import javax.swing.tree.DefaultMutableTreeNode;
29 import javax.swing.tree.DefaultTreeModel;
30 import javax.swing.tree.MutableTreeNode;
31 import javax.swing.tree.TreeNode;
32 import javax.swing.tree.TreePath;
35 import org.apache.log4j.Category;
36 import org.apache.log4j.Logger;
37 import org.jetbrains.annotations.NotNull;
38 import org.jetbrains.annotations.Nullable;
39 
44 public class MapMenu {
45 
49  @NotNull
50  private static final Category LOG = Logger.getLogger(MapMenu.class);
51 
55  @NotNull
57 
61  @NotNull
62  private final DefaultMutableTreeNode root = new DefaultMutableTreeNode(new MapMenuEntryDir("Bookmarks"), true);
63 
67  @NotNull
68  private final DefaultTreeModel treeModel = new DefaultTreeModel(root);
69 
73  @NotNull
74  private final Deque<DeletedNode> deletedNodes = new ArrayDeque<>();
75 
79  private boolean treeModelModified;
80 
85  @NotNull
86  private final TreeModelListener treeModelListener = new TreeModelListener() {
87 
88  @Override
89  public void treeNodesChanged(final TreeModelEvent e) {
90  treeModelModified = true;
91  }
92 
93  @Override
94  public void treeNodesInserted(final TreeModelEvent e) {
95  treeModelModified = true;
96  }
97 
98  @Override
99  public void treeNodesRemoved(final TreeModelEvent e) {
100  treeModelModified = true;
101  }
102 
103  @Override
104  public void treeStructureChanged(final TreeModelEvent e) {
105  treeModelModified = true;
106  }
107 
108  };
109 
115  public MapMenu(@NotNull final String key, @NotNull final PathManager pathManager) {
116  if (LOG.isDebugEnabled()) {
117  //noinspection ThisEscapedInObjectConstruction
118  LOG.debug(System.identityHashCode(this) + " new " + key);
119  }
120  mapMenuLoader = new MapMenuLoader(key, pathManager);
121  treeModel.addTreeModelListener(treeModelListener);
122  }
123 
127  public void load() {
128  final int num = mapMenuLoader.loadNumEntries();
129  if (LOG.isDebugEnabled()) {
130  LOG.debug(System.identityHashCode(this) + " load: root.removeAllChildren");
131  }
132  root.removeAllChildren();
133  for (int i = 0; i < num; i++) {
134  final MapMenuLoader.Result result;
135  try {
136  result = mapMenuLoader.loadEntry(i);
137  } catch (final IOException ex) {
138  LOG.warn("dropping invalid bookmark: " + ex.getMessage());
139  continue;
140  }
141  addMapMenuEntry(result.getDirectory(), result.getMapMenuEntry());
142  }
143  treeModelModified = false;
144  }
145 
149  public void save() {
150  if (!treeModelModified) {
151  return;
152  }
153  treeModelModified = false;
154 
155  saveAlways();
156  }
157 
161  public void saveAlways() {
162  final int prevNum = mapMenuLoader.loadNumEntries();
163  final int num = saveEntries(root, 0, "");
164  mapMenuLoader.saveNumEntries(num);
165  for (int i = num; i < prevNum; i++) {
166  mapMenuLoader.removeEntry(i);
167  }
168  }
169 
177  private int saveEntries(@NotNull final DefaultMutableTreeNode treeNode, final int startIndex, @NotNull final String directory) {
178  final int[] index = { startIndex };
179  for (int i = 0; i < treeModel.getChildCount(treeNode); i++) {
180  final DefaultMutableTreeNode childTreeNode = (DefaultMutableTreeNode) treeModel.getChild(treeNode, i);
181  final MapMenuEntry childMapMenuEntry = (MapMenuEntry) childTreeNode.getUserObject();
182  final String title = childMapMenuEntry.getTitle();
183  final MapMenuEntryVisitor mapMenuEntryVisitor = new MapMenuEntryVisitor() {
184 
185  @Override
186  public void visit(@NotNull final MapMenuEntryDir mapMenuEntry) {
187  mapMenuLoader.saveEntry(index[0]++, childMapMenuEntry.getTitle(), "", directory, MapMenuLoader.Type.DIR);
188  index[0] = saveEntries(childTreeNode, index[0], directory.isEmpty() ? title : directory + "/" + title);
189  }
190 
191  @Override
192  public void visit(@NotNull final MapMenuEntryMap mapMenuEntry) {
193  final MapMenuEntryMap mapMenuEntryMap = (MapMenuEntryMap) childMapMenuEntry;
194  mapMenuLoader.saveEntry(index[0]++, title, mapMenuEntryMap.getMapFile().getFile().getPath(), directory, MapMenuLoader.Type.MAP);
195  }
196 
197  };
198  childMapMenuEntry.visit(mapMenuEntryVisitor);
199  }
200  return index[0];
201  }
202 
209  public void addMapMenuEntry(@NotNull final String directory, @NotNull final MapMenuEntry mapMenuEntry) {
210  addMapMenuEntry(directory, new DefaultMutableTreeNode(mapMenuEntry, mapMenuEntry.allowsChildren()));
211  }
212 
220  @NotNull
221  public TreePath addMapMenuEntry(@NotNull final String directory, @NotNull final DefaultMutableTreeNode treeNode) {
222  if (LOG.isDebugEnabled()) {
223  LOG.debug(System.identityHashCode(this) + " addMapMenuEntry(" + directory + ", " + treeNode + ")");
224  }
225  final String[] paths = StringUtils.PATTERN_SLASH.split(directory);
226  DefaultMutableTreeNode dir2 = root;
227  for (final String path : paths) {
228  if (!path.isEmpty()) {
229  dir2 = getOrCreateDirectory(dir2, path);
230  }
231  }
232  treeModel.insertNodeInto(treeNode, dir2, dir2.getChildCount());
233  return new TreePath(treeModel.getPathToRoot(treeNode));
234  }
235 
243  @NotNull
244  public DefaultMutableTreeNode getOrCreateDirectory(@NotNull final MutableTreeNode this2, @NotNull final String path) {
245  if (!MapMenuEntryDir.isValidDirectory(path)) {
246  if (LOG.isDebugEnabled()) {
247  LOG.debug(System.identityHashCode(this) + " getOrCreateDirectory(" + this2 + ", " + path + ")=invalid directory name");
248  }
249  throw new IllegalArgumentException("invalid directory name '" + path + "'");
250  }
251 
252  for (int i = this2.getChildCount() - 1; i >= 0; i--) {
253  final DefaultMutableTreeNode treeNode = (DefaultMutableTreeNode) this2.getChildAt(i);
254  final MapMenuEntry mapMenuEntry = (MapMenuEntry) treeNode.getUserObject();
255  if (mapMenuEntry.allowsChildren() && mapMenuEntry.getTitle().equals(path)) {
256  if (LOG.isDebugEnabled()) {
257  LOG.debug(System.identityHashCode(this) + " getOrCreateDirectory(" + this2 + ", " + path + ")=existing " + treeNode);
258  }
259  return treeNode;
260  }
261  }
262 
263  final DefaultMutableTreeNode new2 = new DefaultMutableTreeNode(new MapMenuEntryDir(path), true);
264  treeModel.insertNodeInto(new2, this2, this2.getChildCount());
265  if (LOG.isDebugEnabled()) {
266  LOG.debug(System.identityHashCode(this) + " getOrCreateDirectory(" + this2 + ", " + path + ")=new " + new2);
267  }
268  return new2;
269  }
270 
279  @NotNull
280  public TreePath insertNodeInto(@NotNull final MapMenuEntry mapEntry, @NotNull final DefaultMutableTreeNode parent, final int index) {
281  if (LOG.isDebugEnabled()) {
282  LOG.debug(System.identityHashCode(this) + " insertNodeInto(" + mapEntry + ", " + parent + ", " + index + ")");
283  }
284  final DefaultMutableTreeNode treeNode = new DefaultMutableTreeNode(mapEntry, mapEntry.allowsChildren());
285  treeModel.insertNodeInto(treeNode, parent, index);
286  save();
287  return new TreePath(treeModel.getPathToRoot(treeNode));
288  }
289 
295  public void removeNode(@NotNull final DefaultMutableTreeNode treeNode) {
296  if (treeNode != root) {
297  if (LOG.isDebugEnabled()) {
298  LOG.debug(System.identityHashCode(this) + " removeNode " + treeNode);
299  }
300  final String directory = getDirectory(treeNode);
301  treeModel.removeNodeFromParent(treeNode);
302  deletedNodes.addFirst(new DeletedNode(directory, treeNode));
303  while (deletedNodes.size() > 10) {
304  deletedNodes.removeLast();
305  }
306  save();
307  } else {
308  if (LOG.isDebugEnabled()) {
309  LOG.debug(System.identityHashCode(this) + " removeNode: not removing root " + treeNode);
310  }
311  }
312  }
313 
319  @NotNull
320  private String getDirectory(@NotNull final TreeNode treeNode) {
321  final TreeNode[] treePath = treeModel.getPathToRoot(treeNode);
322  if (treePath == null) {
323  throw new IllegalArgumentException();
324  }
325  final StringBuilder sb = new StringBuilder();
326  for (int i = 1; i + 1 < treePath.length; i++) {
327  final TreeNode tmp = treePath[i];
328  final MapMenuEntry mapMenuEntry = (MapMenuEntry) ((DefaultMutableTreeNode) tmp).getUserObject();
329  if (sb.length() > 0) {
330  sb.append('/');
331  }
332  sb.append(mapMenuEntry.getTitle());
333  }
334  return sb.toString();
335  }
336 
342  @Nullable
343  public DeletedNode getDeletedNode(final boolean delete) {
344  return delete ? deletedNodes.pollFirst() : deletedNodes.peekFirst();
345  }
346 
351  public int size() {
352  return size(root);
353  }
354 
360  private int size(@NotNull final TreeNode root) {
361  final int childCount = treeModel.getChildCount(root);
362  int result = childCount;
363  for (int i = 0; i < childCount; i++) {
364  final DefaultMutableTreeNode childTreeNode = (DefaultMutableTreeNode) treeModel.getChild(root, i);
365  final MapMenuEntry mapMenuEntry = (MapMenuEntry) childTreeNode.getUserObject();
366  if (mapMenuEntry.allowsChildren()) {
367  result += size(childTreeNode);
368  }
369  }
370  if (LOG.isDebugEnabled()) {
371  LOG.debug(System.identityHashCode(this) + " size(" + root + ")=" + result);
372  }
373  return result;
374  }
375 
380  @NotNull
381  public JTree newTree() {
382  return new AutoscrollJTree(treeModel);
383  }
384 
389  @NotNull
390  public DefaultMutableTreeNode getRoot() {
391  return root;
392  }
393 
399  public static class DeletedNode {
400 
404  @NotNull
405  private final String directory;
406 
410  @NotNull
411  private final DefaultMutableTreeNode treeNode;
412 
418  private DeletedNode(@NotNull final String directory, @NotNull final DefaultMutableTreeNode treeNode) {
419  this.directory = directory;
420  this.treeNode = treeNode;
421  }
422 
427  @NotNull
428  public String getDirectory() {
429  return directory;
430  }
431 
436  @NotNull
437  public DefaultMutableTreeNode getTreeNode() {
438  return treeNode;
439  }
440 
441  }
442 
443 }
DeletedNode(@NotNull final String directory, @NotNull final DefaultMutableTreeNode treeNode)
Creates a new instance.
Definition: MapMenu.java:418
void saveEntry(final int index, @NotNull final String title, @NotNull final String filename, @NotNull final String directory, @NotNull final Type type)
Saves an entry to preferences.
final String directory
The entry&#39;s directory.
Definition: MapMenu.java:405
Utility class for string manipulation.
final TreeModelListener treeModelListener
The TreeModelListener attached to treeModel for detecting modifications.
Definition: MapMenu.java:86
This class contains methods for converting relative map paths to absolute map paths and vice versa...
A MapMenuEntry that represents a directory.
Reading and writing of maps, handling of paths.
String getTitle()
Returns the entry&#39;s title.
int size()
Returns the number of entries in this menu.
Definition: MapMenu.java:351
int loadNumEntries()
Returns the number of entries present in the preferences.
DeletedNode getDeletedNode(final boolean delete)
Returns the last deleted node.
Definition: MapMenu.java:343
void load()
Loads the contents from preferences.
Definition: MapMenu.java:127
static boolean isValidDirectory(@NotNull final String title)
Returns whether a title is valid.
abstract boolean allowsChildren()
Returns whether this entry is a directory.
Manages the contents of a recent or bookmark menu.
Definition: MapMenu.java:44
String getDirectory()
Returns the entry&#39;s directory.
Definition: MapMenu.java:428
MAP
Preferences value for entries representing map files.
Result loadEntry(final int index)
Loads an entry from preferences.
Result value consisting of a MapMenuEntry and its location (directory).
void saveAlways()
Saves the contents to preferences.
Definition: MapMenu.java:161
Base package of all Gridarta classes.
DefaultMutableTreeNode getRoot()
Returns the root node.
Definition: MapMenu.java:390
JTree newTree()
Creates a new JTree for this map menu.
Definition: MapMenu.java:381
Saves or restores MapMenu contents to Preferences.
abstract void visit(@NotNull MapMenuEntryVisitor visitor)
Calls the.
A JTree that supports auto-scrolling while drag and drop is active.
void saveNumEntries(final int num)
Sets the number of entries present in the preferences.
void save()
Saves the contents to preferences if modified since last save.
Definition: MapMenu.java:149
boolean treeModelModified
Whether treeModel has been modified since last save.
Definition: MapMenu.java:79
TreePath addMapMenuEntry(@NotNull final String directory, @NotNull final DefaultMutableTreeNode treeNode)
Adds a DefaultMutableTreeNode to this menu.
Definition: MapMenu.java:221
MapFile getMapFile()
Returns the map file.
String getDirectory(@NotNull final TreeNode treeNode)
Returns the directory of a TreeNode.
Definition: MapMenu.java:320
DefaultMutableTreeNode getOrCreateDirectory(@NotNull final MutableTreeNode this2, @NotNull final String path)
Returns the MapMenuEntryDir for a given path.
Definition: MapMenu.java:244
final MapMenuLoader mapMenuLoader
The MapMenuLoader for loading/storing the menu contents.
Definition: MapMenu.java:56
final Deque< DeletedNode > deletedNodes
Recently deleted nodes.
Definition: MapMenu.java:74
static final Category LOG
The Logger for printing log messages.
Definition: MapMenu.java:50
int saveEntries(@NotNull final DefaultMutableTreeNode treeNode, final int startIndex, @NotNull final String directory)
Saves a TreeNode instance&#39;s contents to preferences.
Definition: MapMenu.java:177
void addMapMenuEntry(@NotNull final String directory, @NotNull final MapMenuEntry mapMenuEntry)
Adds a MapMenuEntry to this menu.
Definition: MapMenu.java:209
Result value consisting of a TreeNode and its location (directory).
Definition: MapMenu.java:399
TreePath insertNodeInto(@NotNull final MapMenuEntry mapEntry, @NotNull final DefaultMutableTreeNode parent, final int index)
Inserts a new node into the tree.
Definition: MapMenu.java:280
Abstract base class for recent and bookmark menu entries.
final DefaultTreeModel treeModel
The DefaultTreeModel that contains all menu entries.
Definition: MapMenu.java:68
DIR
Preferences value for entries representing directories.
A MapMenuEntry that represents a map.
File getFile()
Returns a File for this map file.
Definition: MapFile.java:102
DefaultMutableTreeNode getTreeNode()
Returns the tree node.
Definition: MapMenu.java:437
void removeEntry(final int index)
Removes an entry.
final DefaultMutableTreeNode treeNode
The entry.
Definition: MapMenu.java:411
void removeNode(@NotNull final DefaultMutableTreeNode treeNode)
Removes a node from the tree.
Definition: MapMenu.java:295
int size(@NotNull final TreeNode root)
Returns the number of entries in a sub-tree.
Definition: MapMenu.java:360
final DefaultMutableTreeNode root
The root node of treeModel.
Definition: MapMenu.java:62
MapMenu(@NotNull final String key, @NotNull final PathManager pathManager)
Creates a new instance.
Definition: MapMenu.java:115
Interface for classes operating on MapMenuEntry instances.
static final Pattern PATTERN_SLASH
The pattern that matches a single slash ("/").