package net.sf.openrocket.gui.configdialog;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.geom.Point2D;
import java.io.IOException;
import java.util.List;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFileChooser;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSeparator;
import javax.swing.JSpinner;
import javax.swing.JTable;
import javax.swing.ListSelectionModel;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.table.AbstractTableModel;
import net.miginfocom.swing.MigLayout;
import net.sf.openrocket.document.OpenRocketDocument;
import net.sf.openrocket.gui.SpinnerEditor;
import net.sf.openrocket.gui.adaptors.DoubleModel;
import net.sf.openrocket.gui.adaptors.EnumModel;
import net.sf.openrocket.gui.adaptors.IntegerModel;
import net.sf.openrocket.gui.components.BasicSlider;
import net.sf.openrocket.gui.components.DescriptionArea;
import net.sf.openrocket.gui.components.StyledLabel;
import net.sf.openrocket.gui.components.UnitSelector;
import net.sf.openrocket.gui.dialogs.ScaleDialog;
import net.sf.openrocket.gui.scalefigure.FinPointFigure;
import net.sf.openrocket.gui.scalefigure.ScaleScrollPane;
import net.sf.openrocket.gui.scalefigure.ScaleSelector;
import net.sf.openrocket.gui.util.CustomFinImporter;
import net.sf.openrocket.gui.util.FileHelper;
import net.sf.openrocket.gui.util.SwingPreferences;
import net.sf.openrocket.l10n.Translator;
import net.sf.openrocket.logging.Markers;
import net.sf.openrocket.material.Material;
import net.sf.openrocket.rocketcomponent.FinSet;
import net.sf.openrocket.rocketcomponent.FreeformFinSet;
import net.sf.openrocket.rocketcomponent.IllegalFinPointException;
import net.sf.openrocket.rocketcomponent.RocketComponent;
import net.sf.openrocket.startup.Application;
import net.sf.openrocket.unit.UnitGroup;
import net.sf.openrocket.util.Coordinate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class FreeformFinSetConfig extends FinSetConfig {
private static final Logger log = LoggerFactory.getLogger(FreeformFinSetConfig.class);
private static final Translator trans = Application.getTranslator();
private final FreeformFinSet finset;
private JTable table = null;
private FinPointTableModel tableModel = null;
private FinPointFigure figure = null;
public FreeformFinSetConfig(OpenRocketDocument d, RocketComponent component) {
super(d, component);
this.finset = (FreeformFinSet) component;
//// General and General properties
tabbedPane.insertTab(trans.get("FreeformFinSetCfg.tab.General"), null, generalPane(), trans.get("FreeformFinSetCfg.tab.ttip.General"), 0);
//// Shape and Fin shape
tabbedPane.insertTab(trans.get("FreeformFinSetCfg.tab.Shape"), null, shapePane(), trans.get("FreeformFinSetCfg.tab.ttip.Finshape"), 1);
tabbedPane.setSelectedIndex(0);
addFinSetButtons();
}
private JPanel generalPane() {
DoubleModel m;
JSpinner spin;
JComboBox combo;
JPanel mainPanel = new JPanel(new MigLayout("fill"));
JPanel panel = new JPanel(new MigLayout("fill, gap rel unrel", "[][65lp::][30lp::]", ""));
//// Number of fins:
panel.add(new JLabel(trans.get("FreeformFinSetCfg.lbl.Numberoffins")));
IntegerModel im = new IntegerModel(component, "FinCount", 1, 8);
spin = new JSpinner(im.getSpinnerModel());
spin.setEditor(new SpinnerEditor(spin));
panel.add(spin, "growx, wrap");
//// Base rotation
panel.add(new JLabel(trans.get("FreeformFinSetCfg.lbl.Finrotation")));
m = new DoubleModel(component, "BaseRotation", UnitGroup.UNITS_ANGLE);
spin = new JSpinner(m.getSpinnerModel());
spin.setEditor(new SpinnerEditor(spin));
panel.add(spin, "growx");
panel.add(new UnitSelector(m), "growx");
panel.add(new BasicSlider(m.getSliderModel(-Math.PI, Math.PI)), "w 100lp, wrap");
//// Fin cant
JLabel label = new JLabel(trans.get("FreeformFinSetCfg.lbl.Fincant"));
//// The angle that the fins are canted with respect to the rocket body.
label.setToolTipText(trans.get("FreeformFinSetCfg.lbl.ttip.Fincant"));
panel.add(label);
m = new DoubleModel(component, "CantAngle", UnitGroup.UNITS_ANGLE, -FinSet.MAX_CANT, FinSet.MAX_CANT);
spin = new JSpinner(m.getSpinnerModel());
spin.setEditor(new SpinnerEditor(spin));
panel.add(spin, "growx");
panel.add(new UnitSelector(m), "growx");
panel.add(new BasicSlider(m.getSliderModel(-FinSet.MAX_CANT, FinSet.MAX_CANT)), "w 100lp, wrap 40lp");
//// Position
//// Position relative to:
panel.add(new JLabel(trans.get("FreeformFinSetCfg.lbl.Posrelativeto")));
combo = new JComboBox(new EnumModel<RocketComponent.Position>(component, "RelativePosition", new RocketComponent.Position[] { RocketComponent.Position.TOP, RocketComponent.Position.MIDDLE,
RocketComponent.Position.BOTTOM, RocketComponent.Position.ABSOLUTE }));
panel.add(combo, "spanx 3, growx, wrap");
//// plus
panel.add(new JLabel(trans.get("FreeformFinSetCfg.lbl.plus")), "right");
m = new DoubleModel(component, "PositionValue", UnitGroup.UNITS_LENGTH);
spin = new JSpinner(m.getSpinnerModel());
spin.setEditor(new SpinnerEditor(spin));
panel.add(spin, "growx");
panel.add(new UnitSelector(m), "growx");
panel.add(new BasicSlider(m.getSliderModel(new DoubleModel(component.getParent(), "Length", -1.0, UnitGroup.UNITS_NONE), new DoubleModel(component.getParent(), "Length"))), "w 100lp, wrap");
mainPanel.add(panel, "aligny 20%");
mainPanel.add(new JSeparator(SwingConstants.VERTICAL), "growy, height 150lp");
panel = new JPanel(new MigLayout("gap rel unrel", "[][65lp::][30lp::]", ""));
//// Cross section
//// Fin cross section:
panel.add(new JLabel(trans.get("FreeformFinSetCfg.lbl.FincrossSection")), "span, split");
combo = new JComboBox(new EnumModel<FinSet.CrossSection>(component, "CrossSection"));
panel.add(combo, "growx, wrap unrel");
//// Thickness:
panel.add(new JLabel(trans.get("FreeformFinSetCfg.lbl.Thickness")));
m = new DoubleModel(component, "Thickness", UnitGroup.UNITS_LENGTH, 0);
spin = new JSpinner(m.getSpinnerModel());
spin.setEditor(new SpinnerEditor(spin));
panel.add(spin, "growx");
panel.add(new UnitSelector(m), "growx");
panel.add(new BasicSlider(m.getSliderModel(0, 0.01)), "w 100lp, wrap 30lp");
//// Material
panel.add(materialPanel(Material.Type.BULK), "span, wrap");
panel.add(filletMaterialPanel(), "span, wrap");
mainPanel.add(panel, "aligny 20%");
return mainPanel;
}
private JPanel shapePane() {
JPanel panel = new JPanel(new MigLayout("fill"));
// Create the figure
figure = new FinPointFigure(finset);
ScaleScrollPane figurePane = new FinPointScrollPane();
figurePane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
figurePane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
// Create the table
tableModel = new FinPointTableModel();
table = new JTable(tableModel);
table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
for (int i = 0; i < Columns.values().length; i++) {
table.getColumnModel().getColumn(i).setPreferredWidth(Columns.values()[i].getWidth());
}
JScrollPane tablePane = new JScrollPane(table);
JButton scaleButton = new JButton(trans.get("FreeformFinSetConfig.lbl.scaleFin"));
scaleButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
log.info(Markers.USER_MARKER, "Scaling free-form fin");
ScaleDialog dialog = new ScaleDialog(document, finset, SwingUtilities.getWindowAncestor(FreeformFinSetConfig.this), true);
dialog.setVisible(true);
dialog.dispose();
}
});
// panel.add(new JLabel("Coordinates:"), "aligny bottom, alignx 50%");
// panel.add(new JLabel(" View:"), "wrap, aligny bottom");
panel.add(tablePane, "growy, width 100lp:100lp:, height 100lp:250lp:");
panel.add(figurePane, "gap unrel, spanx, spany 3, growx, growy 1000, height 100lp:250lp:, wrap");
panel.add(new StyledLabel(trans.get("lbl.doubleClick1"), -2), "alignx 50%, wrap");
panel.add(new StyledLabel(trans.get("FreeformFinSetConfig.lbl.doubleClick2"), -2), "alignx 50%, wrap");
panel.add(scaleButton, "spany 2, alignx 50%, aligny 50%");
panel.add(new ScaleSelector(figurePane), "spany 2, aligny 50%");
JButton importButton = new JButton(trans.get("CustomFinImport.button.label"));
importButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
importImage();
}
});
panel.add(importButton, "spany 2, bottom");
// panel.add(new CustomFinBmpImporter(finset), "spany 2, bottom");
panel.add(new StyledLabel(trans.get("FreeformFinSetConfig.lbl.clickDrag"), -2), "right, wrap");
panel.add(new StyledLabel(trans.get("FreeformFinSetConfig.lbl.ctrlClick"), -2), "right");
return panel;
}
private void importImage() {
JFileChooser chooser = new JFileChooser();
chooser.setFileFilter(FileHelper.getImageFileFilter());
chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
chooser.setCurrentDirectory(((SwingPreferences) Application.getPreferences()).getDefaultDirectory());
JPanel desc = new JPanel(new MigLayout("fill, ins 0 para 0 para"));
desc.add(new DescriptionArea(trans.get("CustomFinImport.description"), 5, 0), "grow, wmin 150lp");
chooser.setAccessory(desc);
int option = chooser.showOpenDialog(this);
if (option == JFileChooser.APPROVE_OPTION) {
try {
CustomFinImporter importer = new CustomFinImporter();
List<Coordinate> points = importer.getPoints(chooser.getSelectedFile());
document.startUndo(trans.get("CustomFinImport.undo"));
finset.setPoints(points);
} catch (IllegalFinPointException e) {
log.warn("Error storing fin points", e);
JOptionPane.showMessageDialog(this, trans.get("CustomFinImport.error.badimage"),
trans.get("CustomFinImport.error.title"), JOptionPane.ERROR_MESSAGE);
} catch (IOException e) {
log.warn("Error loading file", e);
JOptionPane.showMessageDialog(this, e.getLocalizedMessage(),
trans.get("CustomFinImport.error.title"), JOptionPane.ERROR_MESSAGE);
} finally {
document.stopUndo();
}
}
}
@Override
public void updateFields() {
super.updateFields();
if (tableModel != null) {
tableModel.fireTableDataChanged();
}
if (figure != null) {
figure.updateFigure();
}
}
private class FinPointScrollPane extends ScaleScrollPane {
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);
private int dragIndex = -1;
public FinPointScrollPane() {
super(figure, false); // Disallow fitting as it's buggy
}
@Override
public void mousePressed(MouseEvent event) {
int mods = event.getModifiersEx();
if (event.getButton() != MouseEvent.BUTTON1 || (mods & ANY_MASK) != 0) {
super.mousePressed(event);
return;
}
int index = getPoint(event);
if (index >= 0) {
dragIndex = index;
return;
}
index = getSegment(event);
if (index >= 0) {
Point2D.Double point = getCoordinates(event);
finset.addPoint(index);
try {
finset.setPoint(index, point.x, point.y);
} catch (IllegalFinPointException ignore) {
}
dragIndex = index;
return;
}
super.mousePressed(event);
return;
}
@Override
public void mouseDragged(MouseEvent event) {
int mods = event.getModifiersEx();
if (dragIndex < 0 || (mods & (ANY_MASK | MouseEvent.BUTTON1_DOWN_MASK)) != MouseEvent.BUTTON1_DOWN_MASK) {
super.mouseDragged(event);
return;
}
Point2D.Double point = getCoordinates(event);
try {
finset.setPoint(dragIndex, point.x, point.y);
} catch (IllegalFinPointException ignore) {
log.debug("Ignoring IllegalFinPointException while dragging, dragIndex=" + dragIndex + " x=" + point.x + " y=" + point.y);
}
}
@Override
public void mouseReleased(MouseEvent event) {
dragIndex = -1;
super.mouseReleased(event);
}
@Override
public void mouseClicked(MouseEvent event) {
int mods = event.getModifiersEx();
if (event.getButton() != MouseEvent.BUTTON1 || (mods & ANY_MASK) != MouseEvent.CTRL_DOWN_MASK) {
super.mouseClicked(event);
return;
}
int index = getPoint(event);
if (index < 0) {
super.mouseClicked(event);
return;
}
try {
finset.removePoint(index);
} catch (IllegalFinPointException ignore) {
}
}
private int getPoint(MouseEvent event) {
Point p0 = event.getPoint();
Point p1 = this.getViewport().getViewPosition();
int x = p0.x + p1.x;
int y = p0.y + p1.y;
return figure.getIndexByPoint(x, y);
}
private int getSegment(MouseEvent event) {
Point p0 = event.getPoint();
Point p1 = this.getViewport().getViewPosition();
int x = p0.x + p1.x;
int y = p0.y + p1.y;
return figure.getSegmentByPoint(x, y);
}
private Point2D.Double getCoordinates(MouseEvent event) {
Point p0 = event.getPoint();
Point p1 = this.getViewport().getViewPosition();
int x = p0.x + p1.x;
int y = p0.y + p1.y;
return figure.convertPoint(x, y);
}
}
private enum Columns {
// NUMBER {
// @Override
// public String toString() {
// return "#";
// }
// @Override
// public String getValue(FreeformFinSet finset, int row) {
// return "" + (row+1) + ".";
// }
// @Override
// public int getWidth() {
// return 10;
// }
// },
X {
@Override
public String toString() {
return "X / " + UnitGroup.UNITS_LENGTH.getDefaultUnit().toString();
}
@Override
public String getValue(FreeformFinSet finset, int row) {
return UnitGroup.UNITS_LENGTH.getDefaultUnit().toString(finset.getFinPoints()[row].x);
}
},
Y {
@Override
public String toString() {
return "Y / " + UnitGroup.UNITS_LENGTH.getDefaultUnit().toString();
}
@Override
public String getValue(FreeformFinSet finset, int row) {
return UnitGroup.UNITS_LENGTH.getDefaultUnit().toString(finset.getFinPoints()[row].y);
}
};
public abstract String getValue(FreeformFinSet finset, int row);
@Override
public abstract String toString();
public int getWidth() {
return 20;
}
}
private class FinPointTableModel extends AbstractTableModel {
@Override
public int getColumnCount() {
return Columns.values().length;
}
@Override
public int getRowCount() {
return finset.getPointCount();
}
@Override
public Object getValueAt(int rowIndex, int columnIndex) {
return Columns.values()[columnIndex].getValue(finset, rowIndex);
}
@Override
public String getColumnName(int columnIndex) {
return Columns.values()[columnIndex].toString();
}
@Override
public boolean isCellEditable(int rowIndex, int columnIndex) {
if (rowIndex == 0 || rowIndex == getRowCount() - 1) {
return (columnIndex == Columns.X.ordinal());
}
return (columnIndex == Columns.X.ordinal() || columnIndex == Columns.Y.ordinal());
}
@Override
public void setValueAt(Object o, int rowIndex, int columnIndex) {
if (!(o instanceof String))
return;
if (rowIndex < 0 || rowIndex >= finset.getFinPoints().length || columnIndex < 0 || columnIndex >= Columns.values().length) {
throw new IllegalArgumentException("Index out of bounds, row=" + rowIndex + " column=" + columnIndex + " fin point count=" + finset.getFinPoints().length);
}
String str = (String) o;
try {
double value = UnitGroup.UNITS_LENGTH.fromString(str);
Coordinate c = finset.getFinPoints()[rowIndex];
if (columnIndex == Columns.X.ordinal())
c = c.setX(value);
else
c = c.setY(value);
finset.setPoint(rowIndex, c.x, c.y);
} catch (NumberFormatException ignore) {
} catch (IllegalFinPointException ignore) {
}
}
}
}