1 package net.sf.openrocket.document;
2 //TODO: LOW: move class somewhere else?
4 import java.awt.event.ActionEvent;
6 import java.util.ArrayList;
7 import java.util.LinkedList;
10 import javax.swing.AbstractAction;
11 import javax.swing.Action;
13 import net.sf.openrocket.document.events.DocumentChangeEvent;
14 import net.sf.openrocket.document.events.DocumentChangeListener;
15 import net.sf.openrocket.document.events.SimulationChangeEvent;
16 import net.sf.openrocket.rocketcomponent.ComponentChangeEvent;
17 import net.sf.openrocket.rocketcomponent.ComponentChangeListener;
18 import net.sf.openrocket.rocketcomponent.Configuration;
19 import net.sf.openrocket.rocketcomponent.Rocket;
20 import net.sf.openrocket.util.Icons;
23 public class OpenRocketDocument implements ComponentChangeListener {
25 * The minimum number of undo levels that are stored.
27 public static final int UNDO_LEVELS = 50;
29 * The margin of the undo levels. After the number of undo levels exceeds
30 * UNDO_LEVELS by this amount the undo is purged to that length.
32 public static final int UNDO_MARGIN = 10;
35 public static final String SIMULATION_NAME_PREFIX = "Simulation ";
37 private final Rocket rocket;
38 private final Configuration configuration;
40 private final ArrayList<Simulation> simulations = new ArrayList<Simulation>();
43 private int undoPosition = -1; // Illegal position, init in constructor
44 private LinkedList<Rocket> undoHistory = new LinkedList<Rocket>();
45 private LinkedList<String> undoDescription = new LinkedList<String>();
47 private String nextDescription = null;
50 private File file = null;
51 private int savedID = -1;
53 private final StorageOptions storageOptions = new StorageOptions();
56 private final List<DocumentChangeListener> listeners =
57 new ArrayList<DocumentChangeListener>();
59 /* These must be initialized after undo history is set up. */
60 private final UndoRedoAction undoAction;
61 private final UndoRedoAction redoAction;
64 public OpenRocketDocument(Rocket rocket) {
65 this(rocket.getDefaultConfiguration());
69 private OpenRocketDocument(Configuration configuration) {
70 this.configuration = configuration;
71 this.rocket = configuration.getRocket();
73 undoHistory.add(rocket.copy());
74 undoDescription.add(null);
77 undoAction = new UndoRedoAction(UndoRedoAction.UNDO);
78 redoAction = new UndoRedoAction(UndoRedoAction.REDO);
80 rocket.addComponentChangeListener(this);
86 public Rocket getRocket() {
91 public Configuration getDefaultConfiguration() {
96 public File getFile() {
100 public void setFile(File file) {
105 public boolean isSaved() {
106 return rocket.getModID() == savedID;
109 public void setSaved(boolean saved) {
113 this.savedID = rocket.getModID();
117 * Retrieve the default storage options for this document.
119 * @return the storage options.
121 public StorageOptions getDefaultStorageOptions() {
122 return storageOptions;
129 @SuppressWarnings("unchecked")
130 public List<Simulation> getSimulations() {
131 return (ArrayList<Simulation>)simulations.clone();
133 public int getSimulationCount() {
134 return simulations.size();
136 public Simulation getSimulation(int n) {
137 return simulations.get(n);
139 public int getSimulationIndex(Simulation simulation) {
140 return simulations.indexOf(simulation);
142 public void addSimulation(Simulation simulation) {
143 simulations.add(simulation);
144 fireDocumentChangeEvent(new SimulationChangeEvent(simulation));
146 public void addSimulation(Simulation simulation, int n) {
147 simulations.add(n, simulation);
148 fireDocumentChangeEvent(new SimulationChangeEvent(simulation));
150 public void removeSimulation(Simulation simulation) {
151 simulations.remove(simulation);
152 fireDocumentChangeEvent(new SimulationChangeEvent(simulation));
154 public Simulation removeSimulation(int n) {
155 Simulation simulation = simulations.remove(n);
156 fireDocumentChangeEvent(new SimulationChangeEvent(simulation));
161 * Return a unique name suitable for the next simulation. The name begins
162 * with {@link #SIMULATION_NAME_PREFIX} and has a unique number larger than any
163 * previous simulation.
165 * @return the new name.
167 public String getNextSimulationName() {
168 // Generate unique name for the simulation
170 for (Simulation s: simulations) {
171 String name = s.getName();
172 if (name.startsWith(SIMULATION_NAME_PREFIX)) {
174 maxValue = Math.max(maxValue,
175 Integer.parseInt(name.substring(SIMULATION_NAME_PREFIX.length())));
176 } catch (NumberFormatException ignore) { }
179 return SIMULATION_NAME_PREFIX + (maxValue+1);
184 * Adds an undo point at this position. This method should be called *before* any
185 * action that is to be undoable. All actions after the call will be undone by a
186 * single "Undo" action.
188 * The description should be a short, descriptive string of the actions that will
189 * follow. This is shown to the user e.g. in the Edit-menu, for example
190 * "Undo (Modify body tube)". If the actions are not known (in general should not
191 * be the case!) description may be null.
193 * If this method is called successively without any change events occurring between the
194 * calls, only the last call will have any effect.
196 * @param description A short description of the following actions.
198 public void addUndoPosition(String description) {
200 // Check whether modifications have been done since last call
201 if (isCleanState()) {
203 nextDescription = description;
209 * Modifications have been made to the rocket. We should be at the end of the
210 * undo history, but check for consistency.
212 assert(undoPosition == undoHistory.size()-1): "Undo inconsistency, report bug!";
213 while (undoPosition < undoHistory.size()-1) {
214 undoHistory.removeLast();
215 undoDescription.removeLast();
219 // Add the current state to the undo history
220 undoHistory.add(rocket.copy());
221 undoDescription.add(description);
222 nextDescription = description;
226 // Maintain maximum undo size
227 if (undoHistory.size() > UNDO_LEVELS + UNDO_MARGIN) {
228 for (int i=0; i < UNDO_MARGIN+1; i++) {
229 undoHistory.removeFirst();
230 undoDescription.removeFirst();
237 public Action getUndoAction() {
242 public Action getRedoAction() {
248 public void componentChanged(ComponentChangeEvent e) {
250 if (!e.isUndoChange()) {
251 // Remove any redo information if available
252 while (undoPosition < undoHistory.size()-1) {
253 undoHistory.removeLast();
254 undoDescription.removeLast();
257 // Set the latest description
258 undoDescription.set(undoPosition, nextDescription);
261 undoAction.setAllValues();
262 redoAction.setAllValues();
266 public boolean isUndoAvailable() {
267 if (undoPosition > 0)
270 return !isCleanState();
273 public String getUndoDescription() {
274 if (!isUndoAvailable())
277 if (isCleanState()) {
278 return undoDescription.get(undoPosition-1);
280 return undoDescription.get(undoPosition);
285 public boolean isRedoAvailable() {
286 return undoPosition < undoHistory.size()-1;
289 public String getRedoDescription() {
290 if (!isRedoAvailable())
293 return undoDescription.get(undoPosition);
299 if (!isUndoAvailable()) {
300 throw new IllegalStateException("Undo not available.");
303 // Update history position
305 if (isCleanState()) {
306 // We are in a clean state, simply move backwards in history
309 // Modifications have been made, save the state and restore previous state
310 undoHistory.add(rocket.copy());
311 undoDescription.add(null);
314 rocket.loadFrom(undoHistory.get(undoPosition).copy());
319 if (!isRedoAvailable()) {
320 throw new IllegalStateException("Redo not available.");
325 rocket.loadFrom(undoHistory.get(undoPosition).copy());
329 private boolean isCleanState() {
330 return rocket.getModID() == undoHistory.get(undoPosition).getModID();
338 public void addDocumentChangeListener(DocumentChangeListener listener) {
339 listeners.add(listener);
342 public void removeDocumentChangeListener(DocumentChangeListener listener) {
343 listeners.remove(listener);
346 protected void fireDocumentChangeEvent(DocumentChangeEvent event) {
347 DocumentChangeListener[] array = listeners.toArray(new DocumentChangeListener[0]);
348 for (DocumentChangeListener l: array) {
349 l.documentChanged(event);
357 * Inner class to implement undo/redo actions.
359 private class UndoRedoAction extends AbstractAction {
360 public static final int UNDO = 1;
361 public static final int REDO = 2;
363 private final int type;
366 public UndoRedoAction(int type) {
367 if (type != UNDO && type != REDO) {
368 throw new IllegalArgumentException("Unknown type = "+type);
375 // Actual action to make
376 public void actionPerformed(ActionEvent e) {
389 // Set all the values correctly (name and enabled/disabled status)
390 public void setAllValues() {
397 desc = getUndoDescription();
398 enabled = isUndoAvailable();
399 this.putValue(SMALL_ICON, Icons.EDIT_UNDO);
404 desc = getRedoDescription();
405 enabled = isRedoAvailable();
406 this.putValue(SMALL_ICON, Icons.EDIT_REDO);
410 throw new RuntimeException("EEEK!");
414 name = name + " ("+desc+")";
416 putValue(NAME, name);