1 package net.sf.openrocket.gui.main;
4 import java.awt.Component;
5 import java.awt.Dimension;
6 import java.awt.Rectangle;
7 import java.awt.event.ActionEvent;
8 import java.lang.reflect.Constructor;
10 import javax.swing.Icon;
11 import javax.swing.JButton;
12 import javax.swing.JCheckBox;
13 import javax.swing.JFrame;
14 import javax.swing.JLabel;
15 import javax.swing.JOptionPane;
16 import javax.swing.JPanel;
17 import javax.swing.JViewport;
18 import javax.swing.Scrollable;
19 import javax.swing.SwingConstants;
20 import javax.swing.event.ChangeEvent;
21 import javax.swing.event.ChangeListener;
22 import javax.swing.event.TreeSelectionEvent;
23 import javax.swing.event.TreeSelectionListener;
24 import javax.swing.tree.TreePath;
25 import javax.swing.tree.TreeSelectionModel;
27 import net.miginfocom.swing.MigLayout;
28 import net.sf.openrocket.document.OpenRocketDocument;
29 import net.sf.openrocket.gui.ResizeLabel;
30 import net.sf.openrocket.gui.configdialog.ComponentConfigDialog;
31 import net.sf.openrocket.rocketcomponent.BodyComponent;
32 import net.sf.openrocket.rocketcomponent.BodyTube;
33 import net.sf.openrocket.rocketcomponent.Bulkhead;
34 import net.sf.openrocket.rocketcomponent.CenteringRing;
35 import net.sf.openrocket.rocketcomponent.EllipticalFinSet;
36 import net.sf.openrocket.rocketcomponent.EngineBlock;
37 import net.sf.openrocket.rocketcomponent.FreeformFinSet;
38 import net.sf.openrocket.rocketcomponent.InnerTube;
39 import net.sf.openrocket.rocketcomponent.LaunchLug;
40 import net.sf.openrocket.rocketcomponent.MassComponent;
41 import net.sf.openrocket.rocketcomponent.NoseCone;
42 import net.sf.openrocket.rocketcomponent.Parachute;
43 import net.sf.openrocket.rocketcomponent.RocketComponent;
44 import net.sf.openrocket.rocketcomponent.ShockCord;
45 import net.sf.openrocket.rocketcomponent.Streamer;
46 import net.sf.openrocket.rocketcomponent.Transition;
47 import net.sf.openrocket.rocketcomponent.TrapezoidFinSet;
48 import net.sf.openrocket.rocketcomponent.TubeCoupler;
49 import net.sf.openrocket.util.Prefs;
52 * A component that contains addition buttons to add different types of rocket components
53 * to a rocket. It enables and disables buttons according to the current selection of a
56 * @author Sampo Niskanen <sampo.niskanen@iki.fi>
59 public class ComponentAddButtons extends JPanel implements Scrollable {
61 private static final int ROWS = 3;
62 private static final int MAXCOLS = 6;
63 private static final String BUTTONPARAM = "grow, sizegroup buttons";
65 private static final int GAP = 5;
66 private static final int EXTRASPACE = 0;
68 private final ComponentButton[][] buttons;
70 private final OpenRocketDocument document;
71 private final TreeSelectionModel selectionModel;
72 private final JViewport viewport;
73 private final MigLayout layout;
75 private final int width, height;
78 public ComponentAddButtons(OpenRocketDocument document, TreeSelectionModel model,
82 String constaint = "[min!]";
83 for (int i=1; i<MAXCOLS; i++)
84 constaint = constaint + GAP + "[min!]";
86 layout = new MigLayout("fill",constaint);
88 this.document = document;
89 this.selectionModel = model;
90 this.viewport = viewport;
92 buttons = new ComponentButton[ROWS][];
95 ////////////////////////////////////////////
97 // addButtonRow("Body components",row,
98 // new ComponentButton(NoseCone.class,"Nose cone") {
100 // public boolean isAddable(RocketComponent c) {
101 // if (!(c instanceof ComponentAssembly))
103 // if (c.getSiblingCount() == 0)
108 // new BodyComponentButton(BodyTube.class,"Body tube"),
109 // new BodyComponentButton(null,"Transition"));
112 addButtonRow("Body components and fin sets",row,
113 new BodyComponentButton(NoseCone.class,"Nose cone"),
114 new BodyComponentButton(BodyTube.class,"Body tube"),
115 new BodyComponentButton(Transition.class,"Transition"),
116 new FinButton(TrapezoidFinSet.class,"Trapezoidal"), // TODO: MEDIUM: freer fin placing
117 new FinButton(EllipticalFinSet.class,"Elliptical"),
118 new FinButton(FreeformFinSet.class,"Freeform"),
119 new FinButton(LaunchLug.class,"Launch lug")
126 /////////////////////////////////////////////
128 addButtonRow("Inner component",row,
129 new ComponentButton(InnerTube.class, "Inner tube"),
130 new ComponentButton(TubeCoupler.class, "Coupler"),
131 new ComponentButton(CenteringRing.class, "Centering\nring"),
132 new ComponentButton(Bulkhead.class, "Bulkhead"),
133 new ComponentButton(EngineBlock.class, "Engine\nblock"));
137 ////////////////////////////////////////////
139 addButtonRow("Mass objects",row,
140 new ComponentButton(Parachute.class, "Parachute"),
141 new ComponentButton(Streamer.class, "Streamer"),
142 new ComponentButton(ShockCord.class, "Shock cord"),
143 // new ComponentButton("Motor clip"),
144 // new ComponentButton("Payload"),
145 new ComponentButton(MassComponent.class,"Mass\ncomponent")
149 // Get maximum button size
152 for (row=0; row < buttons.length; row++) {
153 for (int col=0; col < buttons[row].length; col++) {
154 Dimension d = buttons[row][col].getPreferredSize();
162 // Set all buttons to maximum size
163 System.out.println("Setting w="+w+" h="+h);
166 Dimension d = new Dimension(width,height);
167 for (row=0; row < buttons.length; row++) {
168 for (int col=0; col < buttons[row].length; col++) {
169 buttons[row][col].setMinimumSize(d);
170 buttons[row][col].setPreferredSize(d);
171 buttons[row][col].getComponent(0).validate();
175 // Add viewport listener if viewport provided
176 if (viewport != null) {
177 viewport.addChangeListener(new ChangeListener() {
178 private int oldWidth = -1;
179 public void stateChanged(ChangeEvent e) {
180 Dimension d = ComponentAddButtons.this.viewport.getExtentSize();
181 if (d.width != oldWidth) {
189 add(new JPanel(),"grow");
194 * Adds a row of buttons to the panel.
195 * @param label Label placed before the row
196 * @param row Row number
197 * @param b List of ComponentButtons to place on the row
199 private void addButtonRow(String label, int row, ComponentButton ... b) {
201 add(new JLabel(label),"span, gaptop unrel, wrap");
203 add(new JLabel(label),"span, gaptop 0, wrap");
206 buttons[row] = new ComponentButton[b.length];
208 for (int i=0; i<b.length; i++) {
209 buttons[row][col] = b[i];
211 add(b[i],BUTTONPARAM);
213 add(b[i],BUTTONPARAM+", wrap");
220 * Flows the buttons in all rows of the panel. If a button would come too close
221 * to the right edge of the viewport, "newline" is added to its constraints flowing
222 * it to the next line.
224 private void flowButtons() {
230 Dimension d = viewport.getExtentSize();
232 for (int row=0; row < buttons.length; row++) {
234 for (int col=0; col < buttons[row].length; col++) {
236 String param = BUTTONPARAM+",width "+width+"!,height "+height+"!";
238 if (w+EXTRASPACE > d.width) {
239 param = param + ",newline";
242 if (col == buttons[row].length-1)
243 param = param + ",wrap";
244 layout.setComponentConstraints(buttons[row][col], param);
253 * Class for a component button.
255 private class ComponentButton extends JButton implements TreeSelectionListener {
256 protected Class<? extends RocketComponent> componentClass = null;
257 private Constructor<? extends RocketComponent> constructor = null;
259 /** Only label, no icon. */
260 public ComponentButton(String text) {
261 this(text,null,null);
265 * Constructor with icon and label. The icon and label are placed into the button.
266 * The label may contain "\n" as a newline.
268 public ComponentButton(String text, Icon enabled, Icon disabled) {
270 setLayout(new MigLayout("fill, flowy, insets 0, gap 0","",""));
272 add(new JLabel(),"push, sizegroup spacing");
275 if (enabled != null) {
276 JLabel label = new JLabel(enabled);
277 if (disabled != null)
278 label.setDisabledIcon(disabled);
283 String[] l = text.split("\n");
284 for (int i=0; i<l.length; i++) {
285 add(new ResizeLabel(l[i],SwingConstants.CENTER,-3.0f),"growx");
288 add(new JLabel(),"push, sizegroup spacing");
290 valueChanged(null); // Update enabled status
291 selectionModel.addTreeSelectionListener(this);
296 * Main constructor that should be used. The generated component type is specified
297 * and the text. The icons are fetched based on the component type.
299 public ComponentButton(Class<? extends RocketComponent> c, String text) {
300 this(text,ComponentIcons.getLargeIcon(c),ComponentIcons.getLargeDisabledIcon(c));
308 constructor = c.getConstructor();
309 } catch (NoSuchMethodException e) {
310 throw new IllegalArgumentException("Unable to get default "+
311 "constructor for class "+c,e);
317 * Return whether the current component is addable when the component c is selected.
318 * c is null if there is no selection. The default is to use c.isCompatible(class).
320 public boolean isAddable(RocketComponent c) {
323 if (componentClass==null)
325 return c.isCompatible(componentClass);
329 * Return the position to add the component if component c is selected currently.
330 * The first element of the returned array is the RocketComponent to add the component
331 * to, and the second (in any) an Integer telling the position of the component.
332 * A return value of null means that the user cancelled addition of the component.
333 * If the array has only one element, the component is added at the end of the sibling
334 * list. By default returns the end of the currently selected component.
336 * @param c The component currently selected
337 * @return The position to add the new component to, or null if should not add.
339 public Object[] getAdditionPosition(RocketComponent c) {
340 return new Object[] { c };
344 * Updates the enabled status of the button.
345 * TODO: LOW: What about updates to the rocket tree?
347 public void valueChanged(TreeSelectionEvent e) {
352 * Sets the enabled status of the button and all subcomponents.
355 public void setEnabled(boolean enabled) {
356 super.setEnabled(enabled);
357 Component[] c = getComponents();
358 for (int i=0; i<c.length; i++)
359 c[i].setEnabled(enabled);
364 * Update the enabled status of the button.
366 private void updateEnabled() {
367 RocketComponent c=null;
368 TreePath p = selectionModel.getSelectionPath();
370 c = (RocketComponent)p.getLastPathComponent();
371 setEnabled(isAddable(c));
376 protected void fireActionPerformed(ActionEvent event) {
377 super.fireActionPerformed(event);
378 RocketComponent c = null;
379 Integer position = null;
381 TreePath p = selectionModel.getSelectionPath();
383 c = (RocketComponent)p.getLastPathComponent();
385 Object[] pos = getAdditionPosition(c);
386 if (pos==null || pos.length==0) {
391 c = (RocketComponent)pos[0];
393 position = (Integer)pos[1];
398 System.err.println("ERROR: Could not place new component.");
404 if (constructor == null) {
405 System.err.println("ERROR: Construction of type not supported yet.");
409 RocketComponent component;
411 component = (RocketComponent)constructor.newInstance();
412 } catch (Exception e) {
413 throw new RuntimeException("Could not construct new instance of class "+
417 // Next undo position is set by opening the configuration dialog
418 document.addUndoPosition("Add " + component.getComponentName());
421 if (position == null)
422 c.addChild(component);
424 c.addChild(component, position);
426 // Select new component and open config dialog
427 selectionModel.setSelectionPath(ComponentTreeModel.makeTreePath(component));
429 JFrame parent = null;
430 for (Component comp = ComponentAddButtons.this; comp != null;
431 comp = comp.getParent()) {
432 if (comp instanceof JFrame) {
433 parent = (JFrame) comp;
438 ComponentConfigDialog.showDialog(parent, document, component);
443 * A class suitable for BodyComponents. Addition is allowed ...
445 private class BodyComponentButton extends ComponentButton {
447 public BodyComponentButton(Class<? extends RocketComponent> c, String text) {
451 public BodyComponentButton(String text, Icon enabled, Icon disabled) {
452 super(text, enabled, disabled);
455 public BodyComponentButton(String text) {
460 public boolean isAddable(RocketComponent c) {
461 if (super.isAddable(c))
463 if (c instanceof BodyComponent) // Handled separately
469 public Object[] getAdditionPosition(RocketComponent c) {
470 if (super.isAddable(c)) // Handled automatically
471 return super.getAdditionPosition(c);
473 // Handle BodyComponent separately
474 if (!(c instanceof BodyComponent))
476 RocketComponent parent = c.getParent();
477 assert(parent != null);
479 // Check whether to insert between or at the end.
480 // 0 = ask, 1 = in between, 2 = at the end
481 int pos = Prefs.getChoise(Prefs.BODY_COMPONENT_INSERT_POSITION_KEY, 2, 0);
483 if (parent.getChildPosition(c) == parent.getChildCount()-1)
484 pos = 2; // Selected component is the last component
494 // Insert after current position
495 return new Object[] { parent, new Integer(parent.getChildPosition(c)+1) };
497 // Insert at the end of the parent
498 return new Object[] { parent };
500 System.err.println("ERROR: Bad position type: "+pos);
506 private int askPosition() {
507 Object[] options = { "Insert here", "Add to the end", "Cancel" };
509 JPanel panel = new JPanel(new MigLayout());
510 JCheckBox check = new JCheckBox("Do not ask me again");
511 panel.add(check,"wrap");
512 panel.add(new ResizeLabel("You can change the default operation in the " +
515 int sel = JOptionPane.showOptionDialog(null, // parent component
517 "Insert the component after the current component or as the last " +
520 "Select component position", // title
521 JOptionPane.DEFAULT_OPTION, // default selections
522 JOptionPane.QUESTION_MESSAGE, // dialog type
525 options[0]); // initial value
528 case JOptionPane.CLOSED_OPTION:
541 System.err.println("ERROR: JOptionPane returned "+sel);
546 if (check.isSelected()) {
547 // Save the preference
548 Prefs.NODE.putInt(Prefs.BODY_COMPONENT_INSERT_POSITION_KEY, sel);
558 * Class for fin sets, that attach only to BodyTubes.
560 private class FinButton extends ComponentButton {
561 public FinButton(Class<? extends RocketComponent> c, String text) {
565 public FinButton(String text, Icon enabled, Icon disabled) {
566 super(text, enabled, disabled);
569 public FinButton(String text) {
574 public boolean isAddable(RocketComponent c) {
577 return (c.getClass().equals(BodyTube.class));
583 ///////// Scrolling functionality
586 public Dimension getPreferredScrollableViewportSize() {
587 return getPreferredSize();
592 public int getScrollableBlockIncrement(Rectangle visibleRect,
593 int orientation, int direction) {
594 if (orientation == SwingConstants.VERTICAL)
595 return visibleRect.height * 8 / 10;
601 public boolean getScrollableTracksViewportHeight() {
607 public boolean getScrollableTracksViewportWidth() {
613 public int getScrollableUnitIncrement(Rectangle visibleRect,
614 int orientation, int direction) {