Initial commit
[debian/openrocket] / src / net / sf / openrocket / document / OpenRocketDocument.java
1 package net.sf.openrocket.document;
2 //TODO: LOW: move class somewhere else?
3
4 import java.awt.event.ActionEvent;
5 import java.io.File;
6 import java.util.ArrayList;
7 import java.util.LinkedList;
8 import java.util.List;
9
10 import javax.swing.AbstractAction;
11 import javax.swing.Action;
12
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;
18
19
20 public class OpenRocketDocument implements ComponentChangeListener {
21         /**
22          * The minimum number of undo levels that are stored.
23          */
24         public static final int UNDO_LEVELS = 50;
25         /**
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.
28          */
29         public static final int UNDO_MARGIN = 10;
30
31         
32         private final Rocket rocket;
33         private final Configuration configuration;
34
35         private final ArrayList<Simulation> simulations = new ArrayList<Simulation>();
36         
37         
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>();
41         
42         private String nextDescription = null;
43         
44         
45         private File file = null;
46         private int savedID = -1;
47         
48         private final StorageOptions storageOptions = new StorageOptions();
49         
50         
51         /* These must be initialized after undo history is set up. */
52         private final UndoRedoAction undoAction;
53         private final UndoRedoAction redoAction;
54                 
55         
56         public OpenRocketDocument(Rocket rocket) {
57                 this(rocket.getDefaultConfiguration());
58         }
59         
60
61         private OpenRocketDocument(Configuration configuration) {
62                 this.configuration = configuration;
63                 this.rocket = configuration.getRocket();
64                 
65                 undoHistory.add(rocket.copy());
66                 undoDescription.add(null);
67                 undoPosition = 0;
68                 
69                 undoAction = new UndoRedoAction(UndoRedoAction.UNDO);
70                 redoAction = new UndoRedoAction(UndoRedoAction.REDO);
71                 
72                 rocket.addComponentChangeListener(this);
73                 
74                 
75         }
76         
77         
78         
79         
80         public Rocket getRocket() {
81                 return rocket;
82         }
83
84         
85         public Configuration getDefaultConfiguration() {
86                 return configuration;
87         }
88
89
90         public File getFile() {
91                 return file;
92         }
93
94         public void setFile(File file) {
95                 this.file = file;
96         }
97         
98
99         public boolean isSaved() {
100                 return rocket.getModID() == savedID;
101         }
102
103         public void setSaved(boolean saved) {
104                 if (saved == false)
105                         this.savedID = -1;
106                 else
107                         this.savedID = rocket.getModID();
108         }
109         
110         /**
111          * Retrieve the default storage options for this document.
112          * 
113          * @return      the storage options.
114          */
115         public StorageOptions getDefaultStorageOptions() {
116                 return storageOptions;
117         }
118         
119         
120         
121         
122         
123         @SuppressWarnings("unchecked")
124         public List<Simulation> getSimulations() {
125                 return (ArrayList<Simulation>)simulations.clone();
126         }
127         public int getSimulationCount() {
128                 return simulations.size();
129         }
130         public Simulation getSimulation(int n) {
131                 return simulations.get(n);
132         }
133         public int getSimulationIndex(Simulation simulation) {
134                 return simulations.indexOf(simulation);
135         }
136         public void addSimulation(Simulation simulation) {
137                 simulations.add(simulation);
138         }
139         public void addSimulation(Simulation simulation, int n) {
140                 simulations.add(n, simulation);
141         }
142         public void removeSimulation(Simulation simulation) {
143                 simulations.remove(simulation);
144         }
145         public Simulation removeSimulation(int n) {
146                 return simulations.remove(n);
147         }
148         
149         
150         
151         /**
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.
155          * <p>
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.
160          * <p>
161          * If this method is called successively without any change events occurring between the
162          * calls, only the last call will have any effect.
163          * 
164          * @param description A short description of the following actions.
165          */
166         public void addUndoPosition(String description) {
167
168                 // Check whether modifications have been done since last call
169                 if (isCleanState()) {
170                         // No modifications
171                         nextDescription = description;
172                         return;
173                 }
174
175                 
176                 /*
177                  * Modifications have been made to the rocket.  We should be at the end of the
178                  * undo history, but check for consistency.
179                  */
180                 assert(undoPosition == undoHistory.size()-1): "Undo inconsistency, report bug!";
181                 while (undoPosition < undoHistory.size()-1) {
182                         undoHistory.removeLast();
183                         undoDescription.removeLast();
184                 }
185                 
186                 
187                 // Add the current state to the undo history
188                 undoHistory.add(rocket.copy());
189                 undoDescription.add(description);
190                 nextDescription = description;
191                 undoPosition++;
192                 
193                 
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();
199                                 undoPosition--;
200                         }
201                 }
202         }
203
204         
205         public Action getUndoAction() {
206                 return undoAction;
207         }
208         
209         
210         public Action getRedoAction() {
211                 return redoAction;
212         }
213         
214         
215         @Override
216         public void componentChanged(ComponentChangeEvent e) {
217                 
218                 if (!e.isUndoChange()) {
219                         // Remove any redo information if available
220                         while (undoPosition < undoHistory.size()-1) {
221                                 undoHistory.removeLast();
222                                 undoDescription.removeLast();
223                         }
224                         
225                         // Set the latest description
226                         undoDescription.set(undoPosition, nextDescription);
227                 }
228                 
229                 undoAction.setAllValues();
230                 redoAction.setAllValues();
231         }
232
233         
234         public boolean isUndoAvailable() {
235                 if (undoPosition > 0)
236                         return true;
237                 
238                 return !isCleanState();
239         }
240         
241         public String getUndoDescription() {
242                 if (!isUndoAvailable())
243                         return null;
244                 
245                 if (isCleanState()) {
246                         return undoDescription.get(undoPosition-1);
247                 } else {
248                         return undoDescription.get(undoPosition);
249                 }
250         }
251
252         
253         public boolean isRedoAvailable() {
254                 return undoPosition < undoHistory.size()-1;
255         }
256         
257         public String getRedoDescription() {
258                 if (!isRedoAvailable())
259                         return null;
260                 
261                 return undoDescription.get(undoPosition);
262         }
263         
264         
265         
266         public void undo() {
267                 if (!isUndoAvailable()) {
268                         throw new IllegalStateException("Undo not available.");
269                 }
270
271                 // Update history position
272                 
273                 if (isCleanState()) {
274                         // We are in a clean state, simply move backwards in history
275                         undoPosition--;
276                 } else {
277                         // Modifications have been made, save the state and restore previous state
278                         undoHistory.add(rocket.copy());
279                         undoDescription.add(null);
280                 }
281                 
282                 rocket.loadFrom(undoHistory.get(undoPosition).copy());
283         }
284         
285         
286         public void redo() {
287                 if (!isRedoAvailable()) {
288                         throw new IllegalStateException("Redo not available.");
289                 }
290                 
291                 undoPosition++;
292                 
293                 rocket.loadFrom(undoHistory.get(undoPosition).copy());
294         }
295         
296         
297         private boolean isCleanState() {
298                 return rocket.getModID() == undoHistory.get(undoPosition).getModID();
299         }
300         
301         
302         
303         
304         
305         
306         /**
307          * Inner class to implement undo/redo actions.
308          */
309         private class UndoRedoAction extends AbstractAction {
310                 public static final int UNDO = 1;
311                 public static final int REDO = 2;
312                 
313                 private final int type;
314                 
315                 // Sole constructor
316                 public UndoRedoAction(int type) {
317                         if (type != UNDO && type != REDO) {
318                                 throw new IllegalArgumentException("Unknown type = "+type);
319                         }
320                         this.type = type;
321                         setAllValues();
322                 }
323
324                 
325                 // Actual action to make
326                 public void actionPerformed(ActionEvent e) {
327                         switch (type) {
328                         case UNDO:
329                                 undo();
330                                 break;
331                                 
332                         case REDO:
333                                 redo();
334                                 break;
335                         }
336                 }
337
338                 
339                 // Set all the values correctly (name and enabled/disabled status)
340                 public void setAllValues() {
341                         String name,desc;
342                         boolean enabled;
343                         
344                         switch (type) {
345                         case UNDO:
346                                 name = "Undo";
347                                 desc = getUndoDescription();
348                                 enabled = isUndoAvailable();
349                                 this.putValue(SMALL_ICON, Icons.EDIT_UNDO);
350                                 break;
351                                 
352                         case REDO:
353                                 name = "Redo";
354                                 desc = getRedoDescription();
355                                 enabled = isRedoAvailable();
356                                 this.putValue(SMALL_ICON, Icons.EDIT_REDO);
357                                 break;
358                                 
359                         default:
360                                 throw new RuntimeException("EEEK!");
361                         }
362                         
363                         if (desc != null)
364                                 name = name + " ("+desc+")";
365                         
366                         putValue(NAME, name);
367                         setEnabled(enabled);
368                 }
369         }
370 }