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