1 package net.sf.openrocket.gui.configdialog;
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;
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;
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;
55 public class FreeformFinSetConfig extends FinSetConfig {
57 private static final LogHelper log = Application.getLogger();
58 private static final Translator trans = Application.getTranslator();
60 private final FreeformFinSet finset;
61 private JTable table = null;
62 private FinPointTableModel tableModel = null;
64 private FinPointFigure figure = null;
67 public FreeformFinSetConfig(OpenRocketDocument d, RocketComponent component) {
69 this.finset = (FreeformFinSet) component;
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);
82 private JPanel generalPane() {
88 JPanel mainPanel = new JPanel(new MigLayout("fill"));
90 JPanel panel = new JPanel(new MigLayout("fill, gap rel unrel", "[][65lp::][30lp::]", ""));
95 panel.add(new JLabel(trans.get("FreeformFinSetCfg.lbl.Numberoffins")));
97 IntegerModel im = new IntegerModel(component, "FinCount", 1, 8);
99 spin = new JSpinner(im.getSpinnerModel());
100 spin.setEditor(new SpinnerEditor(spin));
101 panel.add(spin, "growx, wrap");
105 panel.add(new JLabel(trans.get("FreeformFinSetCfg.lbl.Finrotation")));
107 m = new DoubleModel(component, "BaseRotation", UnitGroup.UNITS_ANGLE, -Math.PI, Math.PI);
109 spin = new JSpinner(m.getSpinnerModel());
110 spin.setEditor(new SpinnerEditor(spin));
111 panel.add(spin, "growx");
113 panel.add(new UnitSelector(m), "growx");
114 panel.add(new BasicSlider(m.getSliderModel(-Math.PI, Math.PI)), "w 100lp, wrap");
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"));
124 m = new DoubleModel(component, "CantAngle", UnitGroup.UNITS_ANGLE, -FinSet.MAX_CANT, FinSet.MAX_CANT);
126 spin = new JSpinner(m.getSpinnerModel());
127 spin.setEditor(new SpinnerEditor(spin));
128 panel.add(spin, "growx");
130 panel.add(new UnitSelector(m), "growx");
131 panel.add(new BasicSlider(m.getSliderModel(-FinSet.MAX_CANT, FinSet.MAX_CANT)), "w 100lp, wrap 40lp");
136 //// Position relative to:
137 panel.add(new JLabel(trans.get("FreeformFinSetCfg.lbl.Posrelativeto")));
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");
143 panel.add(new JLabel(trans.get("FreeformFinSetCfg.lbl.plus")), "right");
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");
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");
157 mainPanel.add(panel, "aligny 20%");
158 mainPanel.add(new JSeparator(SwingConstants.VERTICAL), "growy, height 150lp");
161 panel = new JPanel(new MigLayout("gap rel unrel", "[][65lp::][30lp::]", ""));
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");
174 panel.add(new JLabel(trans.get("FreeformFinSetCfg.lbl.Thickness")));
176 m = new DoubleModel(component, "Thickness", UnitGroup.UNITS_LENGTH, 0);
178 spin = new JSpinner(m.getSpinnerModel());
179 spin.setEditor(new SpinnerEditor(spin));
180 panel.add(spin, "growx");
182 panel.add(new UnitSelector(m), "growx");
183 panel.add(new BasicSlider(m.getSliderModel(0, 0.01)), "w 100lp, wrap 30lp");
187 materialPanel(panel, Material.Type.BULK);
191 mainPanel.add(panel, "aligny 20%");
198 private JPanel shapePane() {
199 JPanel panel = new JPanel(new MigLayout("fill"));
203 figure = new FinPointFigure(finset);
204 ScaleScrollPane figurePane = new FinPointScrollPane();
205 figurePane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
206 figurePane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
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());
215 JScrollPane tablePane = new JScrollPane(table);
217 JButton scaleButton = new JButton(trans.get("FreeformFinSetConfig.lbl.scaleFin"));
218 scaleButton.addActionListener(new ActionListener() {
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);
228 // panel.add(new JLabel("Coordinates:"), "aligny bottom, alignx 50%");
229 // panel.add(new JLabel(" View:"), "wrap, aligny bottom");
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");
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");
238 panel.add(scaleButton, "spany 2, alignx 50%, aligny 50%");
239 panel.add(new ScaleSelector(figurePane), "spany 2, aligny 50%");
241 JButton importButton = new JButton(trans.get("CustomFinImport.button.label"));
242 importButton.addActionListener(new ActionListener() {
244 public void actionPerformed(ActionEvent e) {
248 panel.add(importButton, "spany 2, bottom");
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");
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());
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);
271 int option = chooser.showOpenDialog(this);
273 if (option == JFileChooser.APPROVE_OPTION) {
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);
296 public void updateFields() {
297 super.updateFields();
299 if (tableModel != null) {
300 tableModel.fireTableDataChanged();
302 if (figure != null) {
303 figure.updateFigure();
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);
313 private int dragIndex = -1;
315 public FinPointScrollPane() {
316 super(figure, false); // Disallow fitting as it's buggy
320 public void mousePressed(MouseEvent event) {
321 int mods = event.getModifiersEx();
323 if (event.getButton() != MouseEvent.BUTTON1 || (mods & ANY_MASK) != 0) {
324 super.mousePressed(event);
328 int index = getPoint(event);
333 index = getSegment(event);
335 Point2D.Double point = getCoordinates(event);
336 finset.addPoint(index);
338 finset.setPoint(index, point.x, point.y);
339 } catch (IllegalFinPointException ignore) {
346 super.mousePressed(event);
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);
358 Point2D.Double point = getCoordinates(event);
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);
369 public void mouseReleased(MouseEvent event) {
371 super.mouseReleased(event);
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);
382 int index = getPoint(event);
384 super.mouseClicked(event);
389 finset.removePoint(index);
390 } catch (IllegalFinPointException ignore) {
395 private int getPoint(MouseEvent event) {
396 Point p0 = event.getPoint();
397 Point p1 = this.getViewport().getViewPosition();
401 return figure.getIndexByPoint(x, y);
404 private int getSegment(MouseEvent event) {
405 Point p0 = event.getPoint();
406 Point p1 = this.getViewport().getViewPosition();
410 return figure.getSegmentByPoint(x, y);
413 private Point2D.Double getCoordinates(MouseEvent event) {
414 Point p0 = event.getPoint();
415 Point p1 = this.getViewport().getViewPosition();
419 return figure.convertPoint(x, y);
429 private enum Columns {
432 // public String toString() {
436 // public String getValue(FreeformFinSet finset, int row) {
437 // return "" + (row+1) + ".";
440 // public int getWidth() {
446 public String toString() {
447 return "X / " + UnitGroup.UNITS_LENGTH.getDefaultUnit().toString();
451 public String getValue(FreeformFinSet finset, int row) {
452 return UnitGroup.UNITS_LENGTH.getDefaultUnit().toString(finset.getFinPoints()[row].x);
457 public String toString() {
458 return "Y / " + UnitGroup.UNITS_LENGTH.getDefaultUnit().toString();
462 public String getValue(FreeformFinSet finset, int row) {
463 return UnitGroup.UNITS_LENGTH.getDefaultUnit().toString(finset.getFinPoints()[row].y);
467 public abstract String getValue(FreeformFinSet finset, int row);
470 public abstract String toString();
472 public int getWidth() {
477 private class FinPointTableModel extends AbstractTableModel {
480 public int getColumnCount() {
481 return Columns.values().length;
485 public int getRowCount() {
486 return finset.getPointCount();
490 public Object getValueAt(int rowIndex, int columnIndex) {
491 return Columns.values()[columnIndex].getValue(finset, rowIndex);
495 public String getColumnName(int columnIndex) {
496 return Columns.values()[columnIndex].toString();
500 public boolean isCellEditable(int rowIndex, int columnIndex) {
501 if (rowIndex == 0 || rowIndex == getRowCount() - 1) {
502 return (columnIndex == Columns.X.ordinal());
505 return (columnIndex == Columns.X.ordinal() || columnIndex == Columns.Y.ordinal());
509 public void setValueAt(Object o, int rowIndex, int columnIndex) {
510 if (!(o instanceof String))
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);
517 String str = (String) o;
520 double value = UnitGroup.UNITS_LENGTH.fromString(str);
521 Coordinate c = finset.getFinPoints()[rowIndex];
522 if (columnIndex == Columns.X.ordinal())
527 finset.setPoint(rowIndex, c.x, c.y);
529 } catch (NumberFormatException ignore) {
530 } catch (IllegalFinPointException ignore) {