1 package net.sf.openrocket.gui.main;
4 import java.awt.event.ActionEvent;
5 import java.awt.event.KeyEvent;
6 import java.util.ArrayList;
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;
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;
32 * A class that holds Actions for common rocket and simulation operations such as
33 * cut/copy/paste/delete etc.
35 * @author Sampo Niskanen <sampo.niskanen@iki.fi>
37 public class RocketActions {
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);
46 private final OpenRocketDocument document;
47 private final Rocket rocket;
48 private final BasicFrame parentFrame;
49 private final DocumentSelectionModel selectionModel;
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;
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;
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();
83 OpenRocketClipboard.addClipboardListener(this.pasteAction);
86 // Update all actions when tree selection or rocket changes
88 selectionModel.addDocumentSelectionListener(new DocumentSelectionListener() {
90 public void valueChanged(int changeType) {
94 document.getRocket().addComponentChangeListener(new ComponentChangeListener() {
96 public void componentChanged(ComponentChangeEvent e) {
103 * Update the state of all of the actions.
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();
119 public Action getDeleteComponentAction() {
123 public Action getDeleteSimulationAction() {
127 public Action getDeleteAction() {
131 public Action getCutAction() {
135 public Action getCopyAction() {
139 public Action getPasteAction() {
143 public Action getEditAction() {
147 public Action getNewStageAction() {
148 return newStageAction;
151 public Action getMoveUpAction() {
155 public Action getMoveDownAction() {
156 return moveDownAction;
161 //////// Helper methods for the actions
163 private boolean isDeletable(RocketComponent c) {
165 if (c == null || c.getParent() == null)
168 // Cannot remove Rocket
169 if (c instanceof Rocket)
172 // Cannot remove last stage
173 if ((c instanceof Stage) && (c.getParent().getChildCount() == 1)) {
180 private void delete(RocketComponent c) {
181 if (!isDeletable(c)) {
182 throw new IllegalArgumentException("Report bug! Component " + c +
186 RocketComponent parent = c.getParent();
187 parent.removeChild(c);
190 private boolean isCopyable(RocketComponent c) {
193 if (c instanceof Rocket)
199 private boolean isSimulationSelected() {
200 Simulation[] selection = selectionModel.getSelectedSimulations();
201 return (selection != null && selection.length > 0);
206 private boolean verifyDeleteSimulation() {
207 boolean verify = Prefs.NODE.getBoolean(Prefs.CONFIRM_DELETE_SIMULATION, true);
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 " +
215 int ret = JOptionPane.showConfirmDialog(
218 "Delete the selected simulations?",
219 "<html><i>This operation cannot be undone.</i>",
222 "Delete simulations",
223 JOptionPane.OK_CANCEL_OPTION,
224 JOptionPane.WARNING_MESSAGE);
225 if (ret != JOptionPane.OK_OPTION)
228 if (dontAsk.isSelected()) {
229 Prefs.NODE.putBoolean(Prefs.CONFIRM_DELETE_SIMULATION, false);
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.
242 * @param clipboard the component on the clipboard.
243 * @return a Pair with both components defined, or null.
245 private Pair<RocketComponent, Integer> getPastePosition(RocketComponent clipboard) {
246 RocketComponent selected = selectionModel.getSelectedComponent();
247 if (selected == null)
250 if (clipboard == null)
253 if (selected.isCompatible(clipboard))
254 return new Pair<RocketComponent, Integer>(selected, selected.getChildCount());
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);
269 /////// Action classes
271 private abstract class RocketAction extends AbstractAction implements ClipboardListener {
272 public abstract void clipboardChanged();
277 * Action that deletes the selected component.
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);
290 public void actionPerformed(ActionEvent e) {
291 RocketComponent c = selectionModel.getSelectedComponent();
293 if (isDeletable(c)) {
294 ComponentConfigDialog.hideDialog();
296 document.addUndoPosition("Delete " + c.getComponentName());
302 public void clipboardChanged() {
303 this.setEnabled(isDeletable(selectionModel.getSelectedComponent()));
310 * Action that deletes the selected component.
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);
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);
335 public void clipboardChanged() {
336 this.setEnabled(isSimulationSelected());
343 * Action that deletes the selected component.
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);
356 public void actionPerformed(ActionEvent e) {
357 if (isSimulationSelected()) {
358 deleteSimulationAction.actionPerformed(e);
359 parentFrame.selectTab(BasicFrame.SIMULATION_TAB);
361 deleteComponentAction.actionPerformed(e);
362 parentFrame.selectTab(BasicFrame.COMPONENT_TAB);
367 public void clipboardChanged() {
368 this.setEnabled(isDeletable(selectionModel.getSelectedComponent()) ||
369 isSimulationSelected());
376 * Action the cuts the selected component (copies to clipboard and deletes).
378 private class CutAction extends RocketAction {
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);
390 public void actionPerformed(ActionEvent e) {
391 RocketComponent c = selectionModel.getSelectedComponent();
392 Simulation[] sims = selectionModel.getSelectedSimulations();
394 if (isDeletable(c) && isCopyable(c)) {
395 ComponentConfigDialog.hideDialog();
397 document.addUndoPosition("Cut " + c.getComponentName());
398 OpenRocketClipboard.setClipboard(c.copy());
400 parentFrame.selectTab(BasicFrame.COMPONENT_TAB);
401 } else if (isSimulationSelected()) {
403 Simulation[] simsCopy = new Simulation[sims.length];
404 for (int i=0; i < sims.length; i++) {
405 simsCopy[i] = sims[i].copy();
407 OpenRocketClipboard.setClipboard(simsCopy);
409 for (Simulation s: sims) {
410 document.removeSimulation(s);
412 parentFrame.selectTab(BasicFrame.SIMULATION_TAB);
417 public void clipboardChanged() {
418 RocketComponent c = selectionModel.getSelectedComponent();
419 this.setEnabled((isDeletable(c) && isCopyable(c)) || isSimulationSelected());
426 * Action that copies the selected component to the clipboard.
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 "
435 this.putValue(SMALL_ICON, Icons.EDIT_COPY);
440 public void actionPerformed(ActionEvent e) {
441 RocketComponent c = selectionModel.getSelectedComponent();
442 Simulation[] sims = selectionModel.getSelectedSimulations();
445 OpenRocketClipboard.setClipboard(c.copy());
446 parentFrame.selectTab(BasicFrame.COMPONENT_TAB);
447 } else if (sims.length >= 0) {
449 Simulation[] simsCopy = new Simulation[sims.length];
450 for (int i=0; i < sims.length; i++) {
451 simsCopy[i] = sims[i].copy();
453 OpenRocketClipboard.setClipboard(simsCopy);
454 parentFrame.selectTab(BasicFrame.SIMULATION_TAB);
459 public void clipboardChanged() {
460 this.setEnabled(isCopyable(selectionModel.getSelectedComponent()) ||
461 isSimulationSelected());
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.
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);
485 public void actionPerformed(ActionEvent e) {
486 RocketComponent clipboard = OpenRocketClipboard.getClipboardComponent();
487 Simulation[] sims = OpenRocketClipboard.getClipboardSimulations();
489 Pair<RocketComponent, Integer> position = getPastePosition(clipboard);
490 if (position != null) {
491 ComponentConfigDialog.hideDialog();
493 RocketComponent pasted = clipboard.copy();
494 document.addUndoPosition("Paste " + pasted.getComponentName());
495 position.getU().addChild(pasted, position.getV());
496 selectionModel.setSelectedComponent(pasted);
498 parentFrame.selectTab(BasicFrame.COMPONENT_TAB);
500 } else if (sims != null) {
502 ArrayList<Simulation> copySims = new ArrayList<Simulation>();
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());
510 document.addSimulation(copy);
513 selectionModel.setSelectedSimulations(copySims.toArray(new Simulation[0]));
515 parentFrame.selectTab(BasicFrame.SIMULATION_TAB);
520 public void clipboardChanged() {
522 (getPastePosition(OpenRocketClipboard.getClipboardComponent()) != null) ||
523 (OpenRocketClipboard.getClipboardSimulations() != null));
533 * Action to edit the currently selected component.
535 private class EditAction extends RocketAction {
536 public EditAction() {
537 this.putValue(NAME, "Edit");
538 this.putValue(SHORT_DESCRIPTION, "Edit the selected component.");
543 public void actionPerformed(ActionEvent e) {
544 RocketComponent c = selectionModel.getSelectedComponent();
548 ComponentConfigDialog.showDialog(parentFrame, document, c);
552 public void clipboardChanged() {
553 this.setEnabled(selectionModel.getSelectedComponent() != null);
564 * Action to add a new stage to the rocket.
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.");
574 public void actionPerformed(ActionEvent e) {
576 ComponentConfigDialog.hideDialog();
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);
589 public void clipboardChanged() {
590 this.setEnabled(true);
598 * Action to move the selected component upwards in the parent's child list.
600 private class MoveUpAction extends RocketAction {
601 public MoveUpAction() {
602 this.putValue(NAME, "Move up");
603 this.putValue(SHORT_DESCRIPTION, "Move this component upwards.");
608 public void actionPerformed(ActionEvent e) {
609 RocketComponent selected = selectionModel.getSelectedComponent();
610 if (!canMove(selected))
613 ComponentConfigDialog.hideDialog();
615 RocketComponent parent = selected.getParent();
616 document.addUndoPosition("Move "+selected.getComponentName());
617 parent.moveChild(selected, parent.getChildPosition(selected)-1);
618 selectionModel.setSelectedComponent(selected);
622 public void clipboardChanged() {
623 this.setEnabled(canMove(selectionModel.getSelectedComponent()));
626 private boolean canMove(RocketComponent c) {
627 if (c == null || c.getParent() == null)
629 RocketComponent parent = c.getParent();
630 if (parent.getChildPosition(c) > 0)
639 * Action to move the selected component down in the parent's child list.
641 private class MoveDownAction extends RocketAction {
642 public MoveDownAction() {
643 this.putValue(NAME, "Move down");
644 this.putValue(SHORT_DESCRIPTION, "Move this component downwards.");
649 public void actionPerformed(ActionEvent e) {
650 RocketComponent selected = selectionModel.getSelectedComponent();
651 if (!canMove(selected))
654 ComponentConfigDialog.hideDialog();
656 RocketComponent parent = selected.getParent();
657 document.addUndoPosition("Move "+selected.getComponentName());
658 parent.moveChild(selected, parent.getChildPosition(selected)+1);
659 selectionModel.setSelectedComponent(selected);
663 public void clipboardChanged() {
664 this.setEnabled(canMove(selectionModel.getSelectedComponent()));
667 private boolean canMove(RocketComponent c) {
668 if (c == null || c.getParent() == null)
670 RocketComponent parent = c.getParent();
671 if (parent.getChildPosition(c) < parent.getChildCount()-1)