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.StyledLabel;
19 import net.sf.openrocket.gui.configdialog.ComponentConfigDialog;
20 import net.sf.openrocket.l10n.Translator;
21 import net.sf.openrocket.rocketcomponent.ComponentChangeEvent;
22 import net.sf.openrocket.rocketcomponent.ComponentChangeListener;
23 import net.sf.openrocket.rocketcomponent.Rocket;
24 import net.sf.openrocket.rocketcomponent.RocketComponent;
25 import net.sf.openrocket.rocketcomponent.Stage;
26 import net.sf.openrocket.startup.Application;
27 import net.sf.openrocket.util.Icons;
28 import net.sf.openrocket.util.Pair;
29 import net.sf.openrocket.util.Prefs;
34 * A class that holds Actions for common rocket and simulation operations such as
35 * cut/copy/paste/delete etc.
37 * @author Sampo Niskanen <sampo.niskanen@iki.fi>
39 public class RocketActions {
41 public static final KeyStroke CUT_KEY_STROKE = KeyStroke.getKeyStroke(KeyEvent.VK_X,
42 ActionEvent.CTRL_MASK);
43 public static final KeyStroke COPY_KEY_STROKE = KeyStroke.getKeyStroke(KeyEvent.VK_C,
44 ActionEvent.CTRL_MASK);
45 public static final KeyStroke PASTE_KEY_STROKE = KeyStroke.getKeyStroke(KeyEvent.VK_V,
46 ActionEvent.CTRL_MASK);
48 private final OpenRocketDocument document;
49 private final Rocket rocket;
50 private final BasicFrame parentFrame;
51 private final DocumentSelectionModel selectionModel;
54 private final RocketAction deleteComponentAction;
55 private final RocketAction deleteSimulationAction;
56 private final RocketAction deleteAction;
57 private final RocketAction cutAction;
58 private final RocketAction copyAction;
59 private final RocketAction pasteAction;
60 private final RocketAction editAction;
61 private final RocketAction newStageAction;
62 private final RocketAction moveUpAction;
63 private final RocketAction moveDownAction;
64 private static final Translator trans = Application.getTranslator();
67 public RocketActions(OpenRocketDocument document, DocumentSelectionModel selectionModel,
68 BasicFrame parentFrame) {
69 this.document = document;
70 this.rocket = document.getRocket();
71 this.selectionModel = selectionModel;
72 this.parentFrame = parentFrame;
74 // Add action also to updateActions()
75 this.deleteAction = new DeleteAction();
76 this.deleteComponentAction = new DeleteComponentAction();
77 this.deleteSimulationAction = new DeleteSimulationAction();
78 this.cutAction = new CutAction();
79 this.copyAction = new CopyAction();
80 this.pasteAction = new PasteAction();
81 this.editAction = new EditAction();
82 this.newStageAction = new NewStageAction();
83 this.moveUpAction = new MoveUpAction();
84 this.moveDownAction = new MoveDownAction();
86 OpenRocketClipboard.addClipboardListener(this.pasteAction);
89 // Update all actions when tree selection or rocket changes
91 selectionModel.addDocumentSelectionListener(new DocumentSelectionListener() {
93 public void valueChanged(int changeType) {
97 document.getRocket().addComponentChangeListener(new ComponentChangeListener() {
99 public void componentChanged(ComponentChangeEvent e) {
106 * Update the state of all of the actions.
108 private void updateActions() {
109 deleteAction.clipboardChanged();
110 cutAction.clipboardChanged();
111 copyAction.clipboardChanged();
112 pasteAction.clipboardChanged();
113 editAction.clipboardChanged();
114 newStageAction.clipboardChanged();
115 moveUpAction.clipboardChanged();
116 moveDownAction.clipboardChanged();
122 public Action getDeleteComponentAction() {
126 public Action getDeleteSimulationAction() {
130 public Action getDeleteAction() {
134 public Action getCutAction() {
138 public Action getCopyAction() {
142 public Action getPasteAction() {
146 public Action getEditAction() {
150 public Action getNewStageAction() {
151 return newStageAction;
154 public Action getMoveUpAction() {
158 public Action getMoveDownAction() {
159 return moveDownAction;
164 //////// Helper methods for the actions
166 private boolean isDeletable(RocketComponent c) {
168 if (c == null || c.getParent() == null)
171 // Cannot remove Rocket
172 if (c instanceof Rocket)
175 // Cannot remove last stage
176 if ((c instanceof Stage) && (c.getParent().getChildCount() == 1)) {
183 private void delete(RocketComponent c) {
184 if (!isDeletable(c)) {
185 throw new IllegalArgumentException("Report bug! Component " + c +
189 RocketComponent parent = c.getParent();
190 parent.removeChild(c);
193 private boolean isCopyable(RocketComponent c) {
196 if (c instanceof Rocket)
202 private boolean isSimulationSelected() {
203 Simulation[] selection = selectionModel.getSelectedSimulations();
204 return (selection != null && selection.length > 0);
209 private boolean verifyDeleteSimulation() {
210 boolean verify = Prefs.NODE.getBoolean(Prefs.CONFIRM_DELETE_SIMULATION, true);
212 JPanel panel = new JPanel(new MigLayout());
213 //// Do not ask me again
214 JCheckBox dontAsk = new JCheckBox(trans.get("RocketActions.checkbox.Donotaskmeagain"));
215 panel.add(dontAsk,"wrap");
216 //// You can change the default operation in the preferences.
217 panel.add(new StyledLabel(trans.get("RocketActions.lbl.Youcanchangedefop"),-2));
219 int ret = JOptionPane.showConfirmDialog(
222 //// Delete the selected simulations?
223 trans.get("RocketActions.showConfirmDialog.lbl1"),
224 //// <html><i>This operation cannot be undone.</i>
225 trans.get("RocketActions.showConfirmDialog.lbl2"),
228 //// Delete simulations
229 trans.get("RocketActions.showConfirmDialog.title"),
230 JOptionPane.OK_CANCEL_OPTION,
231 JOptionPane.WARNING_MESSAGE);
232 if (ret != JOptionPane.OK_OPTION)
235 if (dontAsk.isSelected()) {
236 Prefs.NODE.putBoolean(Prefs.CONFIRM_DELETE_SIMULATION, false);
245 * Return the component and position to which the current clipboard
246 * should be pasted. Returns null if the clipboard is empty or if the
247 * clipboard cannot be pasted to the current selection.
249 * @param clipboard the component on the clipboard.
250 * @return a Pair with both components defined, or null.
252 private Pair<RocketComponent, Integer> getPastePosition(RocketComponent clipboard) {
253 RocketComponent selected = selectionModel.getSelectedComponent();
254 if (selected == null)
257 if (clipboard == null)
260 if (selected.isCompatible(clipboard))
261 return new Pair<RocketComponent, Integer>(selected, selected.getChildCount());
263 RocketComponent parent = selected.getParent();
264 if (parent != null && parent.isCompatible(clipboard)) {
265 int index = parent.getChildPosition(selected) + 1;
266 return new Pair<RocketComponent, Integer>(parent, index);
276 /////// Action classes
278 private abstract class RocketAction extends AbstractAction implements ClipboardListener {
279 public abstract void clipboardChanged();
284 * Action that deletes the selected component.
286 private class DeleteComponentAction extends RocketAction {
287 public DeleteComponentAction() {
289 this.putValue(NAME, trans.get("RocketActions.DelCompAct.Delete"));
290 //// Delete the selected component.
291 this.putValue(SHORT_DESCRIPTION, trans.get("RocketActions.DelCompAct.ttip.Delete"));
292 this.putValue(MNEMONIC_KEY, KeyEvent.VK_D);
293 // this.putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0));
294 this.putValue(SMALL_ICON, Icons.EDIT_DELETE);
299 public void actionPerformed(ActionEvent e) {
300 RocketComponent c = selectionModel.getSelectedComponent();
302 if (isDeletable(c)) {
303 ComponentConfigDialog.hideDialog();
305 document.addUndoPosition("Delete " + c.getComponentName());
311 public void clipboardChanged() {
312 this.setEnabled(isDeletable(selectionModel.getSelectedComponent()));
319 * Action that deletes the selected component.
321 private class DeleteSimulationAction extends RocketAction {
322 public DeleteSimulationAction() {
324 this.putValue(NAME, trans.get("RocketActions.DelSimuAct.Delete"));
325 //// Delete the selected simulation.
326 this.putValue(SHORT_DESCRIPTION, trans.get("RocketActions.DelSimuAct.ttip.Delete"));
327 this.putValue(MNEMONIC_KEY, KeyEvent.VK_D);
328 // this.putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0));
329 this.putValue(SMALL_ICON, Icons.EDIT_DELETE);
334 public void actionPerformed(ActionEvent e) {
335 Simulation[] sims = selectionModel.getSelectedSimulations();
336 if (sims.length > 0) {
337 if (verifyDeleteSimulation()) {
338 for (Simulation s: sims) {
339 document.removeSimulation(s);
346 public void clipboardChanged() {
347 this.setEnabled(isSimulationSelected());
354 * Action that deletes the selected component.
356 private class DeleteAction extends RocketAction {
357 public DeleteAction() {
359 this.putValue(NAME, trans.get("RocketActions.DelAct.Delete"));
360 //// Delete the selected component or simulation.
361 this.putValue(SHORT_DESCRIPTION, trans.get("RocketActions.DelAct.ttip.Delete"));
362 this.putValue(MNEMONIC_KEY, KeyEvent.VK_D);
363 this.putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0));
364 this.putValue(SMALL_ICON, Icons.EDIT_DELETE);
369 public void actionPerformed(ActionEvent e) {
370 if (isSimulationSelected()) {
371 deleteSimulationAction.actionPerformed(e);
372 parentFrame.selectTab(BasicFrame.SIMULATION_TAB);
374 deleteComponentAction.actionPerformed(e);
375 parentFrame.selectTab(BasicFrame.COMPONENT_TAB);
380 public void clipboardChanged() {
381 this.setEnabled(isDeletable(selectionModel.getSelectedComponent()) ||
382 isSimulationSelected());
389 * Action the cuts the selected component (copies to clipboard and deletes).
391 private class CutAction extends RocketAction {
394 this.putValue(NAME, trans.get("RocketActions.CutAction.Cut"));
395 this.putValue(MNEMONIC_KEY, KeyEvent.VK_T);
396 this.putValue(ACCELERATOR_KEY, CUT_KEY_STROKE);
397 //// Cut this component or simulation to the clipboard and remove from this design
398 this.putValue(SHORT_DESCRIPTION, trans.get("RocketActions.CutAction.ttip.Cut"));
399 this.putValue(SMALL_ICON, Icons.EDIT_CUT);
404 public void actionPerformed(ActionEvent e) {
405 RocketComponent c = selectionModel.getSelectedComponent();
406 Simulation[] sims = selectionModel.getSelectedSimulations();
408 if (isDeletable(c) && isCopyable(c)) {
409 ComponentConfigDialog.hideDialog();
411 document.addUndoPosition("Cut " + c.getComponentName());
412 OpenRocketClipboard.setClipboard(c.copy());
414 parentFrame.selectTab(BasicFrame.COMPONENT_TAB);
415 } else if (isSimulationSelected()) {
417 Simulation[] simsCopy = new Simulation[sims.length];
418 for (int i=0; i < sims.length; i++) {
419 simsCopy[i] = sims[i].copy();
421 OpenRocketClipboard.setClipboard(simsCopy);
423 for (Simulation s: sims) {
424 document.removeSimulation(s);
426 parentFrame.selectTab(BasicFrame.SIMULATION_TAB);
431 public void clipboardChanged() {
432 RocketComponent c = selectionModel.getSelectedComponent();
433 this.setEnabled((isDeletable(c) && isCopyable(c)) || isSimulationSelected());
440 * Action that copies the selected component to the clipboard.
442 private class CopyAction extends RocketAction {
443 public CopyAction() {
445 this.putValue(NAME, trans.get("RocketActions.CopyAct.Copy"));
446 this.putValue(MNEMONIC_KEY, KeyEvent.VK_C);
447 this.putValue(ACCELERATOR_KEY, COPY_KEY_STROKE);
448 //// Copy this component (and subcomponents) to the clipboard.
449 this.putValue(SHORT_DESCRIPTION, trans.get("RocketActions.CopyAct.Copy"));
450 this.putValue(SMALL_ICON, Icons.EDIT_COPY);
455 public void actionPerformed(ActionEvent e) {
456 RocketComponent c = selectionModel.getSelectedComponent();
457 Simulation[] sims = selectionModel.getSelectedSimulations();
460 OpenRocketClipboard.setClipboard(c.copy());
461 parentFrame.selectTab(BasicFrame.COMPONENT_TAB);
462 } else if (sims.length >= 0) {
464 Simulation[] simsCopy = new Simulation[sims.length];
465 for (int i=0; i < sims.length; i++) {
466 simsCopy[i] = sims[i].copy();
468 OpenRocketClipboard.setClipboard(simsCopy);
469 parentFrame.selectTab(BasicFrame.SIMULATION_TAB);
474 public void clipboardChanged() {
475 this.setEnabled(isCopyable(selectionModel.getSelectedComponent()) ||
476 isSimulationSelected());
484 * Action that pastes the current clipboard to the selected position.
485 * It first tries to paste the component to the end of the selected component
486 * as a child, and after that as a sibling after the selected component.
488 private class PasteAction extends RocketAction {
489 public PasteAction() {
491 this.putValue(NAME, trans.get("RocketActions.PasteAct.Paste"));
492 this.putValue(MNEMONIC_KEY, KeyEvent.VK_P);
493 this.putValue(ACCELERATOR_KEY, PASTE_KEY_STROKE);
494 //// Paste the component or simulation on the clipboard to the design.
495 this.putValue(SHORT_DESCRIPTION, trans.get("RocketActions.PasteAct.ttip.Paste"));
496 this.putValue(SMALL_ICON, Icons.EDIT_PASTE);
501 public void actionPerformed(ActionEvent e) {
502 RocketComponent clipboard = OpenRocketClipboard.getClipboardComponent();
503 Simulation[] sims = OpenRocketClipboard.getClipboardSimulations();
505 Pair<RocketComponent, Integer> position = getPastePosition(clipboard);
506 if (position != null) {
507 ComponentConfigDialog.hideDialog();
509 RocketComponent pasted = clipboard.copy();
510 document.addUndoPosition("Paste " + pasted.getComponentName());
511 position.getU().addChild(pasted, position.getV());
512 selectionModel.setSelectedComponent(pasted);
514 parentFrame.selectTab(BasicFrame.COMPONENT_TAB);
516 } else if (sims != null) {
518 ArrayList<Simulation> copySims = new ArrayList<Simulation>();
520 for (Simulation s: sims) {
521 Simulation copy = s.duplicateSimulation(rocket);
522 String name = copy.getName();
523 if (name.matches(OpenRocketDocument.SIMULATION_NAME_PREFIX + "[0-9]+ *")) {
524 copy.setName(document.getNextSimulationName());
526 document.addSimulation(copy);
529 selectionModel.setSelectedSimulations(copySims.toArray(new Simulation[0]));
531 parentFrame.selectTab(BasicFrame.SIMULATION_TAB);
536 public void clipboardChanged() {
538 (getPastePosition(OpenRocketClipboard.getClipboardComponent()) != null) ||
539 (OpenRocketClipboard.getClipboardSimulations() != null));
549 * Action to edit the currently selected component.
551 private class EditAction extends RocketAction {
552 public EditAction() {
554 this.putValue(NAME, trans.get("RocketActions.EditAct.Edit"));
555 //// Edit the selected component.
556 this.putValue(SHORT_DESCRIPTION, trans.get("RocketActions.EditAct.ttip.Edit"));
561 public void actionPerformed(ActionEvent e) {
562 RocketComponent c = selectionModel.getSelectedComponent();
566 ComponentConfigDialog.showDialog(parentFrame, document, c);
570 public void clipboardChanged() {
571 this.setEnabled(selectionModel.getSelectedComponent() != null);
582 * Action to add a new stage to the rocket.
584 private class NewStageAction extends RocketAction {
585 public NewStageAction() {
587 this.putValue(NAME, trans.get("RocketActions.NewStageAct.Newstage"));
588 //// Add a new stage to the rocket design.
589 this.putValue(SHORT_DESCRIPTION, trans.get("RocketActions.NewStageAct.Newstage"));
594 public void actionPerformed(ActionEvent e) {
596 ComponentConfigDialog.hideDialog();
598 RocketComponent stage = new Stage();
600 stage.setName(trans.get("RocketActions.ActBoosterstage"));
602 document.addUndoPosition("Add stage");
603 rocket.addChild(stage);
604 rocket.getDefaultConfiguration().setAllStages();
605 selectionModel.setSelectedComponent(stage);
606 ComponentConfigDialog.showDialog(parentFrame, document, stage);
611 public void clipboardChanged() {
612 this.setEnabled(true);
620 * Action to move the selected component upwards in the parent's child list.
622 private class MoveUpAction extends RocketAction {
623 public MoveUpAction() {
625 this.putValue(NAME, trans.get("RocketActions.MoveUpAct.Moveup"));
626 //// Move this component upwards.
627 this.putValue(SHORT_DESCRIPTION, trans.get("RocketActions.MoveUpAct.ttip.Moveup"));
632 public void actionPerformed(ActionEvent e) {
633 RocketComponent selected = selectionModel.getSelectedComponent();
634 if (!canMove(selected))
637 ComponentConfigDialog.hideDialog();
639 RocketComponent parent = selected.getParent();
640 document.addUndoPosition("Move "+selected.getComponentName());
641 parent.moveChild(selected, parent.getChildPosition(selected)-1);
642 selectionModel.setSelectedComponent(selected);
646 public void clipboardChanged() {
647 this.setEnabled(canMove(selectionModel.getSelectedComponent()));
650 private boolean canMove(RocketComponent c) {
651 if (c == null || c.getParent() == null)
653 RocketComponent parent = c.getParent();
654 if (parent.getChildPosition(c) > 0)
663 * Action to move the selected component down in the parent's child list.
665 private class MoveDownAction extends RocketAction {
666 public MoveDownAction() {
668 this.putValue(NAME, trans.get("RocketActions.MoveDownAct.Movedown"));
669 //// Move this component downwards.
670 this.putValue(SHORT_DESCRIPTION, trans.get("RocketActions.MoveDownAct.ttip.Movedown"));
675 public void actionPerformed(ActionEvent e) {
676 RocketComponent selected = selectionModel.getSelectedComponent();
677 if (!canMove(selected))
680 ComponentConfigDialog.hideDialog();
682 RocketComponent parent = selected.getParent();
683 document.addUndoPosition("Move "+selected.getComponentName());
684 parent.moveChild(selected, parent.getChildPosition(selected)+1);
685 selectionModel.setSelectedComponent(selected);
689 public void clipboardChanged() {
690 this.setEnabled(canMove(selectionModel.getSelectedComponent()));
693 private boolean canMove(RocketComponent c) {
694 if (c == null || c.getParent() == null)
696 RocketComponent parent = c.getParent();
697 if (parent.getChildPosition(c) < parent.getChildCount()-1)