5958e18c2b293babfb65e53ac1482fe2a02d48ce
[debian/openrocket] / src / net / sf / openrocket / gui / main / RocketActions.java
1 package net.sf.openrocket.gui.main;
2
3
4 import java.awt.event.ActionEvent;
5 import java.awt.event.KeyEvent;
6 import java.util.ArrayList;
7
8 import javax.swing.AbstractAction;
9 import javax.swing.Action;
10 import javax.swing.JCheckBox;
11 import javax.swing.JOptionPane;
12 import javax.swing.JPanel;
13 import javax.swing.KeyStroke;
14
15 import net.miginfocom.swing.MigLayout;
16 import net.sf.openrocket.document.OpenRocketDocument;
17 import net.sf.openrocket.document.Simulation;
18 import net.sf.openrocket.gui.components.ResizeLabel;
19 import net.sf.openrocket.gui.configdialog.ComponentConfigDialog;
20 import net.sf.openrocket.rocketcomponent.ComponentChangeEvent;
21 import net.sf.openrocket.rocketcomponent.ComponentChangeListener;
22 import net.sf.openrocket.rocketcomponent.Rocket;
23 import net.sf.openrocket.rocketcomponent.RocketComponent;
24 import net.sf.openrocket.rocketcomponent.Stage;
25 import net.sf.openrocket.util.Icons;
26 import net.sf.openrocket.util.Pair;
27 import net.sf.openrocket.util.Prefs;
28
29
30
31 /**
32  * A class that holds Actions for common rocket and simulation operations such as
33  * cut/copy/paste/delete etc.
34  * 
35  * @author Sampo Niskanen <sampo.niskanen@iki.fi>
36  */
37 public class RocketActions {
38
39         public static final KeyStroke CUT_KEY_STROKE = KeyStroke.getKeyStroke(KeyEvent.VK_X,
40                         ActionEvent.CTRL_MASK);
41         public static final KeyStroke COPY_KEY_STROKE = KeyStroke.getKeyStroke(KeyEvent.VK_C,
42                         ActionEvent.CTRL_MASK);
43         public static final KeyStroke PASTE_KEY_STROKE = KeyStroke.getKeyStroke(KeyEvent.VK_V,
44                         ActionEvent.CTRL_MASK);
45         
46         private final OpenRocketDocument document;
47         private final Rocket rocket;
48         private final BasicFrame parentFrame;
49         private final DocumentSelectionModel selectionModel;
50
51
52         private final RocketAction deleteComponentAction;
53         private final RocketAction deleteSimulationAction;
54         private final RocketAction deleteAction;
55         private final RocketAction cutAction;
56         private final RocketAction copyAction;
57         private final RocketAction pasteAction;
58         private final RocketAction editAction;
59         private final RocketAction newStageAction;
60         private final RocketAction moveUpAction;
61         private final RocketAction moveDownAction;
62         
63
64         public RocketActions(OpenRocketDocument document, DocumentSelectionModel selectionModel,
65                         BasicFrame parentFrame) {
66                 this.document = document;
67                 this.rocket = document.getRocket();
68                 this.selectionModel = selectionModel;
69                 this.parentFrame = parentFrame;
70
71                 // Add action also to updateActions()
72                 this.deleteAction = new DeleteAction();
73                 this.deleteComponentAction = new DeleteComponentAction();
74                 this.deleteSimulationAction = new DeleteSimulationAction();
75                 this.cutAction = new CutAction();
76                 this.copyAction = new CopyAction();
77                 this.pasteAction = new PasteAction();
78                 this.editAction = new EditAction();
79                 this.newStageAction = new NewStageAction();
80                 this.moveUpAction = new MoveUpAction();
81                 this.moveDownAction = new MoveDownAction();
82
83                 OpenRocketClipboard.addClipboardListener(this.pasteAction);
84                 updateActions();
85
86                 // Update all actions when tree selection or rocket changes
87
88                 selectionModel.addDocumentSelectionListener(new DocumentSelectionListener() {
89                         @Override
90                         public void valueChanged(int changeType) {
91                                 updateActions();
92                         }
93                 });
94                 document.getRocket().addComponentChangeListener(new ComponentChangeListener() {
95                         @Override
96                         public void componentChanged(ComponentChangeEvent e) {
97                                 updateActions();
98                         }
99                 });
100         }
101
102         /**
103          * Update the state of all of the actions.
104          */
105         private void updateActions() {
106                 deleteAction.clipboardChanged();
107                 cutAction.clipboardChanged();
108                 copyAction.clipboardChanged();
109                 pasteAction.clipboardChanged();
110                 editAction.clipboardChanged();
111                 newStageAction.clipboardChanged();
112                 moveUpAction.clipboardChanged();
113                 moveDownAction.clipboardChanged();
114         }
115         
116         
117         
118
119         public Action getDeleteComponentAction() {
120                 return deleteAction;
121         }
122
123         public Action getDeleteSimulationAction() {
124                 return deleteAction;
125         }
126
127         public Action getDeleteAction() {
128                 return deleteAction;
129         }
130
131         public Action getCutAction() {
132                 return cutAction;
133         }
134         
135         public Action getCopyAction() {
136                 return copyAction;
137         }
138         
139         public Action getPasteAction() {
140                 return pasteAction;
141         }
142         
143         public Action getEditAction() {
144                 return editAction;
145         }
146         
147         public Action getNewStageAction() {
148                 return newStageAction;
149         }
150         
151         public Action getMoveUpAction() {
152                 return moveUpAction;
153         }
154         
155         public Action getMoveDownAction() {
156                 return moveDownAction;
157         }
158
159         
160         
161         ////////  Helper methods for the actions
162
163         private boolean isDeletable(RocketComponent c) {
164                 // Sanity check
165                 if (c == null || c.getParent() == null)
166                         return false;
167
168                 // Cannot remove Rocket
169                 if (c instanceof Rocket)
170                         return false;
171
172                 // Cannot remove last stage
173                 if ((c instanceof Stage) && (c.getParent().getChildCount() == 1)) {
174                         return false;
175                 }
176
177                 return true;
178         }
179
180         private void delete(RocketComponent c) {
181                 if (!isDeletable(c)) {
182                         throw new IllegalArgumentException("Report bug!  Component " + c + 
183                                         " not deletable.");
184                 }
185
186                 RocketComponent parent = c.getParent();
187                 parent.removeChild(c);
188         }
189
190         private boolean isCopyable(RocketComponent c) {
191                 if (c==null)
192                         return false;
193                 if (c instanceof Rocket)
194                         return false;
195                 return true;
196         }
197
198         
199         private boolean isSimulationSelected() {
200                 Simulation[] selection = selectionModel.getSelectedSimulations();
201                 return (selection != null  &&  selection.length > 0);
202         }
203         
204         
205         
206         private boolean verifyDeleteSimulation() {
207                 boolean verify = Prefs.NODE.getBoolean(Prefs.CONFIRM_DELETE_SIMULATION, true);
208                 if (verify) {
209                         JPanel panel = new JPanel(new MigLayout());
210                         JCheckBox dontAsk = new JCheckBox("Do not ask me again");
211                         panel.add(dontAsk,"wrap");
212                         panel.add(new ResizeLabel("You can change the default operation in the " +
213                                         "preferences.",-2));
214
215                         int ret = JOptionPane.showConfirmDialog(
216                                         parentFrame,
217                                         new Object[] {
218                                         "Delete the selected simulations?",
219                                         "<html><i>This operation cannot be undone.</i>",
220                                         "",
221                                         panel },
222                                         "Delete simulations",
223                                         JOptionPane.OK_CANCEL_OPTION,
224                                         JOptionPane.WARNING_MESSAGE);
225                         if (ret != JOptionPane.OK_OPTION)
226                                 return false;
227
228                         if (dontAsk.isSelected()) {
229                                 Prefs.NODE.putBoolean(Prefs.CONFIRM_DELETE_SIMULATION, false);
230                         }
231                 }
232
233                 return true;
234         }
235
236
237         /**
238          * Return the component and position to which the current clipboard
239          * should be pasted.  Returns null if the clipboard is empty or if the
240          * clipboard cannot be pasted to the current selection.
241          * 
242          * @param   clipboard   the component on the clipboard.
243          * @return  a Pair with both components defined, or null.
244          */
245         private Pair<RocketComponent, Integer> getPastePosition(RocketComponent clipboard) {
246                 RocketComponent selected = selectionModel.getSelectedComponent();
247                 if (selected == null)
248                         return null;
249
250                 if (clipboard == null)
251                         return null;
252
253                 if (selected.isCompatible(clipboard))
254                         return new Pair<RocketComponent, Integer>(selected, selected.getChildCount());
255
256                 RocketComponent parent = selected.getParent();
257                 if (parent != null && parent.isCompatible(clipboard)) {
258                         int index = parent.getChildPosition(selected) + 1;
259                         return new Pair<RocketComponent, Integer>(parent, index);
260                 }
261
262                 return null;
263         }
264         
265         
266         
267         
268
269         ///////  Action classes
270
271         private abstract class RocketAction extends AbstractAction implements ClipboardListener {
272                 public abstract void clipboardChanged();
273         }
274
275
276         /**
277          * Action that deletes the selected component.
278          */
279         private class DeleteComponentAction extends RocketAction {
280                 public DeleteComponentAction() {
281                         this.putValue(NAME, "Delete");
282                         this.putValue(SHORT_DESCRIPTION, "Delete the selected component.");
283                         this.putValue(MNEMONIC_KEY, KeyEvent.VK_D);
284 //                      this.putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0));
285                         this.putValue(SMALL_ICON, Icons.EDIT_DELETE);
286                         clipboardChanged();
287                 }
288
289                 @Override
290                 public void actionPerformed(ActionEvent e) {
291                         RocketComponent c = selectionModel.getSelectedComponent();
292
293                         if (isDeletable(c)) {
294                                 ComponentConfigDialog.hideDialog();
295
296                                 document.addUndoPosition("Delete " + c.getComponentName());
297                                 delete(c);
298                         }
299                 }
300
301                 @Override
302                 public void clipboardChanged() {
303                         this.setEnabled(isDeletable(selectionModel.getSelectedComponent()));
304                 }
305         }
306
307
308         
309         /**
310          * Action that deletes the selected component.
311          */
312         private class DeleteSimulationAction extends RocketAction {
313                 public DeleteSimulationAction() {
314                         this.putValue(NAME, "Delete");
315                         this.putValue(SHORT_DESCRIPTION, "Delete the selected simulation.");
316                         this.putValue(MNEMONIC_KEY, KeyEvent.VK_D);
317 //                      this.putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0));
318                         this.putValue(SMALL_ICON, Icons.EDIT_DELETE);
319                         clipboardChanged();
320                 }
321
322                 @Override
323                 public void actionPerformed(ActionEvent e) {
324                         Simulation[] sims = selectionModel.getSelectedSimulations();
325                         if (sims.length > 0) {
326                                 if (verifyDeleteSimulation()) {
327                                         for (Simulation s: sims) {
328                                                 document.removeSimulation(s);
329                                         }
330                                 }
331                         }
332                 }
333
334                 @Override
335                 public void clipboardChanged() {
336                         this.setEnabled(isSimulationSelected());
337                 }
338         }
339
340
341         
342         /**
343          * Action that deletes the selected component.
344          */
345         private class DeleteAction extends RocketAction {
346                 public DeleteAction() {
347                         this.putValue(NAME, "Delete");
348                         this.putValue(SHORT_DESCRIPTION, "Delete the selected component or simulation.");
349                         this.putValue(MNEMONIC_KEY, KeyEvent.VK_D);
350                         this.putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0));
351                         this.putValue(SMALL_ICON, Icons.EDIT_DELETE);
352                         clipboardChanged();
353                 }
354
355                 @Override
356                 public void actionPerformed(ActionEvent e) {
357                         if (isSimulationSelected()) {
358                                 deleteSimulationAction.actionPerformed(e);
359                                 parentFrame.selectTab(BasicFrame.SIMULATION_TAB);
360                         } else {
361                                 deleteComponentAction.actionPerformed(e);
362                                 parentFrame.selectTab(BasicFrame.COMPONENT_TAB);
363                         }
364                 }
365
366                 @Override
367                 public void clipboardChanged() {
368                         this.setEnabled(isDeletable(selectionModel.getSelectedComponent()) ||
369                                         isSimulationSelected());
370                 }
371         }
372
373
374         
375         /**
376          * Action the cuts the selected component (copies to clipboard and deletes).
377          */
378         private class CutAction extends RocketAction {
379                 public CutAction() {
380                         this.putValue(NAME, "Cut");
381                         this.putValue(MNEMONIC_KEY, KeyEvent.VK_T);
382                         this.putValue(ACCELERATOR_KEY, CUT_KEY_STROKE);
383                         this.putValue(SHORT_DESCRIPTION, "Cut this component or simulation to "
384                                         + "the clipboard and remove from this design");
385                         this.putValue(SMALL_ICON, Icons.EDIT_CUT);
386                         clipboardChanged();
387                 }
388
389                 @Override
390                 public void actionPerformed(ActionEvent e) {
391                         RocketComponent c = selectionModel.getSelectedComponent();
392                         Simulation[] sims = selectionModel.getSelectedSimulations();
393
394                         if (isDeletable(c) && isCopyable(c)) {
395                                 ComponentConfigDialog.hideDialog();
396                                 
397                                 document.addUndoPosition("Cut " + c.getComponentName());
398                                 OpenRocketClipboard.setClipboard(c.copy());
399                                 delete(c);
400                                 parentFrame.selectTab(BasicFrame.COMPONENT_TAB);
401                         } else if (isSimulationSelected()) {
402
403                                 Simulation[] simsCopy = new Simulation[sims.length];
404                                 for (int i=0; i < sims.length; i++) {
405                                         simsCopy[i] = sims[i].copy();
406                                 }
407                                 OpenRocketClipboard.setClipboard(simsCopy);
408
409                                 for (Simulation s: sims) {
410                                         document.removeSimulation(s);
411                                 }
412                                 parentFrame.selectTab(BasicFrame.SIMULATION_TAB);
413                         }
414                 }
415
416                 @Override
417                 public void clipboardChanged() {
418                         RocketComponent c = selectionModel.getSelectedComponent();
419                         this.setEnabled((isDeletable(c) && isCopyable(c)) || isSimulationSelected());
420                 }
421         }
422
423
424
425         /**
426          * Action that copies the selected component to the clipboard.
427          */
428         private class CopyAction extends RocketAction {
429                 public CopyAction() {
430                         this.putValue(NAME, "Copy");
431                         this.putValue(MNEMONIC_KEY, KeyEvent.VK_C);
432                         this.putValue(ACCELERATOR_KEY, COPY_KEY_STROKE);
433                         this.putValue(SHORT_DESCRIPTION, "Copy this component (and subcomponents) to "
434                                         + "the clipboard.");
435                         this.putValue(SMALL_ICON, Icons.EDIT_COPY);
436                         clipboardChanged();
437                 }
438
439                 @Override
440                 public void actionPerformed(ActionEvent e) {
441                         RocketComponent c = selectionModel.getSelectedComponent();
442                         Simulation[] sims = selectionModel.getSelectedSimulations();
443
444                         if (isCopyable(c)) {
445                                 OpenRocketClipboard.setClipboard(c.copy());
446                                 parentFrame.selectTab(BasicFrame.COMPONENT_TAB);
447                         } else if (sims.length >= 0) {
448
449                                 Simulation[] simsCopy = new Simulation[sims.length];
450                                 for (int i=0; i < sims.length; i++) {
451                                         simsCopy[i] = sims[i].copy();
452                                 }
453                                 OpenRocketClipboard.setClipboard(simsCopy);
454                                 parentFrame.selectTab(BasicFrame.SIMULATION_TAB);
455                         }
456                 }
457
458                 @Override
459                 public void clipboardChanged() {
460                         this.setEnabled(isCopyable(selectionModel.getSelectedComponent()) ||
461                                         isSimulationSelected());
462                 }
463                 
464         }
465
466
467
468         /**
469          * Action that pastes the current clipboard to the selected position.
470          * It first tries to paste the component to the end of the selected component
471          * as a child, and after that as a sibling after the selected component. 
472          */
473         private class PasteAction extends RocketAction {
474                 public PasteAction() {
475                         this.putValue(NAME, "Paste");
476                         this.putValue(MNEMONIC_KEY, KeyEvent.VK_P);
477                         this.putValue(ACCELERATOR_KEY, PASTE_KEY_STROKE);
478                         this.putValue(SHORT_DESCRIPTION, "Paste the component or simulation on "
479                                         + "the clipboard to the design.");
480                         this.putValue(SMALL_ICON, Icons.EDIT_PASTE);
481                         clipboardChanged();
482                 }
483
484                 @Override
485                 public void actionPerformed(ActionEvent e) {
486                         RocketComponent clipboard = OpenRocketClipboard.getClipboardComponent();
487                         Simulation[] sims = OpenRocketClipboard.getClipboardSimulations();
488                         
489                         Pair<RocketComponent, Integer> position = getPastePosition(clipboard);
490                         if (position != null) {
491                                 ComponentConfigDialog.hideDialog();
492                                 
493                                 RocketComponent pasted = clipboard.copy();
494                                 document.addUndoPosition("Paste " + pasted.getComponentName());
495                                 position.getU().addChild(pasted, position.getV());
496                                 selectionModel.setSelectedComponent(pasted);
497                                 
498                                 parentFrame.selectTab(BasicFrame.COMPONENT_TAB);
499                                 
500                         } else if (sims != null) {
501                                 
502                                 ArrayList<Simulation> copySims = new ArrayList<Simulation>();
503
504                                 for (Simulation s: sims) {
505                                         Simulation copy = s.duplicateSimulation(rocket);
506                                         String name = copy.getName();
507                                         if (name.matches(OpenRocketDocument.SIMULATION_NAME_PREFIX + "[0-9]+ *")) {
508                                                 copy.setName(document.getNextSimulationName());
509                                         }
510                                         document.addSimulation(copy);
511                                         copySims.add(copy);
512                                 }
513                                 selectionModel.setSelectedSimulations(copySims.toArray(new Simulation[0]));
514                                 
515                                 parentFrame.selectTab(BasicFrame.SIMULATION_TAB);
516                         }
517                 }
518
519                 @Override
520                 public void clipboardChanged() {
521                         this.setEnabled(
522                                         (getPastePosition(OpenRocketClipboard.getClipboardComponent()) != null) ||
523                                         (OpenRocketClipboard.getClipboardSimulations() != null));
524                 }
525         }
526         
527         
528         
529         
530
531         
532         /**
533          * Action to edit the currently selected component.
534          */
535         private class EditAction extends RocketAction {
536                 public EditAction() {
537                         this.putValue(NAME, "Edit");
538                         this.putValue(SHORT_DESCRIPTION, "Edit the selected component.");
539                         clipboardChanged();
540                 }
541
542                 @Override
543                 public void actionPerformed(ActionEvent e) {
544                         RocketComponent c = selectionModel.getSelectedComponent();
545                         if (c == null)
546                                 return;
547                         
548                         ComponentConfigDialog.showDialog(parentFrame, document, c);
549                 }
550
551                 @Override
552                 public void clipboardChanged() {
553                         this.setEnabled(selectionModel.getSelectedComponent() != null);
554                 }
555         }
556
557
558         
559         
560         
561         
562         
563         /**
564          * Action to add a new stage to the rocket.
565          */
566         private class NewStageAction extends RocketAction {
567                 public NewStageAction() {
568                         this.putValue(NAME, "New stage");
569                         this.putValue(SHORT_DESCRIPTION, "Add a new stage to the rocket design.");
570                         clipboardChanged();
571                 }
572
573                 @Override
574                 public void actionPerformed(ActionEvent e) {
575                         
576                         ComponentConfigDialog.hideDialog();
577
578                         RocketComponent stage = new Stage();
579                         stage.setName("Booster stage");
580                         document.addUndoPosition("Add stage");
581                         rocket.addChild(stage);
582                         rocket.getDefaultConfiguration().setAllStages();
583                         selectionModel.setSelectedComponent(stage);
584                         ComponentConfigDialog.showDialog(parentFrame, document, stage);
585                         
586                 }
587
588                 @Override
589                 public void clipboardChanged() {
590                         this.setEnabled(true);
591                 }
592         }
593
594
595
596         
597         /**
598          * Action to move the selected component upwards in the parent's child list.
599          */
600         private class MoveUpAction extends RocketAction {
601                 public MoveUpAction() {
602                         this.putValue(NAME, "Move up");
603                         this.putValue(SHORT_DESCRIPTION, "Move this component upwards.");
604                         clipboardChanged();
605                 }
606
607                 @Override
608                 public void actionPerformed(ActionEvent e) {
609                         RocketComponent selected = selectionModel.getSelectedComponent();
610                         if (!canMove(selected))
611                                 return;
612                         
613                         ComponentConfigDialog.hideDialog();
614
615                         RocketComponent parent = selected.getParent();
616                         document.addUndoPosition("Move "+selected.getComponentName());
617                         parent.moveChild(selected, parent.getChildPosition(selected)-1);
618                         selectionModel.setSelectedComponent(selected);
619                 }
620
621                 @Override
622                 public void clipboardChanged() {
623                         this.setEnabled(canMove(selectionModel.getSelectedComponent()));
624                 }
625                 
626                 private boolean canMove(RocketComponent c) {
627                         if (c == null || c.getParent() == null)
628                                 return false;
629                         RocketComponent parent = c.getParent();
630                         if (parent.getChildPosition(c) > 0)
631                                 return true;
632                         return false;
633                 }
634         }
635
636
637
638         /**
639          * Action to move the selected component down in the parent's child list.
640          */
641         private class MoveDownAction extends RocketAction {
642                 public MoveDownAction() {
643                         this.putValue(NAME, "Move down");
644                         this.putValue(SHORT_DESCRIPTION, "Move this component downwards.");
645                         clipboardChanged();
646                 }
647
648                 @Override
649                 public void actionPerformed(ActionEvent e) {
650                         RocketComponent selected = selectionModel.getSelectedComponent();
651                         if (!canMove(selected))
652                                 return;
653                         
654                         ComponentConfigDialog.hideDialog();
655
656                         RocketComponent parent = selected.getParent();
657                         document.addUndoPosition("Move "+selected.getComponentName());
658                         parent.moveChild(selected, parent.getChildPosition(selected)+1);
659                         selectionModel.setSelectedComponent(selected);
660                 }
661
662                 @Override
663                 public void clipboardChanged() {
664                         this.setEnabled(canMove(selectionModel.getSelectedComponent()));
665                 }
666                 
667                 private boolean canMove(RocketComponent c) {
668                         if (c == null || c.getParent() == null)
669                                 return false;
670                         RocketComponent parent = c.getParent();
671                         if (parent.getChildPosition(c) < parent.getChildCount()-1)
672                                 return true;
673                         return false;
674                 }
675         }
676
677
678
679 }