Gridarta Editor
UndoControl.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.undo;
21 
22 import java.util.IdentityHashMap;
23 import java.util.Map;
24 import javax.swing.Action;
41 import net.sf.japi.swing.action.ActionBuilder;
42 import net.sf.japi.swing.action.ActionBuilderFactory;
43 import net.sf.japi.swing.action.ActionMethod;
44 import org.jetbrains.annotations.NotNull;
45 import org.jetbrains.annotations.Nullable;
46 
51 public class UndoControl<G extends GameObject<G, A, R>, A extends MapArchObject<A>, R extends Archetype<G, A, R>> implements EditorAction {
52 
56  @NotNull
57  private static final ActionBuilder ACTION_BUILDER = ActionBuilderFactory.getInstance().getActionBuilder("net.sf.gridarta");
58 
62  @NotNull
63  private final Map<MapModel<G, A, R>, UndoModel<G, A, R>> undoModels = new IdentityHashMap<>();
64 
68  @Nullable
70 
74  private int maxUndoStates = 100;
75 
79  @Nullable
80  private Action aUndo;
81 
85  @Nullable
86  private Action aRedo;
87 
94  public UndoControl(@NotNull final MapManager<G, A, R> mapManager, @NotNull final GameObjectFactory<G, A, R> gameObjectFactory, @NotNull final GameObjectMatchers gameObjectMatchers) {
95  final MapManagerListener<G, A, R> mapManagerListener = new MapManagerListener<G, A, R>() {
96 
97  @Override
98  public void currentMapChanged(@Nullable final MapControl<G, A, R> mapControl) {
99  mapModel = mapControl == null ? null : mapControl.getMapModel();
100  refreshMenus();
101  }
102 
103  @Override
104  public void mapCreated(@NotNull final MapControl<G, A, R> mapControl, final boolean interactive) {
105  final MapModel<G, A, R> mapModel = mapControl.getMapModel();
106  final MapTransactionListener<G, A, R> mapTransactionListener = new MapTransactionListener<G, A, R>() {
107 
116  @Nullable
117  private UndoState<G, A, R> undoState;
118 
123  @Nullable
124  private UndoModel<G, A, R> undoModel;
125 
126  @Override
127  public void preBeginTransaction() {
128  // ignore
129  }
130 
131  @Override
132  public void beginTransaction(@NotNull final String name) {
133  undoModel = undoModels.get(mapModel);
134  undoState = new UndoState<>(name, mapModel.getMapArchObject().createClone());
135  }
136 
137  @Override
138  public void endTransaction(@NotNull final SavedSquares<G, A, R> savedSquares) {
139  final UndoState<G, A, R> savedUndoState = undoState;
140  assert savedUndoState != null;
141  final UndoModel<G, A, R> savedUndoModel = undoModel;
142  assert savedUndoModel != null;
143  if (savedSquares.isEmpty() && savedUndoState.getMapArchObject().equals(mapModel.getMapArchObject())) {
144  return;
145  }
146 
147  final SavedSquares<G, A, R> clonedSavedSquares = savedSquares.cloneAndClear();
148  clonedSavedSquares.removeEmptySquares(savedUndoState.getMapArchObject().getMapSize());
149  savedUndoState.setSavedSquares(clonedSavedSquares);
150  savedUndoModel.finish(savedUndoState);
151  if (maxUndoStates > 0) {
152  savedUndoModel.trimToSize(maxUndoStates);
153  }
154 
155  refreshMenus();
156  }
157 
158  @Override
159  public void postEndTransaction() {
160  // ignore
161  }
162 
163  };
164 
165  mapModel.addMapTransactionListener(mapTransactionListener);
166  undoModels.put(mapModel, new UndoModel<>(gameObjectFactory, gameObjectMatchers, mapTransactionListener));
167  }
168 
169  @Override
170  public void mapClosing(@NotNull final MapControl<G, A, R> mapControl) {
171  // ignore
172  }
173 
174  @Override
175  public void mapClosed(@NotNull final MapControl<G, A, R> mapControl) {
176  final UndoModel<G, A, R> undoModel = undoModels.remove(mapControl.getMapModel());
177  assert undoModel != null;
178  mapControl.getMapModel().removeMapTransactionListener(undoModel.getMapTransactionListener());
179  }
180 
181  };
182  mapManager.addMapManagerListener(mapManagerListener);
183 
184  refreshMenus();
185  }
186 
190  private void refreshMenus() {
191  if (aUndo != null) {
192  final boolean undoEnabled = doUndo(false);
193  //noinspection ConstantConditions
194  aUndo.setEnabled(undoEnabled);
195  if (undoEnabled) {
196  final UndoModel<G, A, R> undoModel = undoModels.get(mapModel);
197  assert undoModel != null;
198  //noinspection ConstantConditions
199  aUndo.putValue(Action.NAME, ACTION_BUILDER.format("undo.name", undoModel.undoName()));
200  } else {
201  //noinspection ConstantConditions
202  aUndo.putValue(Action.NAME, ActionBuilderUtils.getString(ACTION_BUILDER, "undo.text"));
203  }
204  }
205 
206  if (aRedo != null) {
207  final boolean redoEnabled = doRedo(false);
208  //noinspection ConstantConditions
209  aRedo.setEnabled(redoEnabled);
210  if (redoEnabled) {
211  final UndoModel<G, A, R> redoModel = undoModels.get(mapModel);
212  assert redoModel != null;
213  //noinspection ConstantConditions
214  aRedo.putValue(Action.NAME, ACTION_BUILDER.format("redo.name", redoModel.redoName()));
215  } else {
216  //noinspection ConstantConditions
217  aRedo.putValue(Action.NAME, ActionBuilderUtils.getString(ACTION_BUILDER, "redo.text"));
218  }
219  }
220  }
221 
225  @ActionMethod
226  public void undo() {
227  doUndo(true);
228  }
229 
233  @ActionMethod
234  public void redo() {
235  doRedo(true);
236  }
237 
243  public int getMaxUndoStates() {
244  return maxUndoStates;
245  }
246 
252  public void setMaxUndoStates(final int maxUndoStates) {
253  this.maxUndoStates = maxUndoStates;
254 
255  if (maxUndoStates > 0) {
256  for (final UndoModel<G, A, R> undoModel : undoModels.values()) {
257  undoModel.trimToSize(maxUndoStates);
258  }
259  }
260  }
261 
267  private boolean doUndo(final boolean performAction) {
268  final MapModel<G, A, R> tmpMapModel = mapModel;
269  if (tmpMapModel == null) {
270  return false;
271  }
272 
273  final UndoModel<G, A, R> undoModel = undoModels.get(tmpMapModel);
274  if (undoModel == null) {
275  // XXX: should be "assert undoModel != null"; this does not work
276  // because MapViewManager.activateMapView() calls
277  // mainControl.setCurrentLevel() which then generates a
278  // currentMapChanged() event before a mapCreated() event
279  return false;
280  }
281 
282  if (!undoModel.canUndo()) {
283  return false;
284  }
285 
286  if (performAction) {
287  UndoActions.undo(undoModel, tmpMapModel);
288  refreshMenus();
289  }
290 
291  return true;
292  }
293 
299  private boolean doRedo(final boolean performAction) {
300  final MapModel<G, A, R> tmpMapModel = mapModel;
301  if (tmpMapModel == null) {
302  return false;
303  }
304 
305  final UndoModel<G, A, R> undoModel = undoModels.get(tmpMapModel);
306  if (undoModel == null) {
307  // XXX: should be "assert undoModel != null"; this does not work
308  // because MapViewManager.activateMapView() calls
309  // mainControl.setCurrentLevel() which then generates a
310  // currentMapChanged() event before a mapCreated() event
311  return false;
312  }
313 
314  if (!undoModel.canRedo()) {
315  return false;
316  }
317 
318  if (performAction) {
319  UndoActions.redo(undoModel, tmpMapModel);
320  refreshMenus();
321  }
322 
323  return true;
324  }
325 
326  @Override
327  public void setAction(@NotNull final Action action, @NotNull final String name) {
328  if (name.equals("undo")) {
329  aUndo = action;
330  } else if (name.equals("redo")) {
331  aRedo = action;
332  } else {
333  throw new IllegalArgumentException();
334  }
335  }
336 
337 }
Implements the controller for undo/redo actions.
Action aRedo
Action for "redo" function.
void refreshMenus()
Enable/disable menu entries based on the current state.
A MapModel reflects the data of a map.
Definition: MapModel.java:75
A MapManager manages all opened maps.
Definition: MapManager.java:37
String undoName()
Return the "undo" operation name.
Definition: UndoModel.java:150
Maintains the undo state for one map control.
Definition: UndoModel.java:40
This package contains classes related to matching GameObjects, so called GameObjectMatchers.
void setSavedSquares(@NotNull final SavedSquares< G, A, R > savedSquares)
Records the affected map squares.
Definition: UndoState.java:82
boolean canRedo()
Return whether a "redo" operation is possible.
Definition: UndoModel.java:142
void undo()
"Undo" was selected.
void setAction(@NotNull final Action action, @NotNull final String name)
Sets the Action instance for this editor action.
Action aUndo
Action for "undo" function.
void finish(@NotNull final UndoState< G, A, R > undoState)
Finishes an undo or redo operation.
Definition: UndoModel.java:208
SavedSquares< G, A, R > cloneAndClear()
Creates a new instance having the same contents as this instance, then forgets all saves squares in t...
MapModel< G, A, R > mapModel
Map model for the current map.
Holds information to undo/redo one edit operation.
Definition: UndoState.java:34
static String getString(@NotNull final ActionBuilder actionBuilder, @NotNull final String key, @NotNull final String defaultValue)
Returns the value of a key.
Base package of all Gridarta classes.
Interface for listeners listening on map transactions of MapModels.
Reflects a game object (object on a map).
Definition: GameObject.java:36
void addMapTransactionListener(@NotNull MapTransactionListener< G, A, R > listener)
Registers a map transaction listener.
Abstract factory for creating GameObject instances.
A global editor action.
boolean canUndo()
Return whether an "undo" operation is possible.
Definition: UndoModel.java:133
Interface for listeners listening to MapManager changes.
GameObjects are the objects based on Archetypes found on maps.
boolean doRedo(final boolean performAction)
Performs the "redo" action.
MapTransactionListener< G, A, R > getMapTransactionListener()
Returns the MapTransactionListener that was passed to the constructor.
Definition: UndoModel.java:97
void redo()
"Redo" was selected.
Maintains GameObjectMatcher instances.
UndoControl(@NotNull final MapManager< G, A, R > mapManager, @NotNull final GameObjectFactory< G, A, R > gameObjectFactory, @NotNull final GameObjectMatchers gameObjectMatchers)
Create a new instance.
void setMaxUndoStates(final int maxUndoStates)
Sets the maximum number of undo states saved for each map.
boolean doUndo(final boolean performAction)
Performs the "undo" action.
String redoName()
Return the "redo" operation name.
Definition: UndoModel.java:162
Utility class for ActionBuilder related functions.
Implementation of Undo / Redo for Gridarta.
A getMapArchObject()
Returns the Map Arch Object with the meta information about the map.
void removeEmptySquares(@NotNull final Size2D size)
Removes empty squares outside a given area.
final Map< MapModel< G, A, R >, UndoModel< G, A, R > > undoModels
Contains an UndoModel for each known MapModel.
int maxUndoStates
The maximum number of undo states saved for each map.
Currently nothing more than a marker interface for unification.
Definition: MapControl.java:35
Records a set of changed map squares.
int getMaxUndoStates()
Returns the maximum number of undo states saved for each map.
Utility class implementing undo and redo actions.
void trimToSize(final int maxUndoStates)
Discard old undo information.
Definition: UndoModel.java:118
A getMapArchObject()
Returns the map arch object before the operation started.
Definition: UndoState.java:109
static final ActionBuilder ACTION_BUILDER
Action Builder to create Actions.