1 package net.sf.openrocket.gui.configdialog;
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;
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;
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;
47 public class InnerTubeConfig extends ThicknessRingComponentConfig {
48 private static final Translator trans = Application.getTranslator();
51 public InnerTubeConfig(RocketComponent c) {
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);
62 //// Cluster and Cluster configuration
63 tabbedPane.insertTab(trans.get("InnerTubeCfg.tab.Cluster"), null, tab,
64 trans.get("InnerTubeCfg.tab.ttip.Cluster"), 2);
68 tabbedPane.insertTab(trans.get("InnerTubeCfg.tab.Radialpos"), null, tab,
69 trans.get("InnerTubeCfg.tab.ttip.Radialpos"), 3);
71 tabbedPane.setSelectedIndex(0);
75 private JPanel clusterTab() {
76 JPanel panel = new JPanel(new MigLayout());
78 JPanel subPanel = new JPanel(new MigLayout());
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);
91 subPanel = new JPanel(new MigLayout("gap rel unrel", "[][65lp::][30lp::]"));
93 // Tube separation scale
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"));
99 DoubleModel dm = new DoubleModel(component, "ClusterScale", 1, UnitGroup.UNITS_NONE, 0);
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");
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");
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"));
117 dm = new DoubleModel(component, "ClusterRotation", 1, UnitGroup.UNITS_ANGLE,
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");
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");
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() {
143 public void actionPerformed(ActionEvent e) {
144 // Do change in future for overall safety
145 SwingUtilities.invokeLater(new Runnable() {
148 RocketComponent parent = component.getParent();
149 int index = parent.getChildPosition(component);
151 throw new BugException("Inconsistent state: component=" + component +
152 " parent=" + parent + " parent.children=" + parent.getChildren());
155 InnerTube tube = (InnerTube) component;
156 if (tube.getClusterCount() <= 1)
159 ComponentConfigDialog.addUndoPosition("Split cluster");
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));
172 parent.addChild(copy, index + i);
178 subPanel.add(split, "spanx, split 2, gapright para, sizegroup buttons, right");
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() {
188 public void actionPerformed(ActionEvent arg0) {
189 ((InnerTube) component).setClusterScale(1.0);
190 ((InnerTube) component).setClusterRotation(0.0);
193 subPanel.add(reset, "sizegroup buttons, right");
195 panel.add(subPanel, "grow");
203 class ClusterSelectionPanel extends JPanel {
204 private static final int BUTTON_SIZE = 50;
205 private static final int MOTOR_DIAMETER = 10;
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;
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 + "!]"));
217 for (int i = 0; i < ClusterConfiguration.CONFIGURATIONS.length; i++) {
218 ClusterConfiguration config = ClusterConfiguration.CONFIGURATIONS[i];
220 JComponent button = new ClusterButton(component, config);
230 private class ClusterButton extends JPanel implements ChangeListener, MouseListener,
232 private Clusterable component;
233 private ClusterConfiguration config;
235 public ClusterButton(Clusterable c, ClusterConfiguration config) {
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);
249 public void paintComponent(Graphics g) {
250 super.paintComponent(g);
251 Graphics2D g2 = (Graphics2D) g;
252 Rectangle area = g2.getClipBounds();
254 if (component.getClusterConfiguration() == config)
255 g2.setColor(SELECTED_COLOR);
257 g2.setColor(UNSELECTED_COLOR);
259 g2.fillRect(area.x, area.y, area.width, area.height);
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);
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);
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);
278 g2.setColor(MOTOR_FILL_COLOR);
280 g2.setColor(MOTOR_BORDER_COLOR);
287 public void stateChanged(ChangeEvent e) {
293 public void mouseClicked(MouseEvent e) {
294 if (e.getButton() == MouseEvent.BUTTON1) {
295 component.setClusterConfiguration(config);
300 public void mouseEntered(MouseEvent e) {
304 public void mouseExited(MouseEvent e) {
308 public void mousePressed(MouseEvent e) {
312 public void mouseReleased(MouseEvent e) {
317 public void resetModel() {
318 component.removeChangeListener(this);
319 removeMouseListener(this);