create changelog entry
[debian/openrocket] / core / src / net / sf / openrocket / gui / configdialog / FreeformFinSetConfig.java
1 package net.sf.openrocket.gui.configdialog;
2
3
4 import java.awt.Point;
5 import java.awt.event.ActionEvent;
6 import java.awt.event.ActionListener;
7 import java.awt.event.MouseEvent;
8 import java.awt.geom.Point2D;
9 import java.io.IOException;
10 import java.util.List;
11
12 import javax.swing.JButton;
13 import javax.swing.JComboBox;
14 import javax.swing.JFileChooser;
15 import javax.swing.JLabel;
16 import javax.swing.JOptionPane;
17 import javax.swing.JPanel;
18 import javax.swing.JScrollPane;
19 import javax.swing.JSeparator;
20 import javax.swing.JSpinner;
21 import javax.swing.JTable;
22 import javax.swing.ListSelectionModel;
23 import javax.swing.SwingConstants;
24 import javax.swing.SwingUtilities;
25 import javax.swing.table.AbstractTableModel;
26
27 import net.miginfocom.swing.MigLayout;
28 import net.sf.openrocket.document.OpenRocketDocument;
29 import net.sf.openrocket.gui.SpinnerEditor;
30 import net.sf.openrocket.gui.adaptors.DoubleModel;
31 import net.sf.openrocket.gui.adaptors.EnumModel;
32 import net.sf.openrocket.gui.adaptors.IntegerModel;
33 import net.sf.openrocket.gui.components.BasicSlider;
34 import net.sf.openrocket.gui.components.DescriptionArea;
35 import net.sf.openrocket.gui.components.StyledLabel;
36 import net.sf.openrocket.gui.components.UnitSelector;
37 import net.sf.openrocket.gui.dialogs.ScaleDialog;
38 import net.sf.openrocket.gui.scalefigure.FinPointFigure;
39 import net.sf.openrocket.gui.scalefigure.ScaleScrollPane;
40 import net.sf.openrocket.gui.scalefigure.ScaleSelector;
41 import net.sf.openrocket.gui.util.CustomFinImporter;
42 import net.sf.openrocket.gui.util.FileHelper;
43 import net.sf.openrocket.gui.util.SwingPreferences;
44 import net.sf.openrocket.l10n.Translator;
45 import net.sf.openrocket.logging.LogHelper;
46 import net.sf.openrocket.material.Material;
47 import net.sf.openrocket.rocketcomponent.FinSet;
48 import net.sf.openrocket.rocketcomponent.FreeformFinSet;
49 import net.sf.openrocket.rocketcomponent.IllegalFinPointException;
50 import net.sf.openrocket.rocketcomponent.RocketComponent;
51 import net.sf.openrocket.startup.Application;
52 import net.sf.openrocket.unit.UnitGroup;
53 import net.sf.openrocket.util.Coordinate;
54
55 public class FreeformFinSetConfig extends FinSetConfig {
56         
57         private static final LogHelper log = Application.getLogger();
58         private static final Translator trans = Application.getTranslator();
59         
60         private final FreeformFinSet finset;
61         private JTable table = null;
62         private FinPointTableModel tableModel = null;
63         
64         private FinPointFigure figure = null;
65         
66         
67         public FreeformFinSetConfig(OpenRocketDocument d, RocketComponent component) {
68                 super(d, component);
69                 this.finset = (FreeformFinSet) component;
70                 
71                 //// General and General properties
72                 tabbedPane.insertTab(trans.get("FreeformFinSetCfg.tab.General"), null, generalPane(), trans.get("FreeformFinSetCfg.tab.ttip.General"), 0);
73                 //// Shape and Fin shape
74                 tabbedPane.insertTab(trans.get("FreeformFinSetCfg.tab.Shape"), null, shapePane(), trans.get("FreeformFinSetCfg.tab.ttip.Finshape"), 1);
75                 tabbedPane.setSelectedIndex(0);
76                 
77                 addFinSetButtons();
78         }
79         
80         
81         
82         private JPanel generalPane() {
83                 
84                 DoubleModel m;
85                 JSpinner spin;
86                 JComboBox combo;
87                 
88                 JPanel mainPanel = new JPanel(new MigLayout("fill"));
89                 
90                 JPanel panel = new JPanel(new MigLayout("fill, gap rel unrel", "[][65lp::][30lp::]", ""));
91                 
92                 
93                 
94                 ////  Number of fins:
95                 panel.add(new JLabel(trans.get("FreeformFinSetCfg.lbl.Numberoffins")));
96                 
97                 IntegerModel im = new IntegerModel(component, "FinCount", 1, 8);
98                 
99                 spin = new JSpinner(im.getSpinnerModel());
100                 spin.setEditor(new SpinnerEditor(spin));
101                 panel.add(spin, "growx, wrap");
102                 
103                 
104                 ////  Base rotation
105                 panel.add(new JLabel(trans.get("FreeformFinSetCfg.lbl.Finrotation")));
106                 
107                 m = new DoubleModel(component, "BaseRotation", UnitGroup.UNITS_ANGLE);
108                 
109                 spin = new JSpinner(m.getSpinnerModel());
110                 spin.setEditor(new SpinnerEditor(spin));
111                 panel.add(spin, "growx");
112                 
113                 panel.add(new UnitSelector(m), "growx");
114                 panel.add(new BasicSlider(m.getSliderModel(-Math.PI, Math.PI)), "w 100lp, wrap");
115                 
116                 
117                 
118                 ////  Fin cant
119                 JLabel label = new JLabel(trans.get("FreeformFinSetCfg.lbl.Fincant"));
120                 //// The angle that the fins are canted with respect to the rocket body.
121                 label.setToolTipText(trans.get("FreeformFinSetCfg.lbl.ttip.Fincant"));
122                 panel.add(label);
123                 
124                 m = new DoubleModel(component, "CantAngle", UnitGroup.UNITS_ANGLE, -FinSet.MAX_CANT, FinSet.MAX_CANT);
125                 
126                 spin = new JSpinner(m.getSpinnerModel());
127                 spin.setEditor(new SpinnerEditor(spin));
128                 panel.add(spin, "growx");
129                 
130                 panel.add(new UnitSelector(m), "growx");
131                 panel.add(new BasicSlider(m.getSliderModel(-FinSet.MAX_CANT, FinSet.MAX_CANT)), "w 100lp, wrap 40lp");
132                 
133                 
134                 
135                 ////  Position
136                 //// Position relative to:
137                 panel.add(new JLabel(trans.get("FreeformFinSetCfg.lbl.Posrelativeto")));
138                 
139                 combo = new JComboBox(new EnumModel<RocketComponent.Position>(component, "RelativePosition", new RocketComponent.Position[] { RocketComponent.Position.TOP, RocketComponent.Position.MIDDLE,
140                                 RocketComponent.Position.BOTTOM, RocketComponent.Position.ABSOLUTE }));
141                 panel.add(combo, "spanx 3, growx, wrap");
142                 //// plus
143                 panel.add(new JLabel(trans.get("FreeformFinSetCfg.lbl.plus")), "right");
144                 
145                 m = new DoubleModel(component, "PositionValue", UnitGroup.UNITS_LENGTH);
146                 spin = new JSpinner(m.getSpinnerModel());
147                 spin.setEditor(new SpinnerEditor(spin));
148                 panel.add(spin, "growx");
149                 
150                 panel.add(new UnitSelector(m), "growx");
151                 panel.add(new BasicSlider(m.getSliderModel(new DoubleModel(component.getParent(), "Length", -1.0, UnitGroup.UNITS_NONE), new DoubleModel(component.getParent(), "Length"))), "w 100lp, wrap");
152                 
153                 
154                 
155                 
156                 
157                 mainPanel.add(panel, "aligny 20%");
158                 mainPanel.add(new JSeparator(SwingConstants.VERTICAL), "growy, height 150lp");
159                 
160                 
161                 panel = new JPanel(new MigLayout("gap rel unrel", "[][65lp::][30lp::]", ""));
162                 
163                 
164                 
165                 
166                 ////  Cross section
167                 //// Fin cross section:
168                 panel.add(new JLabel(trans.get("FreeformFinSetCfg.lbl.FincrossSection")), "span, split");
169                 combo = new JComboBox(new EnumModel<FinSet.CrossSection>(component, "CrossSection"));
170                 panel.add(combo, "growx, wrap unrel");
171                 
172                 
173                 ////  Thickness:
174                 panel.add(new JLabel(trans.get("FreeformFinSetCfg.lbl.Thickness")));
175                 
176                 m = new DoubleModel(component, "Thickness", UnitGroup.UNITS_LENGTH, 0);
177                 
178                 spin = new JSpinner(m.getSpinnerModel());
179                 spin.setEditor(new SpinnerEditor(spin));
180                 panel.add(spin, "growx");
181                 
182                 panel.add(new UnitSelector(m), "growx");
183                 panel.add(new BasicSlider(m.getSliderModel(0, 0.01)), "w 100lp, wrap 30lp");
184                 
185                 
186                 //// Material
187                 materialPanel(panel, Material.Type.BULK);
188                 
189                 
190                 
191                 mainPanel.add(panel, "aligny 20%");
192                 
193                 return mainPanel;
194         }
195         
196         
197         
198         private JPanel shapePane() {
199                 JPanel panel = new JPanel(new MigLayout("fill"));
200                 
201                 
202                 // Create the figure
203                 figure = new FinPointFigure(finset);
204                 ScaleScrollPane figurePane = new FinPointScrollPane();
205                 figurePane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
206                 figurePane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
207                 
208                 // Create the table
209                 tableModel = new FinPointTableModel();
210                 table = new JTable(tableModel);
211                 table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
212                 for (int i = 0; i < Columns.values().length; i++) {
213                         table.getColumnModel().getColumn(i).setPreferredWidth(Columns.values()[i].getWidth());
214                 }
215                 JScrollPane tablePane = new JScrollPane(table);
216                 
217                 JButton scaleButton = new JButton(trans.get("FreeformFinSetConfig.lbl.scaleFin"));
218                 scaleButton.addActionListener(new ActionListener() {
219                         @Override
220                         public void actionPerformed(ActionEvent e) {
221                                 log.user("Scaling free-form fin");
222                                 ScaleDialog dialog = new ScaleDialog(document, finset, SwingUtilities.getWindowAncestor(FreeformFinSetConfig.this), true);
223                                 dialog.setVisible(true);
224                                 dialog.dispose();
225                         }
226                 });
227                 
228                 //              panel.add(new JLabel("Coordinates:"), "aligny bottom, alignx 50%");
229                 //              panel.add(new JLabel("    View:"), "wrap, aligny bottom");
230                 
231                 
232                 panel.add(tablePane, "growy, width 100lp:100lp:, height 100lp:250lp:");
233                 panel.add(figurePane, "gap unrel, spanx, spany 3, growx, growy 1000, height 100lp:250lp:, wrap");
234                 
235                 panel.add(new StyledLabel(trans.get("lbl.doubleClick1"), -2), "alignx 50%, wrap");
236                 panel.add(new StyledLabel(trans.get("FreeformFinSetConfig.lbl.doubleClick2"), -2), "alignx 50%, wrap");
237                 
238                 panel.add(scaleButton, "spany 2, alignx 50%, aligny 50%");
239                 panel.add(new ScaleSelector(figurePane), "spany 2, aligny 50%");
240                 
241                 JButton importButton = new JButton(trans.get("CustomFinImport.button.label"));
242                 importButton.addActionListener(new ActionListener() {
243                         @Override
244                         public void actionPerformed(ActionEvent e) {
245                                 importImage();
246                         }
247                 });
248                 panel.add(importButton, "spany 2, bottom");
249                 
250                 //              panel.add(new CustomFinBmpImporter(finset), "spany 2, bottom");
251                 panel.add(new StyledLabel(trans.get("FreeformFinSetConfig.lbl.clickDrag"), -2), "right, wrap");
252                 panel.add(new StyledLabel(trans.get("FreeformFinSetConfig.lbl.ctrlClick"), -2), "right");
253                 
254                 return panel;
255         }
256         
257         
258         
259         
260         
261         private void importImage() {
262                 JFileChooser chooser = new JFileChooser();
263                 chooser.setFileFilter(FileHelper.getImageFileFilter());
264                 chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
265                 chooser.setCurrentDirectory(((SwingPreferences) Application.getPreferences()).getDefaultDirectory());
266                 
267                 JPanel desc = new JPanel(new MigLayout("fill, ins 0 para 0 para"));
268                 desc.add(new DescriptionArea(trans.get("CustomFinImport.description"), 5, 0), "grow, wmin 150lp");
269                 chooser.setAccessory(desc);
270                 
271                 int option = chooser.showOpenDialog(this);
272                 
273                 if (option == JFileChooser.APPROVE_OPTION) {
274                         try {
275                                 CustomFinImporter importer = new CustomFinImporter();
276                                 List<Coordinate> points = importer.getPoints(chooser.getSelectedFile());
277                                 document.startUndo(trans.get("CustomFinImport.undo"));
278                                 finset.setPoints(points);
279                         } catch (IllegalFinPointException e) {
280                                 log.warn("Error storing fin points", e);
281                                 JOptionPane.showMessageDialog(this, trans.get("CustomFinImport.error.badimage"),
282                                                 trans.get("CustomFinImport.error.title"), JOptionPane.ERROR_MESSAGE);
283                         } catch (IOException e) {
284                                 log.warn("Error loading file", e);
285                                 JOptionPane.showMessageDialog(this, e.getLocalizedMessage(),
286                                                 trans.get("CustomFinImport.error.title"), JOptionPane.ERROR_MESSAGE);
287                         } finally {
288                                 document.stopUndo();
289                         }
290                 }
291                 
292         }
293         
294         
295         @Override
296         public void updateFields() {
297                 super.updateFields();
298                 
299                 if (tableModel != null) {
300                         tableModel.fireTableDataChanged();
301                 }
302                 if (figure != null) {
303                         figure.updateFigure();
304                 }
305         }
306         
307         
308         
309         
310         private class FinPointScrollPane extends ScaleScrollPane {
311                 private static final int ANY_MASK = (MouseEvent.ALT_DOWN_MASK | MouseEvent.ALT_GRAPH_DOWN_MASK | MouseEvent.META_DOWN_MASK | MouseEvent.CTRL_DOWN_MASK | MouseEvent.SHIFT_DOWN_MASK);
312                 
313                 private int dragIndex = -1;
314                 
315                 public FinPointScrollPane() {
316                         super(figure, false); // Disallow fitting as it's buggy
317                 }
318                 
319                 @Override
320                 public void mousePressed(MouseEvent event) {
321                         int mods = event.getModifiersEx();
322                         
323                         if (event.getButton() != MouseEvent.BUTTON1 || (mods & ANY_MASK) != 0) {
324                                 super.mousePressed(event);
325                                 return;
326                         }
327                         
328                         int index = getPoint(event);
329                         if (index >= 0) {
330                                 dragIndex = index;
331                                 return;
332                         }
333                         index = getSegment(event);
334                         if (index >= 0) {
335                                 Point2D.Double point = getCoordinates(event);
336                                 finset.addPoint(index);
337                                 try {
338                                         finset.setPoint(index, point.x, point.y);
339                                 } catch (IllegalFinPointException ignore) {
340                                 }
341                                 dragIndex = index;
342                                 
343                                 return;
344                         }
345                         
346                         super.mousePressed(event);
347                         return;
348                 }
349                 
350                 
351                 @Override
352                 public void mouseDragged(MouseEvent event) {
353                         int mods = event.getModifiersEx();
354                         if (dragIndex < 0 || (mods & (ANY_MASK | MouseEvent.BUTTON1_DOWN_MASK)) != MouseEvent.BUTTON1_DOWN_MASK) {
355                                 super.mouseDragged(event);
356                                 return;
357                         }
358                         Point2D.Double point = getCoordinates(event);
359                         
360                         try {
361                                 finset.setPoint(dragIndex, point.x, point.y);
362                         } catch (IllegalFinPointException ignore) {
363                                 log.debug("Ignoring IllegalFinPointException while dragging, dragIndex=" + dragIndex + " x=" + point.x + " y=" + point.y);
364                         }
365                 }
366                 
367                 
368                 @Override
369                 public void mouseReleased(MouseEvent event) {
370                         dragIndex = -1;
371                         super.mouseReleased(event);
372                 }
373                 
374                 @Override
375                 public void mouseClicked(MouseEvent event) {
376                         int mods = event.getModifiersEx();
377                         if (event.getButton() != MouseEvent.BUTTON1 || (mods & ANY_MASK) != MouseEvent.CTRL_DOWN_MASK) {
378                                 super.mouseClicked(event);
379                                 return;
380                         }
381                         
382                         int index = getPoint(event);
383                         if (index < 0) {
384                                 super.mouseClicked(event);
385                                 return;
386                         }
387                         
388                         try {
389                                 finset.removePoint(index);
390                         } catch (IllegalFinPointException ignore) {
391                         }
392                 }
393                 
394                 
395                 private int getPoint(MouseEvent event) {
396                         Point p0 = event.getPoint();
397                         Point p1 = this.getViewport().getViewPosition();
398                         int x = p0.x + p1.x;
399                         int y = p0.y + p1.y;
400                         
401                         return figure.getIndexByPoint(x, y);
402                 }
403                 
404                 private int getSegment(MouseEvent event) {
405                         Point p0 = event.getPoint();
406                         Point p1 = this.getViewport().getViewPosition();
407                         int x = p0.x + p1.x;
408                         int y = p0.y + p1.y;
409                         
410                         return figure.getSegmentByPoint(x, y);
411                 }
412                 
413                 private Point2D.Double getCoordinates(MouseEvent event) {
414                         Point p0 = event.getPoint();
415                         Point p1 = this.getViewport().getViewPosition();
416                         int x = p0.x + p1.x;
417                         int y = p0.y + p1.y;
418                         
419                         return figure.convertPoint(x, y);
420                 }
421                 
422                 
423         }
424         
425         
426         
427         
428         
429         private enum Columns {
430                 //              NUMBER {
431                 //                      @Override
432                 //                      public String toString() {
433                 //                              return "#";
434                 //                      }
435                 //                      @Override
436                 //                      public String getValue(FreeformFinSet finset, int row) {
437                 //                              return "" + (row+1) + ".";
438                 //                      }
439                 //                      @Override
440                 //                      public int getWidth() {
441                 //                              return 10;
442                 //                      }
443                 //              }, 
444                 X {
445                         @Override
446                         public String toString() {
447                                 return "X / " + UnitGroup.UNITS_LENGTH.getDefaultUnit().toString();
448                         }
449                         
450                         @Override
451                         public String getValue(FreeformFinSet finset, int row) {
452                                 return UnitGroup.UNITS_LENGTH.getDefaultUnit().toString(finset.getFinPoints()[row].x);
453                         }
454                 },
455                 Y {
456                         @Override
457                         public String toString() {
458                                 return "Y / " + UnitGroup.UNITS_LENGTH.getDefaultUnit().toString();
459                         }
460                         
461                         @Override
462                         public String getValue(FreeformFinSet finset, int row) {
463                                 return UnitGroup.UNITS_LENGTH.getDefaultUnit().toString(finset.getFinPoints()[row].y);
464                         }
465                 };
466                 
467                 public abstract String getValue(FreeformFinSet finset, int row);
468                 
469                 @Override
470                 public abstract String toString();
471                 
472                 public int getWidth() {
473                         return 20;
474                 }
475         }
476         
477         private class FinPointTableModel extends AbstractTableModel {
478                 
479                 @Override
480                 public int getColumnCount() {
481                         return Columns.values().length;
482                 }
483                 
484                 @Override
485                 public int getRowCount() {
486                         return finset.getPointCount();
487                 }
488                 
489                 @Override
490                 public Object getValueAt(int rowIndex, int columnIndex) {
491                         return Columns.values()[columnIndex].getValue(finset, rowIndex);
492                 }
493                 
494                 @Override
495                 public String getColumnName(int columnIndex) {
496                         return Columns.values()[columnIndex].toString();
497                 }
498                 
499                 @Override
500                 public boolean isCellEditable(int rowIndex, int columnIndex) {
501                         if (rowIndex == 0 || rowIndex == getRowCount() - 1) {
502                                 return (columnIndex == Columns.X.ordinal());
503                         }
504                         
505                         return (columnIndex == Columns.X.ordinal() || columnIndex == Columns.Y.ordinal());
506                 }
507                 
508                 @Override
509                 public void setValueAt(Object o, int rowIndex, int columnIndex) {
510                         if (!(o instanceof String))
511                                 return;
512                         
513                         if (rowIndex < 0 || rowIndex >= finset.getFinPoints().length || columnIndex < 0 || columnIndex >= Columns.values().length) {
514                                 throw new IllegalArgumentException("Index out of bounds, row=" + rowIndex + " column=" + columnIndex + " fin point count=" + finset.getFinPoints().length);
515                         }
516                         
517                         String str = (String) o;
518                         try {
519                                 
520                                 double value = UnitGroup.UNITS_LENGTH.fromString(str);
521                                 Coordinate c = finset.getFinPoints()[rowIndex];
522                                 if (columnIndex == Columns.X.ordinal())
523                                         c = c.setX(value);
524                                 else
525                                         c = c.setY(value);
526                                 
527                                 finset.setPoint(rowIndex, c.x, c.y);
528                                 
529                         } catch (NumberFormatException ignore) {
530                         } catch (IllegalFinPointException ignore) {
531                         }
532                 }
533         }
534 }