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