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.Arrays;
17 import java.util.List;
19 import javax.swing.BorderFactory;
20 import javax.swing.JButton;
21 import javax.swing.JComponent;
22 import javax.swing.JLabel;
23 import javax.swing.JPanel;
24 import javax.swing.JSpinner;
25 import javax.swing.SwingUtilities;
26 import javax.swing.border.BevelBorder;
27 import javax.swing.event.ChangeEvent;
28 import javax.swing.event.ChangeListener;
30 import net.miginfocom.swing.MigLayout;
31 import net.sf.openrocket.gui.Resettable;
32 import net.sf.openrocket.gui.SpinnerEditor;
33 import net.sf.openrocket.gui.adaptors.DoubleModel;
34 import net.sf.openrocket.gui.components.BasicSlider;
35 import net.sf.openrocket.gui.components.UnitSelector;
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.unit.UnitGroup;
42 import net.sf.openrocket.util.Coordinate;
45 public class InnerTubeConfig extends ThicknessRingComponentConfig {
48 public InnerTubeConfig(RocketComponent c) {
53 tab = new MotorConfig((MotorMount)c);
54 tabbedPane.insertTab("Motor", null, tab, "Motor mount configuration", 1);
57 tabbedPane.insertTab("Cluster", null, tab, "Cluster configuration", 2);
60 tabbedPane.insertTab("Radial position", null, tab, "Radial position", 3);
62 tabbedPane.setSelectedIndex(0);
66 private JPanel clusterTab() {
67 JPanel panel = new JPanel(new MigLayout());
69 JPanel subPanel = new JPanel(new MigLayout());
71 // Cluster type selection
72 subPanel.add(new JLabel("Select cluster configuration:"),"spanx, wrap");
73 subPanel.add(new ClusterSelectionPanel((InnerTube)component),"spanx, wrap");
74 // JPanel clusterSelection = new ClusterSelectionPanel((InnerTube)component);
75 // clusterSelection.setBackground(Color.blue);
76 // subPanel.add(clusterSelection);
81 subPanel = new JPanel(new MigLayout("gap rel unrel","[][65lp::][30lp::]"));
83 // Tube separation scale
84 JLabel l = new JLabel("Tube separation:");
85 l.setToolTipText("The separation of the tubes, 1.0 = touching each other");
87 DoubleModel dm = new DoubleModel(component,"ClusterScale",1,UnitGroup.UNITS_NONE,0);
89 JSpinner spin = new JSpinner(dm.getSpinnerModel());
90 spin.setEditor(new SpinnerEditor(spin));
91 spin.setToolTipText("The separation of the tubes, 1.0 = touching each other");
92 subPanel.add(spin,"growx");
94 BasicSlider bs = new BasicSlider(dm.getSliderModel(0, 1, 4));
95 bs.setToolTipText("The separation of the tubes, 1.0 = touching each other");
96 subPanel.add(bs,"skip,w 100lp, wrap");
99 l = new JLabel("Rotation:");
100 l.setToolTipText("Rotation angle of the cluster configuration");
102 dm = new DoubleModel(component,"ClusterRotation",1,UnitGroup.UNITS_ANGLE,
105 spin = new JSpinner(dm.getSpinnerModel());
106 spin.setEditor(new SpinnerEditor(spin));
107 spin.setToolTipText("Rotation angle of the cluster configuration");
108 subPanel.add(spin,"growx");
110 subPanel.add(new UnitSelector(dm),"growx");
111 bs = new BasicSlider(dm.getSliderModel(-Math.PI, 0, Math.PI));
112 bs.setToolTipText("Rotation angle of the cluster configuration");
113 subPanel.add(bs,"w 100lp, wrap para");
118 JButton split = new JButton("Split cluster");
119 split.setToolTipText("<html>Split the cluster into separate components.<br>" +
120 "This also duplicates all components attached to this inner tube.");
121 split.addActionListener(new ActionListener() {
123 public void actionPerformed(ActionEvent e) {
124 // Do change in future for overall safety
125 SwingUtilities.invokeLater(new Runnable() {
128 RocketComponent parent = component.getParent();
129 int index = parent.getChildPosition(component);
131 throw new IllegalStateException("component="+component+
132 " parent="+parent+" parent.children=" +
133 Arrays.toString(parent.getChildren()));
136 InnerTube tube = (InnerTube)component;
137 if (tube.getClusterCount() <= 1)
140 ComponentConfigDialog.addUndoPosition("Split cluster");
142 Coordinate[] coords = { Coordinate.NUL };
143 coords = component.shiftCoordinates(coords);
144 parent.removeChild(index);
145 for (int i=0; i<coords.length; i++) {
146 InnerTube copy = (InnerTube)component.copy();
147 copy.setClusterConfiguration(ClusterConfiguration.SINGLE);
148 copy.setClusterRotation(0.0);
149 copy.setClusterScale(1.0);
150 copy.setRadialShift(coords[i].y, coords[i].z);
151 copy.setName(copy.getName() + " #" + (i+1));
153 parent.addChild(copy, index+i);
159 subPanel.add(split, "spanx, split 2, gapright para, sizegroup buttons, right");
163 JButton reset = new JButton("Reset settings");
164 reset.setToolTipText("Reset the separation and rotation to the default values");
165 reset.addActionListener(new ActionListener() {
167 public void actionPerformed(ActionEvent arg0) {
168 ((InnerTube)component).setClusterScale(1.0);
169 ((InnerTube)component).setClusterRotation(0.0);
172 subPanel.add(reset,"sizegroup buttons, right");
174 panel.add(subPanel,"grow");
182 class ClusterSelectionPanel extends JPanel {
183 private static final int BUTTON_SIZE = 50;
184 private static final int MOTOR_DIAMETER = 10;
186 private static final Color SELECTED_COLOR = Color.RED;
187 private static final Color UNSELECTED_COLOR = Color.WHITE;
188 private static final Color MOTOR_FILL_COLOR = Color.GREEN;
189 private static final Color MOTOR_BORDER_COLOR = Color.BLACK;
191 public ClusterSelectionPanel(Clusterable component) {
192 super(new MigLayout("gap 0 0",
193 "["+BUTTON_SIZE+"!]["+BUTTON_SIZE+"!]["+BUTTON_SIZE+"!]["+BUTTON_SIZE+"!]",
194 "["+BUTTON_SIZE+"!]["+BUTTON_SIZE+"!]["+BUTTON_SIZE+"!]"));
196 for (int i=0; i<ClusterConfiguration.CONFIGURATIONS.length; i++) {
197 ClusterConfiguration config = ClusterConfiguration.CONFIGURATIONS[i];
199 JComponent button = new ClusterButton(component,config);
209 private class ClusterButton extends JPanel implements ChangeListener, MouseListener,
211 private Clusterable component;
212 private ClusterConfiguration config;
214 public ClusterButton(Clusterable c, ClusterConfiguration config) {
216 this.config = config;
217 setMinimumSize(new Dimension(BUTTON_SIZE,BUTTON_SIZE));
218 setPreferredSize(new Dimension(BUTTON_SIZE,BUTTON_SIZE));
219 setMaximumSize(new Dimension(BUTTON_SIZE,BUTTON_SIZE));
220 setBorder(BorderFactory.createBevelBorder(BevelBorder.LOWERED));
221 // setBorder(BorderFactory.createLineBorder(Color.BLACK, 1));
222 component.addChangeListener(this);
223 addMouseListener(this);
228 public void paintComponent(Graphics g) {
229 super.paintComponent(g);
230 Graphics2D g2 = (Graphics2D)g;
231 Rectangle area = g2.getClipBounds();
233 if (component.getClusterConfiguration() == config)
234 g2.setColor(SELECTED_COLOR);
236 g2.setColor(UNSELECTED_COLOR);
238 g2.fillRect(area.x, area.y, area.width, area.height);
240 g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
241 RenderingHints.VALUE_STROKE_NORMALIZE);
242 g2.setRenderingHint(RenderingHints.KEY_RENDERING,
243 RenderingHints.VALUE_RENDER_QUALITY);
244 g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
245 RenderingHints.VALUE_ANTIALIAS_ON);
247 List<Double> points = config.getPoints();
248 Ellipse2D.Float circle = new Ellipse2D.Float();
249 for (int i=0; i < points.size()/2; i++) {
250 double x = points.get(i*2);
251 double y = points.get(i*2+1);
253 double px = BUTTON_SIZE/2 + x*MOTOR_DIAMETER;
254 double py = BUTTON_SIZE/2 - y*MOTOR_DIAMETER;
255 circle.setFrameFromCenter(px,py,px+MOTOR_DIAMETER/2,py+MOTOR_DIAMETER/2);
257 g2.setColor(MOTOR_FILL_COLOR);
259 g2.setColor(MOTOR_BORDER_COLOR);
265 public void stateChanged(ChangeEvent e) {
270 public void mouseClicked(MouseEvent e) {
271 if (e.getButton() == MouseEvent.BUTTON1) {
272 component.setClusterConfiguration(config);
276 public void mouseEntered(MouseEvent e) { }
277 public void mouseExited(MouseEvent e) { }
278 public void mousePressed(MouseEvent e) { }
279 public void mouseReleased(MouseEvent e) { }
282 public void resetModel() {
283 component.removeChangeListener(this);
284 removeMouseListener(this);