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.rocketcomponent.ComponentChangeEvent;
14 import net.sf.openrocket.rocketcomponent.ComponentChangeListener;
15 import net.sf.openrocket.rocketcomponent.Configuration;
16 import net.sf.openrocket.rocketcomponent.Rocket;
17 import net.sf.openrocket.util.Icons;
20 public class OpenRocketDocument implements ComponentChangeListener {
22 * The minimum number of undo levels that are stored.
24 public static final int UNDO_LEVELS = 50;
26 * The margin of the undo levels. After the number of undo levels exceeds
27 * UNDO_LEVELS by this amount the undo is purged to that length.
29 public static final int UNDO_MARGIN = 10;
32 private final Rocket rocket;
33 private final Configuration configuration;
35 private final ArrayList<Simulation> simulations = new ArrayList<Simulation>();
38 private int undoPosition = -1; // Illegal position, init in constructor
39 private LinkedList<Rocket> undoHistory = new LinkedList<Rocket>();
40 private LinkedList<String> undoDescription = new LinkedList<String>();
42 private String nextDescription = null;
45 private File file = null;
46 private int savedID = -1;
48 private final StorageOptions storageOptions = new StorageOptions();
51 /* These must be initialized after undo history is set up. */
52 private final UndoRedoAction undoAction;
53 private final UndoRedoAction redoAction;
56 public OpenRocketDocument(Rocket rocket) {
57 this(rocket.getDefaultConfiguration());
61 private OpenRocketDocument(Configuration configuration) {
62 this.configuration = configuration;
63 this.rocket = configuration.getRocket();
65 undoHistory.add(rocket.copy());
66 undoDescription.add(null);
69 undoAction = new UndoRedoAction(UndoRedoAction.UNDO);
70 redoAction = new UndoRedoAction(UndoRedoAction.REDO);
72 rocket.addComponentChangeListener(this);
80 public Rocket getRocket() {
85 public Configuration getDefaultConfiguration() {
90 public File getFile() {
94 public void setFile(File file) {
99 public boolean isSaved() {
100 return rocket.getModID() == savedID;
103 public void setSaved(boolean saved) {
107 this.savedID = rocket.getModID();
111 * Retrieve the default storage options for this document.
113 * @return the storage options.
115 public StorageOptions getDefaultStorageOptions() {
116 return storageOptions;
123 @SuppressWarnings("unchecked")
124 public List<Simulation> getSimulations() {
125 return (ArrayList<Simulation>)simulations.clone();
127 public int getSimulationCount() {
128 return simulations.size();
130 public Simulation getSimulation(int n) {
131 return simulations.get(n);
133 public int getSimulationIndex(Simulation simulation) {
134 return simulations.indexOf(simulation);
136 public void addSimulation(Simulation simulation) {
137 simulations.add(simulation);
139 public void addSimulation(Simulation simulation, int n) {
140 simulations.add(n, simulation);
142 public void removeSimulation(Simulation simulation) {
143 simulations.remove(simulation);
145 public Simulation removeSimulation(int n) {
146 return simulations.remove(n);
152 * Adds an undo point at this position. This method should be called *before* any
153 * action that is to be undoable. All actions after the call will be undone by a
154 * single "Undo" action.
156 * The description should be a short, descriptive string of the actions that will
157 * follow. This is shown to the user e.g. in the Edit-menu, for example
158 * "Undo (Modify body tube)". If the actions are not known (in general should not
159 * be the case!) description may be null.
161 * If this method is called successively without any change events occurring between the
162 * calls, only the last call will have any effect.
164 * @param description A short description of the following actions.
166 public void addUndoPosition(String description) {
168 // Check whether modifications have been done since last call
169 if (isCleanState()) {
171 nextDescription = description;
177 * Modifications have been made to the rocket. We should be at the end of the
178 * undo history, but check for consistency.
180 assert(undoPosition == undoHistory.size()-1): "Undo inconsistency, report bug!";
181 while (undoPosition < undoHistory.size()-1) {
182 undoHistory.removeLast();
183 undoDescription.removeLast();
187 // Add the current state to the undo history
188 undoHistory.add(rocket.copy());
189 undoDescription.add(description);
190 nextDescription = description;
194 // Maintain maximum undo size
195 if (undoHistory.size() > UNDO_LEVELS + UNDO_MARGIN) {
196 for (int i=0; i < UNDO_MARGIN+1; i++) {
197 undoHistory.removeFirst();
198 undoDescription.removeFirst();
205 public Action getUndoAction() {
210 public Action getRedoAction() {
216 public void componentChanged(ComponentChangeEvent e) {
218 if (!e.isUndoChange()) {
219 // Remove any redo information if available
220 while (undoPosition < undoHistory.size()-1) {
221 undoHistory.removeLast();
222 undoDescription.removeLast();
225 // Set the latest description
226 undoDescription.set(undoPosition, nextDescription);
229 undoAction.setAllValues();
230 redoAction.setAllValues();
234 public boolean isUndoAvailable() {
235 if (undoPosition > 0)
238 return !isCleanState();
241 public String getUndoDescription() {
242 if (!isUndoAvailable())
245 if (isCleanState()) {
246 return undoDescription.get(undoPosition-1);
248 return undoDescription.get(undoPosition);
253 public boolean isRedoAvailable() {
254 return undoPosition < undoHistory.size()-1;
257 public String getRedoDescription() {
258 if (!isRedoAvailable())
261 return undoDescription.get(undoPosition);
267 if (!isUndoAvailable()) {
268 throw new IllegalStateException("Undo not available.");
271 // Update history position
273 if (isCleanState()) {
274 // We are in a clean state, simply move backwards in history
277 // Modifications have been made, save the state and restore previous state
278 undoHistory.add(rocket.copy());
279 undoDescription.add(null);
282 rocket.loadFrom(undoHistory.get(undoPosition).copy());
287 if (!isRedoAvailable()) {
288 throw new IllegalStateException("Redo not available.");
293 rocket.loadFrom(undoHistory.get(undoPosition).copy());
297 private boolean isCleanState() {
298 return rocket.getModID() == undoHistory.get(undoPosition).getModID();
307 * Inner class to implement undo/redo actions.
309 private class UndoRedoAction extends AbstractAction {
310 public static final int UNDO = 1;
311 public static final int REDO = 2;
313 private final int type;
316 public UndoRedoAction(int type) {
317 if (type != UNDO && type != REDO) {
318 throw new IllegalArgumentException("Unknown type = "+type);
325 // Actual action to make
326 public void actionPerformed(ActionEvent e) {
339 // Set all the values correctly (name and enabled/disabled status)
340 public void setAllValues() {
347 desc = getUndoDescription();
348 enabled = isUndoAvailable();
349 this.putValue(SMALL_ICON, Icons.EDIT_UNDO);
354 desc = getRedoDescription();
355 enabled = isRedoAvailable();
356 this.putValue(SMALL_ICON, Icons.EDIT_REDO);
360 throw new RuntimeException("EEEK!");
364 name = name + " ("+desc+")";
366 putValue(NAME, name);