cf458724344b770483d96a12decd627fff37285e
[debian/openrocket] / core / src / net / sf / openrocket / gui / scalefigure / RocketPanel.java
1 package net.sf.openrocket.gui.scalefigure;
2
3
4 import java.awt.Dimension;
5 import java.awt.Font;
6 import java.awt.Point;
7 import java.awt.event.ActionEvent;
8 import java.awt.event.InputEvent;
9 import java.awt.event.MouseEvent;
10 import java.util.ArrayList;
11 import java.util.Collection;
12 import java.util.EventListener;
13 import java.util.EventObject;
14 import java.util.List;
15 import java.util.concurrent.Executor;
16 import java.util.concurrent.Executors;
17 import java.util.concurrent.ThreadFactory;
18
19 import javax.swing.AbstractAction;
20 import javax.swing.Action;
21 import javax.swing.JComboBox;
22 import javax.swing.JLabel;
23 import javax.swing.JPanel;
24 import javax.swing.JSlider;
25 import javax.swing.JToggleButton;
26 import javax.swing.JViewport;
27 import javax.swing.SwingUtilities;
28 import javax.swing.event.TreeSelectionEvent;
29 import javax.swing.event.TreeSelectionListener;
30 import javax.swing.tree.TreePath;
31 import javax.swing.tree.TreeSelectionModel;
32
33 import net.miginfocom.swing.MigLayout;
34 import net.sf.openrocket.aerodynamics.AerodynamicCalculator;
35 import net.sf.openrocket.aerodynamics.BarrowmanCalculator;
36 import net.sf.openrocket.aerodynamics.FlightConditions;
37 import net.sf.openrocket.aerodynamics.WarningSet;
38 import net.sf.openrocket.document.OpenRocketDocument;
39 import net.sf.openrocket.document.Simulation;
40 import net.sf.openrocket.gui.adaptors.DoubleModel;
41 import net.sf.openrocket.gui.adaptors.MotorConfigurationModel;
42 import net.sf.openrocket.gui.components.BasicSlider;
43 import net.sf.openrocket.gui.components.StageSelector;
44 import net.sf.openrocket.gui.components.UnitSelector;
45 import net.sf.openrocket.gui.configdialog.ComponentConfigDialog;
46 import net.sf.openrocket.gui.figureelements.CGCaret;
47 import net.sf.openrocket.gui.figureelements.CPCaret;
48 import net.sf.openrocket.gui.figureelements.Caret;
49 import net.sf.openrocket.gui.figureelements.RocketInfo;
50 import net.sf.openrocket.gui.main.SimulationWorker;
51 import net.sf.openrocket.gui.main.componenttree.ComponentTreeModel;
52 import net.sf.openrocket.gui.util.SwingPreferences;
53 import net.sf.openrocket.l10n.Translator;
54 import net.sf.openrocket.masscalc.BasicMassCalculator;
55 import net.sf.openrocket.masscalc.MassCalculator;
56 import net.sf.openrocket.masscalc.MassCalculator.MassCalcType;
57 import net.sf.openrocket.rocketcomponent.Configuration;
58 import net.sf.openrocket.rocketcomponent.Rocket;
59 import net.sf.openrocket.rocketcomponent.RocketComponent;
60 import net.sf.openrocket.rocketcomponent.SymmetricComponent;
61 import net.sf.openrocket.simulation.FlightData;
62 import net.sf.openrocket.simulation.listeners.SimulationListener;
63 import net.sf.openrocket.simulation.listeners.system.ApogeeEndListener;
64 import net.sf.openrocket.simulation.listeners.system.InterruptListener;
65 import net.sf.openrocket.startup.Application;
66 import net.sf.openrocket.unit.UnitGroup;
67 import net.sf.openrocket.util.ChangeSource;
68 import net.sf.openrocket.util.Chars;
69 import net.sf.openrocket.util.Coordinate;
70 import net.sf.openrocket.util.MathUtil;
71 import net.sf.openrocket.util.StateChangeListener;
72
73 /**
74  * A JPanel that contains a RocketFigure and buttons to manipulate the figure. 
75  * 
76  * @author Sampo Niskanen <sampo.niskanen@iki.fi>
77  */
78 public class RocketPanel extends JPanel implements TreeSelectionListener, ChangeSource {
79         
80         private static final Translator trans = Application.getTranslator();
81         private final RocketFigure figure;
82         private final ScaleScrollPane scrollPane;
83         
84         private JLabel infoMessage;
85         
86         private TreeSelectionModel selectionModel = null;
87         
88
89         /* Calculation of CP and CG */
90         private AerodynamicCalculator aerodynamicCalculator;
91         private MassCalculator massCalculator;
92         
93
94         private final OpenRocketDocument document;
95         private final Configuration configuration;
96         
97         private Caret extraCP = null;
98         private Caret extraCG = null;
99         private RocketInfo extraText = null;
100         
101
102         private double cpAOA = Double.NaN;
103         private double cpTheta = Double.NaN;
104         private double cpMach = Double.NaN;
105         private double cpRoll = Double.NaN;
106         
107         // The functional ID of the rocket that was simulated
108         private int flightDataFunctionalID = -1;
109         private String flightDataMotorID = null;
110         
111
112         private SimulationWorker backgroundSimulationWorker = null;
113         
114         private List<EventListener> listeners = new ArrayList<EventListener>();
115         
116
117         /**
118          * The executor service used for running the background simulations.
119          * This uses a fixed-sized thread pool for all background simulations
120          * with all threads in daemon mode and with minimum priority.
121          */
122         private static final Executor backgroundSimulationExecutor;
123         static {
124                 backgroundSimulationExecutor = Executors.newFixedThreadPool(SwingPreferences.getMaxThreadCount(),
125                                 new ThreadFactory() {
126                                         private ThreadFactory factory = Executors.defaultThreadFactory();
127                                         
128                                         @Override
129                                         public Thread newThread(Runnable r) {
130                                                 Thread t = factory.newThread(r);
131                                                 t.setDaemon(true);
132                                                 t.setPriority(Thread.MIN_PRIORITY);
133                                                 return t;
134                                         }
135                                 });
136         }
137         
138         
139         public RocketPanel(OpenRocketDocument document) {
140                 
141                 this.document = document;
142                 configuration = document.getDefaultConfiguration();
143                 
144                 // TODO: FUTURE: calculator selection
145                 aerodynamicCalculator = new BarrowmanCalculator();
146                 massCalculator = new BasicMassCalculator();
147                 
148                 // Create figure and custom scroll pane
149                 figure = new RocketFigure(configuration);
150                 
151                 scrollPane = new ScaleScrollPane(figure) {
152                         @Override
153                         public void mouseClicked(MouseEvent event) {
154                                 handleMouseClick(event);
155                         }
156                 };
157                 scrollPane.getViewport().setScrollMode(JViewport.SIMPLE_SCROLL_MODE);
158                 scrollPane.setFitting(true);
159                 
160                 createPanel();
161                 
162                 configuration.addChangeListener(new StateChangeListener() {
163                         @Override
164                         public void stateChanged(EventObject e) {
165                                 // System.out.println("Configuration changed, calling updateFigure");
166                                 updateExtras();
167                                 figure.updateFigure();
168                         }
169                 });
170         }
171         
172         
173         /**
174          * Creates the layout and components of the panel.
175          */
176         private void createPanel() {
177                 setLayout(new MigLayout("", "[shrink][grow]", "[shrink][shrink][grow][shrink]"));
178                 
179                 setPreferredSize(new Dimension(800, 300));
180                 
181
182                 //// Create toolbar
183                 
184                 // Side/back buttons
185                 FigureTypeAction action = new FigureTypeAction(RocketFigure.TYPE_SIDE);
186                 //// Side view
187                 action.putValue(Action.NAME, trans.get("RocketPanel.FigTypeAct.Sideview"));
188                 //// Side view
189                 action.putValue(Action.SHORT_DESCRIPTION, trans.get("RocketPanel.FigTypeAct.ttip.Sideview"));
190                 JToggleButton toggle = new JToggleButton(action);
191                 add(toggle, "spanx, split");
192                 
193                 action = new FigureTypeAction(RocketFigure.TYPE_BACK);
194                 //// Back view
195                 action.putValue(Action.NAME, trans.get("RocketPanel.FigTypeAct.Backview"));
196                 //// Back view
197                 action.putValue(Action.SHORT_DESCRIPTION, trans.get("RocketPanel.FigTypeAct.ttip.Backview"));
198                 toggle = new JToggleButton(action);
199                 add(toggle, "gap rel");
200                 
201
202                 // Zoom level selector
203                 ScaleSelector scaleSelector = new ScaleSelector(scrollPane);
204                 add(scaleSelector);
205                 
206
207
208                 // Stage selector
209                 StageSelector stageSelector = new StageSelector(configuration);
210                 add(stageSelector, "");
211                 
212
213
214                 // Motor configuration selector
215                 //// Motor configuration:
216                 JLabel label = new JLabel(trans.get("RocketPanel.lbl.Motorcfg"));
217                 label.setHorizontalAlignment(JLabel.RIGHT);
218                 add(label, "growx, right");
219                 add(new JComboBox(new MotorConfigurationModel(configuration)), "wrap");
220                 
221
222
223
224
225                 // Create slider and scroll pane
226                 
227                 DoubleModel theta = new DoubleModel(figure, "Rotation",
228                                 UnitGroup.UNITS_ANGLE, 0, 2 * Math.PI);
229                 UnitSelector us = new UnitSelector(theta, true);
230                 us.setHorizontalAlignment(JLabel.CENTER);
231                 add(us, "alignx 50%, growx");
232                 
233                 // Add the rocket figure
234                 add(scrollPane, "grow, spany 2, wmin 300lp, hmin 100lp, wrap");
235                 
236
237                 // Add rotation slider
238                 // Minimum size to fit "360deg"
239                 JLabel l = new JLabel("360" + Chars.DEGREE);
240                 Dimension d = l.getPreferredSize();
241                 
242                 add(new BasicSlider(theta.getSliderModel(0, 2 * Math.PI), JSlider.VERTICAL, true),
243                                 "ax 50%, wrap, width " + (d.width + 6) + "px:null:null, growy");
244                 
245
246                 //// <html>Click to select &nbsp;&nbsp; Shift+click to select other &nbsp;&nbsp; Double-click to edit &nbsp;&nbsp; Click+drag to move
247                 infoMessage = new JLabel(trans.get("RocketPanel.lbl.infoMessage"));
248                 infoMessage.setFont(new Font("Sans Serif", Font.PLAIN, 9));
249                 add(infoMessage, "skip, span, gapleft 25, wrap");
250                 
251
252                 addExtras();
253         }
254         
255         
256
257         public RocketFigure getFigure() {
258                 return figure;
259         }
260         
261         public AerodynamicCalculator getAerodynamicCalculator() {
262                 return aerodynamicCalculator;
263         }
264         
265         public Configuration getConfiguration() {
266                 return configuration;
267         }
268         
269         /**
270          * Get the center of pressure figure element.
271          * 
272          * @return center of pressure info
273          */
274         public Caret getExtraCP() {
275                 return extraCP;
276         }
277         
278         /**
279          * Get the center of gravity figure element.
280          * 
281          * @return center of gravity info
282          */
283         public Caret getExtraCG() {
284                 return extraCG;
285         }
286         
287         /**
288          * Get the extra text figure element.
289          * 
290          * @return extra text that contains info about the rocket design
291          */
292         public RocketInfo getExtraText() {
293                 return extraText;
294         }
295         
296         public void setSelectionModel(TreeSelectionModel m) {
297                 if (selectionModel != null) {
298                         selectionModel.removeTreeSelectionListener(this);
299                 }
300                 selectionModel = m;
301                 selectionModel.addTreeSelectionListener(this);
302                 valueChanged((TreeSelectionEvent) null); // updates FigureParameters
303         }
304         
305         
306
307         /**
308          * Return the angle of attack used in CP calculation.  NaN signifies the default value
309          * of zero.
310          * @return   the angle of attack used, or NaN.
311          */
312         public double getCPAOA() {
313                 return cpAOA;
314         }
315         
316         /**
317          * Set the angle of attack to be used in CP calculation.  A value of NaN signifies that
318          * the default AOA (zero) should be used.
319          * @param aoa   the angle of attack to use, or NaN
320          */
321         public void setCPAOA(double aoa) {
322                 if (MathUtil.equals(aoa, cpAOA) ||
323                                 (Double.isNaN(aoa) && Double.isNaN(cpAOA)))
324                         return;
325                 cpAOA = aoa;
326                 updateExtras();
327                 figure.updateFigure();
328                 fireChangeEvent();
329         }
330         
331         public double getCPTheta() {
332                 return cpTheta;
333         }
334         
335         public void setCPTheta(double theta) {
336                 if (MathUtil.equals(theta, cpTheta) ||
337                                 (Double.isNaN(theta) && Double.isNaN(cpTheta)))
338                         return;
339                 cpTheta = theta;
340                 if (!Double.isNaN(theta))
341                         figure.setRotation(theta);
342                 updateExtras();
343                 figure.updateFigure();
344                 fireChangeEvent();
345         }
346         
347         public double getCPMach() {
348                 return cpMach;
349         }
350         
351         public void setCPMach(double mach) {
352                 if (MathUtil.equals(mach, cpMach) ||
353                                 (Double.isNaN(mach) && Double.isNaN(cpMach)))
354                         return;
355                 cpMach = mach;
356                 updateExtras();
357                 figure.updateFigure();
358                 fireChangeEvent();
359         }
360         
361         public double getCPRoll() {
362                 return cpRoll;
363         }
364         
365         public void setCPRoll(double roll) {
366                 if (MathUtil.equals(roll, cpRoll) ||
367                                 (Double.isNaN(roll) && Double.isNaN(cpRoll)))
368                         return;
369                 cpRoll = roll;
370                 updateExtras();
371                 figure.updateFigure();
372                 fireChangeEvent();
373         }
374         
375         
376
377         @Override
378         public void addChangeListener(EventListener listener) {
379                 listeners.add(0, listener);
380         }
381         
382         @Override
383         public void removeChangeListener(EventListener listener) {
384                 listeners.remove(listener);
385         }
386         
387         protected void fireChangeEvent() {
388                 EventObject e = new EventObject(this);
389                 for (EventListener l : listeners) {
390                         if ( l instanceof StateChangeListener ) {
391                                 ((StateChangeListener)l).stateChanged(e);
392                         }
393                 }
394         }
395         
396         
397
398
399         /**
400          * Handle clicking on figure shapes.  The functioning is the following:
401          * 
402          * Get the components clicked.
403          * If no component is clicked, do nothing.
404          * If the currently selected component is in the set, keep it, 
405          * unless the selector specified is pressed.  If it is pressed, cycle to 
406          * the next component. Otherwise select the first component in the list. 
407          */
408         public static final int CYCLE_SELECTION_MODIFIER = InputEvent.SHIFT_DOWN_MASK;
409         
410         private void handleMouseClick(MouseEvent event) {
411                 if (event.getButton() != MouseEvent.BUTTON1)
412                         return;
413                 Point p0 = event.getPoint();
414                 Point p1 = scrollPane.getViewport().getViewPosition();
415                 int x = p0.x + p1.x;
416                 int y = p0.y + p1.y;
417                 
418                 RocketComponent[] clicked = figure.getComponentsByPoint(x, y);
419                 
420                 // If no component is clicked, do nothing
421                 if (clicked.length == 0)
422                         return;
423                 
424                 // Check whether the currently selected component is in the clicked components.
425                 TreePath path = selectionModel.getSelectionPath();
426                 if (path != null) {
427                         RocketComponent current = (RocketComponent) path.getLastPathComponent();
428                         path = null;
429                         for (int i = 0; i < clicked.length; i++) {
430                                 if (clicked[i] == current) {
431                                         if (event.isShiftDown() && (event.getClickCount() == 1)) {
432                                                 path = ComponentTreeModel.makeTreePath(clicked[(i + 1) % clicked.length]);
433                                         } else {
434                                                 path = ComponentTreeModel.makeTreePath(clicked[i]);
435                                         }
436                                         break;
437                                 }
438                         }
439                 }
440                 
441                 // Currently selected component not clicked
442                 if (path == null) {
443                         if (event.isShiftDown() && event.getClickCount() == 1 && clicked.length > 1) {
444                                 path = ComponentTreeModel.makeTreePath(clicked[1]);
445                         } else {
446                                 path = ComponentTreeModel.makeTreePath(clicked[0]);
447                         }
448                 }
449                 
450                 // Set selection and check for double-click
451                 selectionModel.setSelectionPath(path);
452                 if (event.getClickCount() == 2) {
453                         RocketComponent component = (RocketComponent) path.getLastPathComponent();
454                         
455                         ComponentConfigDialog.showDialog(SwingUtilities.getWindowAncestor(this),
456                                         document, component);
457                 }
458         }
459         
460         
461
462
463         /**
464          * Updates the extra data included in the figure.  Currently this includes
465          * the CP and CG carets.
466          */
467         private WarningSet warnings = new WarningSet();
468         
469         private void updateExtras() {
470                 Coordinate cp, cg;
471                 double cpx, cgx;
472                 
473                 // TODO: MEDIUM: User-definable conditions
474                 FlightConditions conditions = new FlightConditions(configuration);
475                 warnings.clear();
476                 
477                 if (!Double.isNaN(cpMach)) {
478                         conditions.setMach(cpMach);
479                         extraText.setMach(cpMach);
480                 } else {
481                         conditions.setMach(Application.getPreferences().getDefaultMach());
482                         extraText.setMach(Application.getPreferences().getDefaultMach());
483                 }
484                 
485                 if (!Double.isNaN(cpAOA)) {
486                         conditions.setAOA(cpAOA);
487                 } else {
488                         conditions.setAOA(0);
489                 }
490                 extraText.setAOA(cpAOA);
491                 
492                 if (!Double.isNaN(cpRoll)) {
493                         conditions.setRollRate(cpRoll);
494                 } else {
495                         conditions.setRollRate(0);
496                 }
497                 
498                 if (!Double.isNaN(cpTheta)) {
499                         conditions.setTheta(cpTheta);
500                         cp = aerodynamicCalculator.getCP(configuration, conditions, warnings);
501                 } else {
502                         cp = aerodynamicCalculator.getWorstCP(configuration, conditions, warnings);
503                 }
504                 extraText.setTheta(cpTheta);
505                 
506
507                 cg = massCalculator.getCG(configuration, MassCalcType.LAUNCH_MASS);
508                 //              System.out.println("CG computed as "+cg+ " CP as "+cp);
509                 
510                 if (cp.weight > 0.000001)
511                         cpx = cp.x;
512                 else
513                         cpx = Double.NaN;
514                 
515                 if (cg.weight > 0.000001)
516                         cgx = cg.x;
517                 else
518                         cgx = Double.NaN;
519                 
520                 // Length bound is assumed to be tight
521                 double length = 0, diameter = 0;
522                 Collection<Coordinate> bounds = configuration.getBounds();
523                 if (!bounds.isEmpty()) {
524                         double minX = Double.POSITIVE_INFINITY, maxX = Double.NEGATIVE_INFINITY;
525                         for (Coordinate c : bounds) {
526                                 if (c.x < minX)
527                                         minX = c.x;
528                                 if (c.x > maxX)
529                                         maxX = c.x;
530                         }
531                         length = maxX - minX;
532                 }
533                 
534                 for (RocketComponent c : configuration) {
535                         if (c instanceof SymmetricComponent) {
536                                 double d1 = ((SymmetricComponent) c).getForeRadius() * 2;
537                                 double d2 = ((SymmetricComponent) c).getAftRadius() * 2;
538                                 diameter = MathUtil.max(diameter, d1, d2);
539                         }
540                 }
541                 
542                 extraText.setCG(cgx);
543                 extraText.setCP(cpx);
544                 extraText.setLength(length);
545                 extraText.setDiameter(diameter);
546                 extraText.setMass(cg.weight);
547                 extraText.setWarnings(warnings);
548                 
549
550                 if (figure.getType() == RocketFigure.TYPE_SIDE && length > 0) {
551                         
552                         // TODO: LOW: Y-coordinate and rotation
553                         extraCP.setPosition(cpx * RocketFigure.EXTRA_SCALE, 0);
554                         extraCG.setPosition(cgx * RocketFigure.EXTRA_SCALE, 0);
555                         
556                 } else {
557                         
558                         extraCP.setPosition(Double.NaN, Double.NaN);
559                         extraCG.setPosition(Double.NaN, Double.NaN);
560                         
561                 }
562                 
563
564                 ////////  Flight simulation in background
565                 
566                 // Check whether to compute or not
567                 if (!((SwingPreferences) Application.getPreferences()).computeFlightInBackground()) {
568                         extraText.setFlightData(null);
569                         extraText.setCalculatingData(false);
570                         stopBackgroundSimulation();
571                         return;
572                 }
573                 
574                 // Check whether data is already up to date
575                 if (flightDataFunctionalID == configuration.getRocket().getFunctionalModID() &&
576                                 flightDataMotorID == configuration.getMotorConfigurationID()) {
577                         return;
578                 }
579                 
580                 flightDataFunctionalID = configuration.getRocket().getFunctionalModID();
581                 flightDataMotorID = configuration.getMotorConfigurationID();
582                 
583                 // Stop previous computation (if any)
584                 stopBackgroundSimulation();
585                 
586                 // Check that configuration has motors
587                 if (!configuration.hasMotors()) {
588                         extraText.setFlightData(FlightData.NaN_DATA);
589                         extraText.setCalculatingData(false);
590                         return;
591                 }
592                 
593                 // Start calculation process
594                 extraText.setCalculatingData(true);
595                 
596                 Rocket duplicate = (Rocket) configuration.getRocket().copy();
597                 Simulation simulation = ((SwingPreferences)Application.getPreferences()).getBackgroundSimulation(duplicate);
598                 simulation.getOptions().setMotorConfigurationID(
599                                 configuration.getMotorConfigurationID());
600                 
601                 backgroundSimulationWorker = new BackgroundSimulationWorker(simulation);
602                 backgroundSimulationExecutor.execute(backgroundSimulationWorker);
603         }
604         
605         /**
606          * Cancels the current background simulation worker, if any.
607          */
608         private void stopBackgroundSimulation() {
609                 if (backgroundSimulationWorker != null) {
610                         backgroundSimulationWorker.cancel(true);
611                         backgroundSimulationWorker = null;
612                 }
613         }
614         
615         
616         /**
617          * A SimulationWorker that simulates the rocket flight in the background and
618          * sets the results to the extra text when finished.  The worker can be cancelled
619          * if necessary.
620          */
621         private class BackgroundSimulationWorker extends SimulationWorker {
622                 
623                 public BackgroundSimulationWorker(Simulation sim) {
624                         super(sim);
625                 }
626                 
627                 @Override
628                 protected FlightData doInBackground() {
629                         
630                         // Pause a little while to allow faster UI reaction
631                         try {
632                                 Thread.sleep(300);
633                         } catch (InterruptedException ignore) {
634                         }
635                         if (isCancelled() || backgroundSimulationWorker != this)
636                                 return null;
637                         
638                         return super.doInBackground();
639                 }
640                 
641                 @Override
642                 protected void simulationDone() {
643                         // Do nothing if cancelled
644                         if (isCancelled() || backgroundSimulationWorker != this)
645                                 return;
646                         
647                         backgroundSimulationWorker = null;
648                         extraText.setFlightData(simulation.getSimulatedData());
649                         extraText.setCalculatingData(false);
650                         figure.repaint();
651                 }
652                 
653                 @Override
654                 protected SimulationListener[] getExtraListeners() {
655                         return new SimulationListener[] {
656                                         InterruptListener.INSTANCE,
657                                         ApogeeEndListener.INSTANCE };
658                 }
659                 
660                 @Override
661                 protected void simulationInterrupted(Throwable t) {
662                         // Do nothing on cancel, set N/A data otherwise
663                         if (isCancelled() || backgroundSimulationWorker != this) // Double-check
664                                 return;
665                         
666                         backgroundSimulationWorker = null;
667                         extraText.setFlightData(FlightData.NaN_DATA);
668                         extraText.setCalculatingData(false);
669                         figure.repaint();
670                 }
671         }
672         
673         
674
675         /**
676          * Adds the extra data to the figure.  Currently this includes the CP and CG carets.
677          */
678         private void addExtras() {
679                 figure.clearRelativeExtra();
680                 extraCG = new CGCaret(0, 0);
681                 extraCP = new CPCaret(0, 0);
682                 extraText = new RocketInfo(configuration);
683                 updateExtras();
684                 figure.addRelativeExtra(extraCP);
685                 figure.addRelativeExtra(extraCG);
686                 figure.addAbsoluteExtra(extraText);
687         }
688         
689         
690         /**
691          * Updates the selection in the FigureParameters and repaints the figure.  
692          * Ignores the event itself.
693          */
694         @Override
695         public void valueChanged(TreeSelectionEvent e) {
696                 TreePath[] paths = selectionModel.getSelectionPaths();
697                 if (paths == null) {
698                         figure.setSelection(null);
699                         return;
700                 }
701                 
702                 RocketComponent[] components = new RocketComponent[paths.length];
703                 for (int i = 0; i < paths.length; i++)
704                         components[i] = (RocketComponent) paths[i].getLastPathComponent();
705                 figure.setSelection(components);
706         }
707         
708         
709
710         /**
711          * An <code>Action</code> that shows whether the figure type is the type
712          * given in the constructor.
713          * 
714          * @author Sampo Niskanen <sampo.niskanen@iki.fi>
715          */
716         private class FigureTypeAction extends AbstractAction implements StateChangeListener {
717                 private final int type;
718                 
719                 public FigureTypeAction(int type) {
720                         this.type = type;
721                         stateChanged(null);
722                         figure.addChangeListener(this);
723                 }
724                 
725                 @Override
726                 public void actionPerformed(ActionEvent e) {
727                         boolean state = (Boolean) getValue(Action.SELECTED_KEY);
728                         if (state == true) {
729                                 // This view has been selected
730                                 figure.setType(type);
731                                 updateExtras();
732                         }
733                         stateChanged(null);
734                 }
735                 
736                 @Override
737                 public void stateChanged(EventObject e) {
738                         putValue(Action.SELECTED_KEY, figure.getType() == type);
739                 }
740         }
741         
742 }