b949b2de0a540038610f989aa5d33d1cdfe6fb48
[debian/openrocket] / src / net / sf / openrocket / gui / configdialog / FinSetConfig.java
1 package net.sf.openrocket.gui.configdialog;
2
3 import java.awt.event.ActionEvent;
4 import java.awt.event.ActionListener;
5 import java.util.ArrayList;
6 import java.util.Collections;
7 import java.util.Comparator;
8 import java.util.List;
9
10 import javax.swing.JButton;
11 import javax.swing.JComboBox;
12 import javax.swing.JLabel;
13 import javax.swing.JPanel;
14 import javax.swing.JSpinner;
15 import javax.swing.SwingUtilities;
16
17 import net.miginfocom.swing.MigLayout;
18 import net.sf.openrocket.document.OpenRocketDocument;
19 import net.sf.openrocket.gui.SpinnerEditor;
20 import net.sf.openrocket.gui.adaptors.DoubleModel;
21 import net.sf.openrocket.gui.adaptors.EnumModel;
22 import net.sf.openrocket.gui.components.BasicSlider;
23 import net.sf.openrocket.gui.components.StyledLabel;
24 import net.sf.openrocket.gui.components.StyledLabel.Style;
25 import net.sf.openrocket.gui.components.UnitSelector;
26 import net.sf.openrocket.l10n.Translator;
27 import net.sf.openrocket.logging.LogHelper;
28 import net.sf.openrocket.rocketcomponent.CenteringRing;
29 import net.sf.openrocket.rocketcomponent.Coaxial;
30 import net.sf.openrocket.rocketcomponent.FinSet;
31 import net.sf.openrocket.rocketcomponent.FreeformFinSet;
32 import net.sf.openrocket.rocketcomponent.InnerTube;
33 import net.sf.openrocket.rocketcomponent.RocketComponent;
34 import net.sf.openrocket.startup.Application;
35 import net.sf.openrocket.unit.UnitGroup;
36
37
38 public abstract class FinSetConfig extends RocketComponentConfig {
39         private static final LogHelper log = Application.getLogger();
40         private static final Translator trans = Application.getTranslator();
41         
42         private JButton split = null;
43         
44         public FinSetConfig(OpenRocketDocument d, RocketComponent component) {
45                 super(d, component);
46                 
47                 //// Fin tabs and Through-the-wall fin tabs
48                 tabbedPane.insertTab(trans.get("FinSetConfig.tab.Fintabs"), null, finTabPanel(),
49                                 trans.get("FinSetConfig.tab.Through-the-wall"), 0);
50         }
51         
52         
53         protected void addFinSetButtons() {
54                 JButton convert = null;
55                 
56                 //// Convert buttons
57                 if (!(component instanceof FreeformFinSet)) {
58                         //// Convert to freeform
59                         convert = new JButton(trans.get("FinSetConfig.but.Converttofreeform"));
60                         //// Convert this fin set into a freeform fin set
61                         convert.setToolTipText(trans.get("FinSetConfig.but.Converttofreeform.ttip"));
62                         convert.addActionListener(new ActionListener() {
63                                 @Override
64                                 public void actionPerformed(ActionEvent e) {
65                                         log.user("Converting " + component.getComponentName() + " into freeform fin set");
66                                         
67                                         // Do change in future for overall safety
68                                         SwingUtilities.invokeLater(new Runnable() {
69                                                 @Override
70                                                 public void run() {
71                                                         //// Convert fin set
72                                                         document.addUndoPosition(trans.get("FinSetConfig.Convertfinset"));
73                                                         RocketComponent freeform =
74                                                                         FreeformFinSet.convertFinSet((FinSet) component);
75                                                         ComponentConfigDialog.showDialog(freeform);
76                                                 }
77                                         });
78                                         
79                                         ComponentConfigDialog.hideDialog();
80                                 }
81                         });
82                 }
83                 
84                 //// Split fins
85                 split = new JButton(trans.get("FinSetConfig.but.Splitfins"));
86                 //// Split the fin set into separate fins
87                 split.setToolTipText(trans.get("FinSetConfig.but.Splitfins.ttip"));
88                 split.addActionListener(new ActionListener() {
89                         @Override
90                         public void actionPerformed(ActionEvent e) {
91                                 log.user("Splitting " + component.getComponentName() + " into separate fins, fin count=" +
92                                                 ((FinSet) component).getFinCount());
93                                 
94                                 // Do change in future for overall safety
95                                 SwingUtilities.invokeLater(new Runnable() {
96                                         @Override
97                                         public void run() {
98                                                 RocketComponent parent = component.getParent();
99                                                 int index = parent.getChildPosition(component);
100                                                 int count = ((FinSet) component).getFinCount();
101                                                 double base = ((FinSet) component).getBaseRotation();
102                                                 if (count <= 1)
103                                                         return;
104                                                 
105                                                 document.addUndoPosition("Split fin set");
106                                                 parent.removeChild(index);
107                                                 for (int i = 0; i < count; i++) {
108                                                         FinSet copy = (FinSet) component.copy();
109                                                         copy.setFinCount(1);
110                                                         copy.setBaseRotation(base + i * 2 * Math.PI / count);
111                                                         copy.setName(copy.getName() + " #" + (i + 1));
112                                                         parent.addChild(copy, index + i);
113                                                 }
114                                         }
115                                 });
116                                 
117                                 ComponentConfigDialog.hideDialog();
118                         }
119                 });
120                 split.setEnabled(((FinSet) component).getFinCount() > 1);
121                 
122                 if (convert == null)
123                         addButtons(split);
124                 else
125                         addButtons(split, convert);
126                 
127         }
128         
129         public JPanel finTabPanel() {
130                 JPanel panel = new JPanel(
131                                 new MigLayout("align 50% 20%, fillx, gap rel unrel, ins 20lp 10% 20lp 10%",
132                                                 "[150lp::][65lp::][30lp::][200lp::]", ""));
133                 //              JPanel panel = new JPanel(new MigLayout("fillx, align 20% 20%, gap rel unrel",
134                 //                              "[40lp][80lp::][30lp::][100lp::]",""));
135                 
136                 //// Through-the-wall fin tabs:
137                 panel.add(new StyledLabel(trans.get("FinSetConfig.lbl.Through-the-wall"), Style.BOLD),
138                                 "spanx, wrap 30lp");
139                 
140                 JLabel label;
141                 DoubleModel length;
142                 DoubleModel length2;
143                 DoubleModel length_2;
144                 JSpinner spin;
145                 JButton autoCalc;
146                 
147                 length = new DoubleModel(component, "Length", UnitGroup.UNITS_LENGTH, 0);
148                 length2 = new DoubleModel(component, "Length", 0.5, UnitGroup.UNITS_LENGTH, 0);
149                 length_2 = new DoubleModel(component, "Length", -0.5, UnitGroup.UNITS_LENGTH, 0);
150                 
151                 register(length);
152                 register(length2);
153                 register(length_2);
154                 
155                 ////  Tab length
156                 //// Tab length:
157                 label = new JLabel(trans.get("FinSetConfig.lbl.Tablength"));
158                 //// The length of the fin tab.
159                 label.setToolTipText(trans.get("FinSetConfig.ttip.Tablength"));
160                 panel.add(label, "gapleft para, gapright 40lp, growx 1");
161                 
162                 final DoubleModel mtl = new DoubleModel(component, "TabLength", UnitGroup.UNITS_LENGTH, 0);
163                 
164                 spin = new JSpinner(mtl.getSpinnerModel());
165                 spin.setEditor(new SpinnerEditor(spin));
166                 panel.add(spin, "growx 1");
167                 
168                 panel.add(new UnitSelector(mtl), "growx 1");
169                 panel.add(new BasicSlider(mtl.getSliderModel(DoubleModel.ZERO, length)),
170                                 "w 100lp, growx 5, wrap");
171                 
172
173                 ////  Tab length
174                 //// Tab height:
175                 label = new JLabel(trans.get("FinSetConfig.lbl.Tabheight"));
176                 //// The spanwise height of the fin tab.
177                 label.setToolTipText(trans.get("FinSetConfig.ttip.Tabheight"));
178                 panel.add(label, "gapleft para");
179                 
180                 final DoubleModel mth = new DoubleModel(component, "TabHeight", UnitGroup.UNITS_LENGTH, 0);
181                 
182                 spin = new JSpinner(mth.getSpinnerModel());
183                 spin.setEditor(new SpinnerEditor(spin));
184                 panel.add(spin, "growx");
185                 
186                 panel.add(new UnitSelector(mth), "growx");
187                 panel.add(new BasicSlider(mth.getSliderModel(DoubleModel.ZERO, length2)),
188                                 "w 100lp, growx 5, wrap");
189                 
190                 ////  Tab position:
191                 label = new JLabel(trans.get("FinSetConfig.lbl.Tabposition"));
192                 //// The position of the fin tab.
193                 label.setToolTipText(trans.get("FinSetConfig.ttip.Tabposition"));
194                 panel.add(label, "gapleft para");
195                 
196                 final DoubleModel mts = new DoubleModel(component, "TabShift", UnitGroup.UNITS_LENGTH);
197                 
198                 spin = new JSpinner(mts.getSpinnerModel());
199                 spin.setEditor(new SpinnerEditor(spin));
200                 panel.add(spin, "growx");
201                 
202                 panel.add(new UnitSelector(mts), "growx");
203                 panel.add(new BasicSlider(mts.getSliderModel(length_2, length2)), "w 100lp, growx 5, wrap");
204                 
205
206                 //// relative to
207                 label = new JLabel(trans.get("FinSetConfig.lbl.relativeto"));
208                 panel.add(label, "right, gapright unrel");
209                 
210                 final EnumModel<FinSet.TabRelativePosition> em =
211                                 new EnumModel<FinSet.TabRelativePosition>(component, "TabRelativePosition");
212                 
213                 panel.add(new JComboBox(em), "spanx 3, growx, wrap para");
214                 
215
216                 // Calculate fin tab height, length, and position
217                 autoCalc = new JButton(trans.get("FinSetConfig.but.AutoCalc"));
218                 
219                 autoCalc.addActionListener(new ActionListener() {
220                         @Override
221                         public void actionPerformed(ActionEvent e) {
222                                 log.user("Computing " + component.getComponentName() + " tab height.");
223                                 
224                                 RocketComponent parent = component.getParent();
225                                 if (parent instanceof Coaxial) {
226                                         try {
227                                                 document.startUndo("Compute fin tabs");
228                                                 
229                                                 List<RocketComponent> children = parent.getChildren();
230                                                 List<CenteringRing> rings = new ArrayList<CenteringRing>();
231                                                 
232                                                 for (int i = 0; i < children.size(); i++) {
233                                                         RocketComponent rocketComponent = children.get(i);
234                                                         if (rocketComponent instanceof InnerTube) {
235                                                                 InnerTube it = (InnerTube) rocketComponent;
236                                                                 if (it.isMotorMount()) {
237                                                                         double depth = ((Coaxial) parent).getOuterRadius() - it.getOuterRadius();
238                                                                         //Set fin tab depth
239                                                                         if (depth >= 0.0d) {
240                                                                                 mth.setValue(depth);
241                                                                                 mth.setCurrentUnit(UnitGroup.UNITS_LENGTH.getDefaultUnit());
242                                                                         }
243                                                                 }
244                                                         } else if (rocketComponent instanceof CenteringRing) {
245                                                                 rings.add((CenteringRing) rocketComponent);
246                                                         }
247                                                 }
248                                                 //Figure out position and length of the fin tab
249                                                 if (!rings.isEmpty()) {
250                                                         FinSet.TabRelativePosition temp = (FinSet.TabRelativePosition) em.getSelectedItem();
251                                                         em.setSelectedItem(FinSet.TabRelativePosition.FRONT);
252                                                         double len = computeFinTabLength(rings, component.asPositionValue(RocketComponent.Position.TOP),
253                                                                                 component.getLength(), mts);
254                                                         mtl.setValue(len);
255                                                         //Be nice to the user and set the tab relative position enum back the way they had it.
256                                                         em.setSelectedItem(temp);
257                                                 }
258                                                 
259                                         } finally {
260                                                 document.stopUndo();
261                                         }
262                                 }
263                         }
264                 });
265                 panel.add(autoCalc, "skip 1, spanx");
266                 
267                 return panel;
268         }
269         
270         /**
271          * Scenarios:
272          * <p/>
273          * 1. All rings ahead of start of fin.
274          * 2. First ring ahead of start of fin.  Second ring ahead of end of fin.
275          * 3. First ring ahead of start of fin.  Second ring behind end of fin.
276          * 4. First ring equal or behind start of fin.  Second ring ahead of, or equal to, end of fin.
277          * 5. First ring equal or behind start of fin. Second ring behind end of fin.
278          * 6. All rings behind end of fin.
279          *
280          * @param rings              an unordered list of centering rings attached to the parent of the fin set
281          * @param finPositionFromTop the position from the top of the parent of the start of the fin set root
282          * @param finLength          the length of the root chord
283          * @param mts                the model for the tab shift (position); the model's value is modified as a result of this method call
284          * @return the length of the fin tab
285          */
286         private static double computeFinTabLength(List<CenteringRing> rings, Double finPositionFromTop, Double finLength, DoubleModel mts) {
287                 List<SortableRing> positionsFromTop = new ArrayList<SortableRing>();
288                 
289                 //Fin tabs will be computed between the last two rings that meet the criteria, represented by top and bottom here.
290                 SortableRing top = null;
291                 SortableRing bottom = null;
292                 
293                 if (rings != null) {
294                         //Sort rings from top of parent to bottom
295                         Collections.sort(rings, new Comparator<CenteringRing>() {
296                                 @Override
297                                 public int compare(CenteringRing centeringRing, CenteringRing centeringRing1) {
298                                         return (int) (1000d * (centeringRing.asPositionValue(RocketComponent.Position.TOP) -
299                                                         centeringRing1.asPositionValue(RocketComponent.Position.TOP)));
300                                                 }
301                         });
302                         
303                         for (int i = 0; i < rings.size(); i++) {
304                                 CenteringRing centeringRing = rings.get(i);
305                                 //Handle centering rings that overlap or are adjacent by synthetically merging them into one virtual ring.
306                                 if (!positionsFromTop.isEmpty() &&
307                                                 positionsFromTop.get(positionsFromTop.size() - 1).bottomSidePositionFromTop() >= centeringRing.asPositionValue(RocketComponent.Position.TOP)) {
308                                         SortableRing adjacent = positionsFromTop.get(positionsFromTop.size() - 1);
309                                         adjacent.merge(centeringRing);
310                                 } else {
311                                         positionsFromTop.add(new SortableRing(centeringRing));
312                                 }
313                         }
314                         
315                         for (int i = 0; i < positionsFromTop.size(); i++) {
316                                 SortableRing sortableRing = positionsFromTop.get(i);
317                                 if (top == null) {
318                                         top = sortableRing;
319                                 } else if (sortableRing.bottomSidePositionFromTop() <= finPositionFromTop) {
320                                         top = sortableRing;
321                                         bottom = null;
322                                 } else if (top.bottomSidePositionFromTop() <= finPositionFromTop) {
323                                         if (bottom == null) {
324                                                 //If the current ring is in the upper half of the root chord, make it the top ring
325                                                 if (sortableRing.bottomSidePositionFromTop() < finPositionFromTop + finLength / 2d) {
326                                                         top = sortableRing;
327                                                 } else {
328                                                         bottom = sortableRing;
329                                                 }
330                                         }
331                                         //Is the ring even with or above the end of the root chord? If so, make the existing bottom the top ring,
332                                         //and the current ring the bottom
333                                         else if (sortableRing.positionFromTop() <= finPositionFromTop + finLength) {
334                                                 top = bottom;
335                                                 bottom = sortableRing;
336                                         }
337                                 } else {
338                                         if (bottom == null) {
339                                                 bottom = sortableRing;
340                                         }
341                                 }
342                         }
343                 }
344                 
345                 // Edge case where there are no centering rings or for some odd reason top and bottom are identical.
346                 if (top == null || top == bottom) {
347                         mts.setValue(0);
348                         return finLength;
349                 }
350                 
351                 if (bottom == null) {
352                         // If there is no bottom ring and the top ring's bottom edge is within the span of the root chord, then
353                         // set the position of the fin tab starting at the bottom side of the top ring.
354                         if (top.bottomSidePositionFromTop() >= finPositionFromTop) {
355                                 mts.setValue(top.bottomSidePositionFromTop() - finPositionFromTop);
356                                 return (finPositionFromTop + finLength - top.bottomSidePositionFromTop());
357                         } else {
358                                 // Otherwise the top ring is outside the span of the root chord so set the tab length to be the entire
359                                 // root chord.
360                                 mts.setValue(0);
361                                 return finLength;
362                         }
363                 }
364                 // If the bottom edge of the top centering ring is above the start of the fin's root chord, then make the
365                 // fin tab align with the start of the root chord.
366                 if (top.bottomSidePositionFromTop() < finPositionFromTop) {
367                         mts.setValue(0);
368                         return bottom.positionFromTop - finPositionFromTop;
369                 } else {
370                         // Otherwise the rings are within the span of the root chord.  Place the tab between them.
371                         mts.setValue(top.bottomSidePositionFromTop() - finPositionFromTop);
372                         return (bottom.positionFromTop() - top.bottomSidePositionFromTop());
373                 }
374         }
375         
376         @Override
377         public void updateFields() {
378                 super.updateFields();
379                 if (split != null)
380                         split.setEnabled(((FinSet) component).getFinCount() > 1);
381         }
382         
383         /**
384          * A container class to store pertinent info about centering rings.  This is used in the computation to figure
385          * out tab length and position.
386          */
387         static class SortableRing {
388                 
389                 /**
390                  * The length of the ring (more commonly called the thickness).
391                  */
392                 private double thickness;
393                 /**
394                  * The position of the ring from the top of the parent.
395                  */
396                 private double positionFromTop;
397                 
398                 /**
399                  * Constructor.
400                  *
401                  * @param r the source centering ring
402                  */
403                 SortableRing(CenteringRing r) {
404                         thickness = r.getLength();
405                         positionFromTop = r.asPositionValue(RocketComponent.Position.TOP);
406                 }
407                 
408                 /**
409                  * Merge an adjacent ring.
410                  *
411                  * @param adjacent the adjacent ring
412                  */
413                 public void merge(CenteringRing adjacent) {
414                         double v = adjacent.asPositionValue(RocketComponent.Position.TOP);
415                         if (positionFromTop < v) {
416                                 thickness = (v + adjacent.getLength()) - positionFromTop;
417                         } else {
418                                 double tmp = positionFromTop + thickness;
419                                 positionFromTop = v;
420                                 thickness = tmp - v;
421                         }
422                 }
423                 
424                 /**
425                  * Compute the position of the bottom edge of the ring, relative to the top of the parent.
426                  *
427                  * @return the distance from the top of the parent to the bottom edge of the ring
428                  */
429                 public double bottomSidePositionFromTop() {
430                         return positionFromTop + thickness;
431                 }
432                 
433                 /**
434                  * Compute the position of the top edge of the ring, relative to the top of the parent.
435                  *
436                  * @return the distance from the top of the parent to the top edge of the ring
437                  */
438                 public double positionFromTop() {
439                         return positionFromTop;
440                 }
441         }
442 }