create changelog entry
[debian/openrocket] / core / src / net / sf / openrocket / gui / configdialog / InnerTubeConfig.java
1 package net.sf.openrocket.gui.configdialog;
2
3
4 import net.miginfocom.swing.MigLayout;
5 import net.sf.openrocket.document.OpenRocketDocument;
6 import net.sf.openrocket.gui.Resettable;
7 import net.sf.openrocket.gui.SpinnerEditor;
8 import net.sf.openrocket.gui.adaptors.DoubleModel;
9 import net.sf.openrocket.gui.components.BasicSlider;
10 import net.sf.openrocket.gui.components.UnitSelector;
11 import net.sf.openrocket.l10n.Translator;
12 import net.sf.openrocket.rocketcomponent.ClusterConfiguration;
13 import net.sf.openrocket.rocketcomponent.Clusterable;
14 import net.sf.openrocket.rocketcomponent.InnerTube;
15 import net.sf.openrocket.rocketcomponent.MotorMount;
16 import net.sf.openrocket.rocketcomponent.RocketComponent;
17 import net.sf.openrocket.startup.Application;
18 import net.sf.openrocket.unit.UnitGroup;
19 import net.sf.openrocket.util.BugException;
20 import net.sf.openrocket.util.Coordinate;
21 import net.sf.openrocket.util.StateChangeListener;
22
23 import javax.swing.*;
24 import javax.swing.border.BevelBorder;
25 import java.awt.*;
26 import java.awt.event.ActionEvent;
27 import java.awt.event.ActionListener;
28 import java.awt.event.MouseEvent;
29 import java.awt.event.MouseListener;
30 import java.awt.geom.Ellipse2D;
31 import java.util.EventObject;
32 import java.util.List;
33
34
35 public class InnerTubeConfig extends ThicknessRingComponentConfig {
36         private static final Translator trans = Application.getTranslator();
37         
38         
39         public InnerTubeConfig(OpenRocketDocument d, RocketComponent c) {
40                 super(d, c);
41                 
42                 JPanel tab;
43                 
44                 tab = new MotorConfig((MotorMount) c);
45                 //// Motor and Motor mount configuration
46                 tabbedPane.insertTab(trans.get("InnerTubeCfg.tab.Motor"), null, tab,
47                                 trans.get("InnerTubeCfg.tab.ttip.Motor"), 1);
48                 
49                 tab = clusterTab();
50                 //// Cluster and Cluster configuration
51                 tabbedPane.insertTab(trans.get("InnerTubeCfg.tab.Cluster"), null, tab,
52                                 trans.get("InnerTubeCfg.tab.ttip.Cluster"), 2);
53                 
54                 tab = positionTab();
55                 //// Radial position
56                 tabbedPane.insertTab(trans.get("InnerTubeCfg.tab.Radialpos"), null, tab,
57                                 trans.get("InnerTubeCfg.tab.ttip.Radialpos"), 3);
58                 
59                 tabbedPane.setSelectedIndex(0);
60         }
61         
62         
63         private JPanel clusterTab() {
64                 JPanel panel = new JPanel(new MigLayout());
65                 
66                 JPanel subPanel = new JPanel(new MigLayout());
67                 
68                 // Cluster type selection
69                 //// Select cluster configuration:
70                 subPanel.add(new JLabel(trans.get("InnerTubeCfg.lbl.Selectclustercfg")), "spanx, wrap");
71                 subPanel.add(new ClusterSelectionPanel((InnerTube) component), "spanx, wrap");
72                 //              JPanel clusterSelection = new ClusterSelectionPanel((InnerTube)component);
73                 //              clusterSelection.setBackground(Color.blue);
74                 //              subPanel.add(clusterSelection);
75                 
76                 panel.add(subPanel);
77                 
78
79                 subPanel = new JPanel(new MigLayout("gap rel unrel", "[][65lp::][30lp::]"));
80                 
81                 // Tube separation scale
82                 //// Tube separation:
83                 JLabel l = new JLabel(trans.get("InnerTubeCfg.lbl.TubeSep"));
84                 //// The separation of the tubes, 1.0 = touching each other
85                 l.setToolTipText(trans.get("InnerTubeCfg.lbl.ttip.TubeSep"));
86                 subPanel.add(l);
87                 DoubleModel dm = new DoubleModel(component, "ClusterScale", 1, UnitGroup.UNITS_NONE, 0);
88                 
89                 JSpinner spin = new JSpinner(dm.getSpinnerModel());
90                 spin.setEditor(new SpinnerEditor(spin));
91                 //// The separation of the tubes, 1.0 = touching each other
92                 spin.setToolTipText(trans.get("InnerTubeCfg.lbl.ttip.TubeSep"));
93                 subPanel.add(spin, "growx");
94                 
95                 BasicSlider bs = new BasicSlider(dm.getSliderModel(0, 1, 4));
96                 //// The separation of the tubes, 1.0 = touching each other
97                 bs.setToolTipText(trans.get("InnerTubeCfg.lbl.ttip.TubeSep"));
98                 subPanel.add(bs, "skip,w 100lp, wrap");
99                 
100                 // Rotation:
101                 l = new JLabel(trans.get("InnerTubeCfg.lbl.Rotation"));
102                 //// Rotation angle of the cluster configuration
103                 l.setToolTipText(trans.get("InnerTubeCfg.lbl.ttip.Rotation"));
104                 subPanel.add(l);
105                 dm = new DoubleModel(component, "ClusterRotation", 1, UnitGroup.UNITS_ANGLE,
106                                 -Math.PI, Math.PI);
107                 
108                 spin = new JSpinner(dm.getSpinnerModel());
109                 spin.setEditor(new SpinnerEditor(spin));
110                 //// Rotation angle of the cluster configuration
111                 spin.setToolTipText(trans.get("InnerTubeCfg.lbl.ttip.Rotation"));
112                 subPanel.add(spin, "growx");
113                 
114                 subPanel.add(new UnitSelector(dm), "growx");
115                 bs = new BasicSlider(dm.getSliderModel(-Math.PI, 0, Math.PI));
116                 //// Rotation angle of the cluster configuration
117                 bs.setToolTipText(trans.get("InnerTubeCfg.lbl.ttip.Rotation"));
118                 subPanel.add(bs, "w 100lp, wrap para");
119                 
120
121
122                 // Split button
123                 //// Split cluster
124                 JButton split = new JButton(trans.get("InnerTubeCfg.but.Splitcluster"));
125                 //// <html>Split the cluster into separate components.<br>
126                 //// This also duplicates all components attached to this inner tube.
127                 split.setToolTipText(trans.get("InnerTubeCfg.lbl.longA1") +
128                                 trans.get("InnerTubeCfg.lbl.longA2"));
129                 split.addActionListener(new ActionListener() {
130                         @Override
131                         public void actionPerformed(ActionEvent e) {
132                                 // Do change in future for overall safety
133                                 SwingUtilities.invokeLater(new Runnable() {
134                                         @Override
135                                         public void run() {
136                                                 RocketComponent parent = component.getParent();
137                                                 int index = parent.getChildPosition(component);
138                                                 if (index < 0) {
139                                                         throw new BugException("Inconsistent state: component=" + component +
140                                                                         " parent=" + parent + " parent.children=" + parent.getChildren());
141                                                 }
142                                                 
143                                                 InnerTube tube = (InnerTube) component;
144                                                 if (tube.getClusterCount() <= 1)
145                                                         return;
146                                                 
147                                                 document.addUndoPosition("Split cluster");
148                                                 
149                                                 Coordinate[] coords = { Coordinate.NUL };
150                                                 coords = component.shiftCoordinates(coords);
151                                                 parent.removeChild(index);
152                                                 for (int i = 0; i < coords.length; i++) {
153                             InnerTube copy = makeIndividualClusterComponent(coords[i], component.getName() + " #" + (i + 1), component);
154                                                         
155                                                         parent.addChild(copy, index + i);
156                                                 }
157                                         }
158                                 });
159                         }
160                 });
161                 subPanel.add(split, "spanx, split 2, gapright para, sizegroup buttons, right");
162                 
163
164                 // Reset button
165                 ///// Reset settings
166                 JButton reset = new JButton(trans.get("InnerTubeCfg.but.Resetsettings"));
167                 //// Reset the separation and rotation to the default values
168                 reset.setToolTipText(trans.get("InnerTubeCfg.but.ttip.Resetsettings"));
169                 reset.addActionListener(new ActionListener() {
170                         @Override
171                         public void actionPerformed(ActionEvent arg0) {
172                                 ((InnerTube) component).setClusterScale(1.0);
173                                 ((InnerTube) component).setClusterRotation(0.0);
174                         }
175                 });
176                 subPanel.add(reset, "sizegroup buttons, right");
177                 
178                 panel.add(subPanel, "grow");
179                 
180
181                 return panel;
182         }
183
184     /**
185      * For a given coordinate that represents one tube in a cluster, create an instance of that tube.  Must be called
186      * once for each tube in the cluster.
187      *
188      * @param coord        the coordinate of the clustered tube to create
189      * @param splitName    the name of the individual tube
190      * @param theInnerTube the 'parent' from which this tube will be created.
191      *
192      * @return an instance of an inner tube that represents ONE of the clustered tubes in the cluster represented
193      *  by <code>theInnerTube</code>
194      */
195     public static InnerTube makeIndividualClusterComponent(Coordinate coord, String splitName, RocketComponent theInnerTube) {
196         InnerTube copy = (InnerTube) theInnerTube.copy();
197         copy.setClusterConfiguration(ClusterConfiguration.SINGLE);
198         copy.setClusterRotation(0.0);
199         copy.setClusterScale(1.0);
200         copy.setRadialShift(coord.y, coord.z);
201         copy.setName(splitName);
202         return copy;
203     }
204 }
205
206
207 class ClusterSelectionPanel extends JPanel {
208         private static final int BUTTON_SIZE = 50;
209         private static final int MOTOR_DIAMETER = 10;
210         
211         private static final Color SELECTED_COLOR = Color.RED;
212         private static final Color UNSELECTED_COLOR = Color.WHITE;
213         private static final Color MOTOR_FILL_COLOR = Color.GREEN;
214         private static final Color MOTOR_BORDER_COLOR = Color.BLACK;
215         
216         public ClusterSelectionPanel(Clusterable component) {
217                 super(new MigLayout("gap 0 0",
218                                 "[" + BUTTON_SIZE + "!][" + BUTTON_SIZE + "!][" + BUTTON_SIZE + "!][" + BUTTON_SIZE + "!]",
219                                 "[" + BUTTON_SIZE + "!][" + BUTTON_SIZE + "!][" + BUTTON_SIZE + "!]"));
220                 
221                 for (int i = 0; i < ClusterConfiguration.CONFIGURATIONS.length; i++) {
222                         ClusterConfiguration config = ClusterConfiguration.CONFIGURATIONS[i];
223                         
224                         JComponent button = new ClusterButton(component, config);
225                         if (i % 4 == 3)
226                                 add(button, "wrap");
227                         else
228                                 add(button);
229                 }
230                 
231         }
232         
233         
234         private class ClusterButton extends JPanel implements StateChangeListener, MouseListener,
235                                                                                                                         Resettable {
236                 private Clusterable component;
237                 private ClusterConfiguration config;
238                 
239                 public ClusterButton(Clusterable c, ClusterConfiguration config) {
240                         component = c;
241                         this.config = config;
242                         setMinimumSize(new Dimension(BUTTON_SIZE, BUTTON_SIZE));
243                         setPreferredSize(new Dimension(BUTTON_SIZE, BUTTON_SIZE));
244                         setMaximumSize(new Dimension(BUTTON_SIZE, BUTTON_SIZE));
245                         setBorder(BorderFactory.createBevelBorder(BevelBorder.LOWERED));
246                         //                      setBorder(BorderFactory.createLineBorder(Color.BLACK, 1));
247                         component.addChangeListener(this);
248                         addMouseListener(this);
249                 }
250                 
251                 
252                 @Override
253                 public void paintComponent(Graphics g) {
254                         super.paintComponent(g);
255                         Graphics2D g2 = (Graphics2D) g;
256                         Rectangle area = g2.getClipBounds();
257                         
258                         if (component.getClusterConfiguration() == config)
259                                 g2.setColor(SELECTED_COLOR);
260                         else
261                                 g2.setColor(UNSELECTED_COLOR);
262                         
263                         g2.fillRect(area.x, area.y, area.width, area.height);
264                         
265                         g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
266                                         RenderingHints.VALUE_STROKE_NORMALIZE);
267                         g2.setRenderingHint(RenderingHints.KEY_RENDERING,
268                                         RenderingHints.VALUE_RENDER_QUALITY);
269                         g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
270                                         RenderingHints.VALUE_ANTIALIAS_ON);
271                         
272                         List<Double> points = config.getPoints();
273                         Ellipse2D.Float circle = new Ellipse2D.Float();
274                         for (int i = 0; i < points.size() / 2; i++) {
275                                 double x = points.get(i * 2);
276                                 double y = points.get(i * 2 + 1);
277                                 
278                                 double px = BUTTON_SIZE / 2 + x * MOTOR_DIAMETER;
279                                 double py = BUTTON_SIZE / 2 - y * MOTOR_DIAMETER;
280                                 circle.setFrameFromCenter(px, py, px + MOTOR_DIAMETER / 2, py + MOTOR_DIAMETER / 2);
281                                 
282                                 g2.setColor(MOTOR_FILL_COLOR);
283                                 g2.fill(circle);
284                                 g2.setColor(MOTOR_BORDER_COLOR);
285                                 g2.draw(circle);
286                         }
287                 }
288                 
289                 
290                 @Override
291                 public void stateChanged(EventObject e) {
292                         repaint();
293                 }
294                 
295                 
296                 @Override
297                 public void mouseClicked(MouseEvent e) {
298                         if (e.getButton() == MouseEvent.BUTTON1) {
299                                 component.setClusterConfiguration(config);
300                         }
301                 }
302                 
303                 @Override
304                 public void mouseEntered(MouseEvent e) {
305                 }
306                 
307                 @Override
308                 public void mouseExited(MouseEvent e) {
309                 }
310                 
311                 @Override
312                 public void mousePressed(MouseEvent e) {
313                 }
314                 
315                 @Override
316                 public void mouseReleased(MouseEvent e) {
317                 }
318                 
319                 
320                 @Override
321                 public void resetModel() {
322                         component.removeChangeListener(this);
323                         removeMouseListener(this);
324                 }
325         }
326         
327 }