1 package net.sf.openrocket.gui.configdialog;
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;
24 import javax.swing.border.BevelBorder;
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;
35 public class InnerTubeConfig extends ThicknessRingComponentConfig {
36 private static final Translator trans = Application.getTranslator();
39 public InnerTubeConfig(OpenRocketDocument d, RocketComponent c) {
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);
50 //// Cluster and Cluster configuration
51 tabbedPane.insertTab(trans.get("InnerTubeCfg.tab.Cluster"), null, tab,
52 trans.get("InnerTubeCfg.tab.ttip.Cluster"), 2);
56 tabbedPane.insertTab(trans.get("InnerTubeCfg.tab.Radialpos"), null, tab,
57 trans.get("InnerTubeCfg.tab.ttip.Radialpos"), 3);
59 tabbedPane.setSelectedIndex(0);
63 private JPanel clusterTab() {
64 JPanel panel = new JPanel(new MigLayout());
66 JPanel subPanel = new JPanel(new MigLayout());
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);
79 subPanel = new JPanel(new MigLayout("gap rel unrel", "[][65lp::][30lp::]"));
81 // Tube separation scale
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"));
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 //// The separation of the tubes, 1.0 = touching each other
92 spin.setToolTipText(trans.get("InnerTubeCfg.lbl.ttip.TubeSep"));
93 subPanel.add(spin, "growx");
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");
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"));
105 dm = new DoubleModel(component, "ClusterRotation", 1, UnitGroup.UNITS_ANGLE,
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");
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");
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() {
131 public void actionPerformed(ActionEvent e) {
132 // Do change in future for overall safety
133 SwingUtilities.invokeLater(new Runnable() {
136 RocketComponent parent = component.getParent();
137 int index = parent.getChildPosition(component);
139 throw new BugException("Inconsistent state: component=" + component +
140 " parent=" + parent + " parent.children=" + parent.getChildren());
143 InnerTube tube = (InnerTube) component;
144 if (tube.getClusterCount() <= 1)
147 document.addUndoPosition("Split cluster");
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);
155 parent.addChild(copy, index + i);
161 subPanel.add(split, "spanx, split 2, gapright para, sizegroup buttons, right");
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() {
171 public void actionPerformed(ActionEvent arg0) {
172 ((InnerTube) component).setClusterScale(1.0);
173 ((InnerTube) component).setClusterRotation(0.0);
176 subPanel.add(reset, "sizegroup buttons, right");
178 panel.add(subPanel, "grow");
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.
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.
192 * @return an instance of an inner tube that represents ONE of the clustered tubes in the cluster represented
193 * by <code>theInnerTube</code>
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);
207 class ClusterSelectionPanel extends JPanel {
208 private static final int BUTTON_SIZE = 50;
209 private static final int MOTOR_DIAMETER = 10;
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;
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 + "!]"));
221 for (int i = 0; i < ClusterConfiguration.CONFIGURATIONS.length; i++) {
222 ClusterConfiguration config = ClusterConfiguration.CONFIGURATIONS[i];
224 JComponent button = new ClusterButton(component, config);
234 private class ClusterButton extends JPanel implements StateChangeListener, MouseListener,
236 private Clusterable component;
237 private ClusterConfiguration config;
239 public ClusterButton(Clusterable c, ClusterConfiguration config) {
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);
253 public void paintComponent(Graphics g) {
254 super.paintComponent(g);
255 Graphics2D g2 = (Graphics2D) g;
256 Rectangle area = g2.getClipBounds();
258 if (component.getClusterConfiguration() == config)
259 g2.setColor(SELECTED_COLOR);
261 g2.setColor(UNSELECTED_COLOR);
263 g2.fillRect(area.x, area.y, area.width, area.height);
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);
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);
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);
282 g2.setColor(MOTOR_FILL_COLOR);
284 g2.setColor(MOTOR_BORDER_COLOR);
291 public void stateChanged(EventObject e) {
297 public void mouseClicked(MouseEvent e) {
298 if (e.getButton() == MouseEvent.BUTTON1) {
299 component.setClusterConfiguration(config);
304 public void mouseEntered(MouseEvent e) {
308 public void mouseExited(MouseEvent e) {
312 public void mousePressed(MouseEvent e) {
316 public void mouseReleased(MouseEvent e) {
321 public void resetModel() {
322 component.removeChangeListener(this);
323 removeMouseListener(this);