menu icons, window sizing, compass direction selector
This commit is contained in:
parent
7b352c1f7c
commit
09ce6bb183
@ -1,3 +1,11 @@
|
||||
2011-09-18 Sampo Niskanen
|
||||
|
||||
* Remember window/dialog sizes and/or positions
|
||||
|
||||
2011-09-13 Sampo Niskanen
|
||||
|
||||
* Added icons to help menu
|
||||
|
||||
2011-08-28 Richard Graham
|
||||
|
||||
* Patch for geodetic computations + coriolis effect
|
||||
|
@ -351,8 +351,8 @@ simedtdlg.IntensityDesc.High = High
|
||||
simedtdlg.IntensityDesc.Veryhigh = Very high
|
||||
simedtdlg.IntensityDesc.Extreme = Extreme
|
||||
|
||||
GeodeticComputationStrategy.none.name = None
|
||||
GeodeticComputationStrategy.none.desc = Perform no geodetic computations.
|
||||
GeodeticComputationStrategy.flat.name = Flat Earth
|
||||
GeodeticComputationStrategy.flat.desc = Perform computations with a flat Earth approximation. Sufficient for low-altitude flights.
|
||||
GeodeticComputationStrategy.spherical.name = Spherical approximation
|
||||
GeodeticComputationStrategy.spherical.desc = <html>Perform geodetic computations assuming a spherical Earth.<br>This is sufficiently accurate for almost all purposes.
|
||||
GeodeticComputationStrategy.wgs84.name = WGS84 ellipsoid
|
||||
@ -1501,3 +1501,20 @@ LandingDistanceParameter.name = Landing distance
|
||||
TotalFlightTimeParameter.name = Total flight time
|
||||
DeploymentVelocityParameter.name = Velocity at parachute deployment
|
||||
|
||||
|
||||
! Compass directions drawn on a compass rose.
|
||||
CompassRose.lbl.north = N
|
||||
CompassRose.lbl.east = E
|
||||
CompassRose.lbl.south = S
|
||||
CompassRose.lbl.west = W
|
||||
|
||||
! Compass directions with subdirections. These might not be localized even if the directions on the compass rose are.
|
||||
CompassSelectionButton.lbl.N = N
|
||||
CompassSelectionButton.lbl.NE = NE
|
||||
CompassSelectionButton.lbl.E = E
|
||||
CompassSelectionButton.lbl.SE = SE
|
||||
CompassSelectionButton.lbl.S = S
|
||||
CompassSelectionButton.lbl.SW = SW
|
||||
CompassSelectionButton.lbl.W = W
|
||||
CompassSelectionButton.lbl.NW = NW
|
||||
|
||||
|
@ -30,4 +30,7 @@ delete.png
|
||||
preferences.png
|
||||
zoom-in.png
|
||||
zoom-out.png
|
||||
|
||||
help-license.png
|
||||
help-log.png
|
||||
help-about.png
|
||||
help-bug.png
|
||||
|
BIN
pix/icons/help-about.png
Normal file
BIN
pix/icons/help-about.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.5 KiB |
BIN
pix/icons/help-bug.png
Normal file
BIN
pix/icons/help-bug.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.6 KiB |
BIN
pix/icons/help-license.png
Normal file
BIN
pix/icons/help-license.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.2 KiB |
BIN
pix/icons/help-log.png
Normal file
BIN
pix/icons/help-log.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.3 KiB |
@ -1291,7 +1291,7 @@ class SimulationConditionsHandler extends ElementHandler {
|
||||
public SimulationConditionsHandler(Rocket rocket) {
|
||||
conditions = new SimulationOptions(rocket);
|
||||
// Set up default loading settings (which may differ from the new defaults)
|
||||
conditions.setGeodeticComputation(GeodeticComputationStrategy.NONE);
|
||||
conditions.setGeodeticComputation(GeodeticComputationStrategy.FLAT);
|
||||
}
|
||||
|
||||
public SimulationOptions getConditions() {
|
||||
|
69
src/net/sf/openrocket/gui/components/FlatButton.java
Normal file
69
src/net/sf/openrocket/gui/components/FlatButton.java
Normal file
@ -0,0 +1,69 @@
|
||||
package net.sf.openrocket.gui.components;
|
||||
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
|
||||
import javax.swing.Action;
|
||||
import javax.swing.Icon;
|
||||
import javax.swing.JButton;
|
||||
|
||||
/**
|
||||
* A JButton that appears flat until you roll over it.
|
||||
*
|
||||
* @author Sampo Niskanen <sampo.niskanen@iki.fi>
|
||||
*/
|
||||
public class FlatButton extends JButton {
|
||||
|
||||
public FlatButton() {
|
||||
super();
|
||||
initialize();
|
||||
}
|
||||
|
||||
public FlatButton(Icon icon) {
|
||||
super(icon);
|
||||
initialize();
|
||||
}
|
||||
|
||||
public FlatButton(String text) {
|
||||
super(text);
|
||||
initialize();
|
||||
}
|
||||
|
||||
public FlatButton(Action a) {
|
||||
super(a);
|
||||
initialize();
|
||||
}
|
||||
|
||||
public FlatButton(String text, Icon icon) {
|
||||
super(text, icon);
|
||||
initialize();
|
||||
}
|
||||
|
||||
|
||||
private void initialize() {
|
||||
this.addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mouseExited(MouseEvent e) {
|
||||
flatten();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseEntered(MouseEvent e) {
|
||||
raise();
|
||||
}
|
||||
});
|
||||
flatten();
|
||||
}
|
||||
|
||||
|
||||
private void flatten() {
|
||||
this.setContentAreaFilled(false);
|
||||
this.setBorderPainted(false);
|
||||
}
|
||||
|
||||
private void raise() {
|
||||
this.setContentAreaFilled(true);
|
||||
this.setBorderPainted(true);
|
||||
}
|
||||
|
||||
}
|
218
src/net/sf/openrocket/gui/components/compass/CompassPointer.java
Normal file
218
src/net/sf/openrocket/gui/components/compass/CompassPointer.java
Normal file
@ -0,0 +1,218 @@
|
||||
package net.sf.openrocket.gui.components.compass;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.RenderingHints;
|
||||
|
||||
import javax.swing.event.ChangeEvent;
|
||||
import javax.swing.event.ChangeListener;
|
||||
|
||||
import net.sf.openrocket.gui.Resettable;
|
||||
import net.sf.openrocket.gui.adaptors.DoubleModel;
|
||||
|
||||
/**
|
||||
* A component that draws a pointer onto a compass rose.
|
||||
*
|
||||
* @author Sampo Niskanen <sampo.niskanen@iki.fi>
|
||||
*/
|
||||
public class CompassPointer extends CompassRose implements Resettable {
|
||||
|
||||
private static final Color PRIMARY_POINTER_COLOR = new Color(1.0f, 0.2f, 0.2f);
|
||||
private static final Color SECONDARY_POINTER_COLOR = new Color(0.2f, 0.2f, 0.2f, 0.2f);
|
||||
|
||||
private final DoubleModel model;
|
||||
private final ChangeListener listener;
|
||||
|
||||
protected int width = -1;
|
||||
protected int mid = -1;
|
||||
|
||||
private DoubleModel secondaryModel;
|
||||
|
||||
private float pointerLength = 0.95f;
|
||||
private float pointerWidth = 0.1f;
|
||||
private float pointerArrowWidth = 0.2f;
|
||||
private boolean pointerArrow = true;
|
||||
|
||||
|
||||
|
||||
public CompassPointer(DoubleModel model) {
|
||||
super();
|
||||
this.model = model;
|
||||
listener = new ChangeListener() {
|
||||
@Override
|
||||
public void stateChanged(ChangeEvent e) {
|
||||
CompassPointer.this.repaint();
|
||||
}
|
||||
};
|
||||
model.addChangeListener(listener);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void paintComponent(Graphics g) {
|
||||
super.paintComponent(g);
|
||||
|
||||
Graphics2D g2 = (Graphics2D) g;
|
||||
|
||||
|
||||
Dimension dimension = this.getSize();
|
||||
|
||||
width = Math.min(dimension.width, dimension.height);
|
||||
mid = width / 2;
|
||||
width = (int) (getScaler() * width);
|
||||
|
||||
|
||||
g2.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
|
||||
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
|
||||
|
||||
if (secondaryModel != null) {
|
||||
drawArrow(secondaryModel.getValue(), SECONDARY_POINTER_COLOR, g2);
|
||||
}
|
||||
drawArrow(model.getValue(), PRIMARY_POINTER_COLOR, g2);
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
private void drawArrow(double angle, Color color, Graphics2D g2) {
|
||||
|
||||
int pLength = (int) (width * pointerLength / 2);
|
||||
int pWidth = (int) (width * pointerWidth / 2);
|
||||
int pArrowWidth = (int) (width * pointerArrowWidth / 2);
|
||||
|
||||
int[] x = new int[8];
|
||||
int[] y = new int[8];
|
||||
|
||||
g2.setColor(color);
|
||||
|
||||
|
||||
double sin = Math.sin(angle);
|
||||
double cos = Math.cos(angle);
|
||||
|
||||
int n = 0;
|
||||
|
||||
// Top part
|
||||
x[n] = 0;
|
||||
y[n] = -pLength;
|
||||
n++;
|
||||
if (pointerArrow) {
|
||||
x[n] = -pArrowWidth;
|
||||
y[n] = -pLength + 2 * pArrowWidth;
|
||||
n++;
|
||||
x[n] = -pWidth;
|
||||
y[n] = -pLength + 2 * pArrowWidth;
|
||||
n++;
|
||||
}
|
||||
|
||||
// Bottom part
|
||||
x[n] = -pWidth;
|
||||
y[n] = pLength;
|
||||
n++;
|
||||
x[n] = 0;
|
||||
y[n] = pLength - pWidth;
|
||||
n++;
|
||||
x[n] = pWidth;
|
||||
y[n] = pLength;
|
||||
n++;
|
||||
|
||||
// Top part
|
||||
if (pointerArrow) {
|
||||
x[n] = pWidth;
|
||||
y[n] = -pLength + 2 * pArrowWidth;
|
||||
n++;
|
||||
x[n] = pArrowWidth;
|
||||
y[n] = -pLength + 2 * pArrowWidth;
|
||||
n++;
|
||||
}
|
||||
|
||||
// Rotate and shift
|
||||
for (int i = 0; i < n; i++) {
|
||||
double x2, y2;
|
||||
x2 = cos * x[i] - sin * y[i];
|
||||
y2 = sin * x[i] + cos * y[i];
|
||||
|
||||
x[i] = (int) (x2 + mid);
|
||||
y[i] = (int) (y2 + mid);
|
||||
}
|
||||
|
||||
g2.fillPolygon(x, y, n);
|
||||
|
||||
g2.setColor(color.darker());
|
||||
g2.drawPolygon(x, y, n);
|
||||
|
||||
}
|
||||
|
||||
|
||||
public boolean isPointerArrow() {
|
||||
return pointerArrow;
|
||||
}
|
||||
|
||||
|
||||
public void setPointerArrow(boolean useArrow) {
|
||||
this.pointerArrow = useArrow;
|
||||
repaint();
|
||||
}
|
||||
|
||||
|
||||
public float getPointerLength() {
|
||||
return pointerLength;
|
||||
}
|
||||
|
||||
|
||||
public void setPointerLength(float pointerLength) {
|
||||
this.pointerLength = pointerLength;
|
||||
repaint();
|
||||
}
|
||||
|
||||
|
||||
public float getPointerWidth() {
|
||||
return pointerWidth;
|
||||
}
|
||||
|
||||
|
||||
public void setPointerWidth(float pointerWidth) {
|
||||
this.pointerWidth = pointerWidth;
|
||||
repaint();
|
||||
}
|
||||
|
||||
|
||||
public float getPointerArrowWidth() {
|
||||
return pointerArrowWidth;
|
||||
}
|
||||
|
||||
|
||||
public void setPointerArrowWidth(float pointerArrowWidth) {
|
||||
this.pointerArrowWidth = pointerArrowWidth;
|
||||
repaint();
|
||||
}
|
||||
|
||||
|
||||
|
||||
public DoubleModel getSecondaryModel() {
|
||||
return secondaryModel;
|
||||
}
|
||||
|
||||
|
||||
public void setSecondaryModel(DoubleModel secondaryModel) {
|
||||
if (this.secondaryModel != null) {
|
||||
this.secondaryModel.removeChangeListener(listener);
|
||||
}
|
||||
this.secondaryModel = secondaryModel;
|
||||
if (this.secondaryModel != null) {
|
||||
this.secondaryModel.addChangeListener(listener);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void resetModel() {
|
||||
model.removeChangeListener(listener);
|
||||
setSecondaryModel(null);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
221
src/net/sf/openrocket/gui/components/compass/CompassRose.java
Normal file
221
src/net/sf/openrocket/gui/components/compass/CompassRose.java
Normal file
@ -0,0 +1,221 @@
|
||||
package net.sf.openrocket.gui.components.compass;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Font;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.RenderingHints;
|
||||
import java.awt.font.GlyphVector;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
|
||||
import javax.swing.JComponent;
|
||||
|
||||
import net.sf.openrocket.l10n.Translator;
|
||||
import net.sf.openrocket.startup.Application;
|
||||
|
||||
/**
|
||||
* A component that draws a compass rose. This class has no other functionality, but superclasses
|
||||
* may add functionality to it.
|
||||
*
|
||||
* @author Sampo Niskanen <sampo.niskanen@iki.fi>
|
||||
*/
|
||||
public class CompassRose extends JComponent {
|
||||
private static final Translator trans = Application.getTranslator();
|
||||
|
||||
|
||||
private static final Color MAIN_COLOR = new Color(0.4f, 0.4f, 1.0f);
|
||||
private static final float MAIN_LENGTH = 0.95f;
|
||||
private static final float MAIN_WIDTH = 0.15f;
|
||||
|
||||
private static final int CIRCLE_BORDER = 2;
|
||||
private static final Color CIRCLE_HIGHLIGHT = new Color(1.0f, 1.0f, 1.0f, 0.7f);
|
||||
private static final Color CIRCLE_SHADE = new Color(0.0f, 0.0f, 0.0f, 0.2f);
|
||||
|
||||
private static final Color MARKER_COLOR = Color.BLACK;
|
||||
|
||||
|
||||
private double scaler;
|
||||
|
||||
private double markerRadius;
|
||||
private Font markerFont;
|
||||
|
||||
|
||||
/**
|
||||
* Construct a compass rose with the default settings.
|
||||
*/
|
||||
public CompassRose() {
|
||||
this(0.8, 1.1, Font.decode("Serif-PLAIN-16"));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Construct a compass rose with the specified settings.
|
||||
*
|
||||
* @param scaler The scaler of the rose. The bordering circle will we this portion of the component dimensions.
|
||||
* @param markerRadius The radius for the marker positions (N/E/S/W), or NaN for no markers. A value greater than one
|
||||
* will position the markers outside of the bordering circle.
|
||||
* @param markerFont The font used for the markers.
|
||||
*/
|
||||
public CompassRose(double scaler, double markerRadius, Font markerFont) {
|
||||
this.scaler = scaler;
|
||||
this.markerRadius = markerRadius;
|
||||
this.markerFont = markerFont;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void paintComponent(Graphics g) {
|
||||
|
||||
Graphics2D g2 = (Graphics2D) g;
|
||||
|
||||
int[] x = new int[3];
|
||||
int[] y = new int[3];
|
||||
Dimension dimension = this.getSize();
|
||||
|
||||
int width = Math.min(dimension.width, dimension.height);
|
||||
int mid = width / 2;
|
||||
width = (int) (scaler * width);
|
||||
|
||||
int mainLength = (int) (width * MAIN_LENGTH / 2);
|
||||
int mainWidth = (int) (width * MAIN_WIDTH / 2);
|
||||
|
||||
|
||||
g2.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
|
||||
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
|
||||
g2.setColor(MAIN_COLOR);
|
||||
|
||||
// North
|
||||
x[0] = mid;
|
||||
y[0] = mid;
|
||||
x[1] = mid;
|
||||
y[1] = mid - mainLength;
|
||||
x[2] = mid - mainWidth;
|
||||
y[2] = mid - mainWidth;
|
||||
g2.fillPolygon(x, y, 3);
|
||||
|
||||
x[2] = mid + mainWidth;
|
||||
g2.drawPolygon(x, y, 3);
|
||||
|
||||
// East
|
||||
x[0] = mid;
|
||||
y[0] = mid;
|
||||
x[1] = mid + mainLength;
|
||||
y[1] = mid;
|
||||
x[2] = mid + mainWidth;
|
||||
y[2] = mid - mainWidth;
|
||||
g2.fillPolygon(x, y, 3);
|
||||
|
||||
y[2] = mid + mainWidth;
|
||||
g2.drawPolygon(x, y, 3);
|
||||
|
||||
// South
|
||||
x[0] = mid;
|
||||
y[0] = mid;
|
||||
x[1] = mid;
|
||||
y[1] = mid + mainLength;
|
||||
x[2] = mid + mainWidth;
|
||||
y[2] = mid + mainWidth;
|
||||
g2.fillPolygon(x, y, 3);
|
||||
|
||||
x[2] = mid - mainWidth;
|
||||
g2.drawPolygon(x, y, 3);
|
||||
|
||||
// West
|
||||
x[0] = mid;
|
||||
y[0] = mid;
|
||||
x[1] = mid - mainLength;
|
||||
y[1] = mid;
|
||||
x[2] = mid - mainWidth;
|
||||
y[2] = mid + mainWidth;
|
||||
g2.fillPolygon(x, y, 3);
|
||||
|
||||
y[2] = mid - mainWidth;
|
||||
g2.drawPolygon(x, y, 3);
|
||||
|
||||
|
||||
// Border circle
|
||||
g2.setColor(CIRCLE_SHADE);
|
||||
g2.drawArc(mid - width / 2 + CIRCLE_BORDER, mid - width / 2 + CIRCLE_BORDER,
|
||||
width - 2 * CIRCLE_BORDER, width - 2 * CIRCLE_BORDER, 45, 180);
|
||||
g2.setColor(CIRCLE_HIGHLIGHT);
|
||||
g2.drawArc(mid - width / 2 + CIRCLE_BORDER, mid - width / 2 + CIRCLE_BORDER,
|
||||
width - 2 * CIRCLE_BORDER, width - 2 * CIRCLE_BORDER, 180 + 45, 180);
|
||||
|
||||
|
||||
// Draw direction markers
|
||||
if (!Double.isNaN(markerRadius) && markerFont != null) {
|
||||
|
||||
int pos = (int) (width * markerRadius / 2);
|
||||
|
||||
g2.setColor(MARKER_COLOR);
|
||||
drawMarker(g2, mid, mid - pos, trans.get("lbl.north"));
|
||||
drawMarker(g2, mid + pos, mid, trans.get("lbl.east"));
|
||||
drawMarker(g2, mid, mid + pos, trans.get("lbl.south"));
|
||||
drawMarker(g2, mid - pos, mid, trans.get("lbl.west"));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void drawMarker(Graphics2D g2, float x, float y, String str) {
|
||||
GlyphVector gv = markerFont.createGlyphVector(g2.getFontRenderContext(), str);
|
||||
Rectangle2D rect = gv.getVisualBounds();
|
||||
|
||||
x -= rect.getWidth() / 2;
|
||||
y += rect.getHeight() / 2;
|
||||
|
||||
g2.drawGlyphVector(gv, x, y);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public double getScaler() {
|
||||
return scaler;
|
||||
}
|
||||
|
||||
|
||||
public void setScaler(double scaler) {
|
||||
this.scaler = scaler;
|
||||
repaint();
|
||||
}
|
||||
|
||||
|
||||
public double getMarkerRadius() {
|
||||
return markerRadius;
|
||||
}
|
||||
|
||||
|
||||
public void setMarkerRadius(double markerRadius) {
|
||||
this.markerRadius = markerRadius;
|
||||
repaint();
|
||||
}
|
||||
|
||||
|
||||
public Font getMarkerFont() {
|
||||
return markerFont;
|
||||
}
|
||||
|
||||
|
||||
public void setMarkerFont(Font markerFont) {
|
||||
this.markerFont = markerFont;
|
||||
repaint();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dimension getPreferredSize() {
|
||||
Dimension dim = super.getPreferredSize();
|
||||
int min = Math.min(dim.width, dim.height);
|
||||
dim.setSize(min, min);
|
||||
return dim;
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,190 @@
|
||||
package net.sf.openrocket.gui.components.compass;
|
||||
|
||||
import java.awt.Dimension;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
|
||||
import javax.swing.BorderFactory;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JPopupMenu;
|
||||
import javax.swing.JSpinner;
|
||||
import javax.swing.border.BevelBorder;
|
||||
import javax.swing.event.ChangeEvent;
|
||||
import javax.swing.event.ChangeListener;
|
||||
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
import net.sf.openrocket.gui.Resettable;
|
||||
import net.sf.openrocket.gui.adaptors.DoubleModel;
|
||||
import net.sf.openrocket.gui.components.FlatButton;
|
||||
import net.sf.openrocket.l10n.Translator;
|
||||
import net.sf.openrocket.startup.Application;
|
||||
import net.sf.openrocket.util.Chars;
|
||||
import net.sf.openrocket.util.MathUtil;
|
||||
|
||||
|
||||
/**
|
||||
* A button that displays a current compass direction and opens a popup to edit
|
||||
* the value when clicked.
|
||||
*
|
||||
* @author Sampo Niskanen <sampo.niskanen@iki.fi>
|
||||
*/
|
||||
public class CompassSelectionButton extends FlatButton implements Resettable {
|
||||
|
||||
private static final Translator trans = Application.getTranslator();
|
||||
|
||||
private static final int POPUP_COMPASS_SIZE = 200;
|
||||
private static final double SECTOR = 45;
|
||||
|
||||
private static int minWidth = -1;
|
||||
|
||||
|
||||
private final DoubleModel model;
|
||||
private final ChangeListener listener;
|
||||
|
||||
private JPopupMenu popup;
|
||||
|
||||
|
||||
public CompassSelectionButton(final DoubleModel model) {
|
||||
this.model = model;
|
||||
|
||||
JPanel panel = new JPanel(new MigLayout("fill, ins 0"));
|
||||
panel.setOpaque(false);
|
||||
|
||||
CompassPointer pointer = new CompassPointer(model);
|
||||
pointer.setPreferredSize(new Dimension(24, 24));
|
||||
pointer.setMarkerFont(null);
|
||||
pointer.setPointerArrow(false);
|
||||
pointer.setPointerWidth(0.45f);
|
||||
pointer.setScaler(1.0f);
|
||||
panel.add(pointer, "gapright rel");
|
||||
|
||||
|
||||
final JLabel label = new JLabel();
|
||||
label.setText(getLabel(model.getValue()));
|
||||
panel.add(label);
|
||||
|
||||
listener = new ChangeListener() {
|
||||
@Override
|
||||
public void stateChanged(ChangeEvent e) {
|
||||
label.setText(getLabel(model.getValue()));
|
||||
}
|
||||
};
|
||||
model.addChangeListener(listener);
|
||||
|
||||
|
||||
if (minWidth < 0) {
|
||||
calculateMinWidth();
|
||||
label.setMinimumSize(new Dimension(minWidth, 0));
|
||||
}
|
||||
|
||||
|
||||
this.add(panel);
|
||||
|
||||
this.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
openPopup();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
private String getLabel(double value) {
|
||||
String str;
|
||||
|
||||
value = MathUtil.reduce360(value);
|
||||
value = Math.toDegrees(value);
|
||||
str = "" + Math.round(value) + Chars.DEGREE + " (";
|
||||
|
||||
if (value <= 0.5 * SECTOR || value >= 7.5 * SECTOR) {
|
||||
str += trans.get("lbl.N");
|
||||
} else if (value <= 1.5 * SECTOR) {
|
||||
str += trans.get("lbl.NE");
|
||||
} else if (value <= 2.5 * SECTOR) {
|
||||
str += trans.get("lbl.E");
|
||||
} else if (value <= 3.5 * SECTOR) {
|
||||
str += trans.get("lbl.SE");
|
||||
} else if (value <= 4.5 * SECTOR) {
|
||||
str += trans.get("lbl.S");
|
||||
} else if (value <= 5.5 * SECTOR) {
|
||||
str += trans.get("lbl.SW");
|
||||
} else if (value <= 6.5 * SECTOR) {
|
||||
str += trans.get("lbl.W");
|
||||
} else {
|
||||
str += trans.get("lbl.NW");
|
||||
}
|
||||
|
||||
str += ")";
|
||||
return str;
|
||||
}
|
||||
|
||||
|
||||
private void openPopup() {
|
||||
if (popup == null) {
|
||||
popup = new JPopupMenu();
|
||||
|
||||
|
||||
final JPanel panel = new JPanel(new MigLayout("fill"));
|
||||
|
||||
final CompassPointer rose = new CompassSelector(model);
|
||||
rose.setPreferredSize(new Dimension(POPUP_COMPASS_SIZE, POPUP_COMPASS_SIZE));
|
||||
panel.add(rose, "spany, gapright unrel");
|
||||
|
||||
panel.add(new JPanel(), "growy, wrap");
|
||||
|
||||
JSpinner spin = new JSpinner(model.getSpinnerModel());
|
||||
panel.add(spin, "wmin 50lp, growx, gapright 0, aligny bottom");
|
||||
|
||||
panel.add(new JLabel("" + Chars.DEGREE), "wrap para");
|
||||
|
||||
JButton close = new JButton("OK");
|
||||
close.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
popup.setVisible(false);
|
||||
}
|
||||
});
|
||||
panel.add(close, "span 2, growx, wrap");
|
||||
|
||||
panel.add(new JPanel(), "growy, wrap");
|
||||
|
||||
popup.add(panel);
|
||||
popup.setBorder(BorderFactory.createBevelBorder(BevelBorder.RAISED));
|
||||
}
|
||||
|
||||
popup.pack();
|
||||
|
||||
Dimension popupSize = popup.getPreferredSize();
|
||||
Dimension buttonSize = this.getSize();
|
||||
|
||||
int posX = buttonSize.width / 2 - popupSize.width / 2;
|
||||
int posY = buttonSize.height / 2 - popupSize.height / 2;
|
||||
popup.show(this, posX, posY);
|
||||
}
|
||||
|
||||
private void calculateMinWidth() {
|
||||
JLabel label = new JLabel();
|
||||
int max = 0;
|
||||
for (double deg = 0; deg < 360; deg += 0.99999999999) {
|
||||
label.setText(getLabel(Math.toRadians(deg)));
|
||||
int w = label.getPreferredSize().width;
|
||||
if (w > max) {
|
||||
max = w;
|
||||
}
|
||||
}
|
||||
minWidth = max + 1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void resetModel() {
|
||||
model.removeChangeListener(listener);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,97 @@
|
||||
package net.sf.openrocket.gui.components.compass;
|
||||
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
|
||||
import net.sf.openrocket.gui.adaptors.DoubleModel;
|
||||
import net.sf.openrocket.util.MathUtil;
|
||||
|
||||
/**
|
||||
* Component that allows selecting a compass direction on a CompassSelector.
|
||||
*
|
||||
* @author Sampo Niskanen <sampo.niskanen@iki.fi>
|
||||
*/
|
||||
public class CompassSelector extends CompassPointer {
|
||||
|
||||
private final DoubleModel model;
|
||||
|
||||
public CompassSelector(DoubleModel model) {
|
||||
super(model);
|
||||
this.model = model;
|
||||
|
||||
MouseAdapter mouse = new MouseAdapter() {
|
||||
private boolean dragging = false;
|
||||
|
||||
@Override
|
||||
public void mousePressed(MouseEvent e) {
|
||||
if (!isWithinCircle(e))
|
||||
return;
|
||||
if (e.getButton() != MouseEvent.BUTTON1)
|
||||
return;
|
||||
dragging = true;
|
||||
clicked(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseReleased(MouseEvent e) {
|
||||
if (e.getButton() != MouseEvent.BUTTON1)
|
||||
return;
|
||||
dragging = false;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void mouseDragged(MouseEvent e) {
|
||||
if (!dragging)
|
||||
return;
|
||||
clicked(e);
|
||||
}
|
||||
};
|
||||
this.addMouseListener(mouse);
|
||||
this.addMouseMotionListener(mouse);
|
||||
|
||||
}
|
||||
|
||||
private boolean isWithinCircle(MouseEvent e) {
|
||||
if (mid < 0 || width < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int x = e.getX() - mid;
|
||||
int y = e.getY() - mid;
|
||||
|
||||
double distance = Math.hypot(x, y);
|
||||
return distance < width / 2;
|
||||
}
|
||||
|
||||
private void clicked(MouseEvent e) {
|
||||
|
||||
if (mid < 0 || width < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
int x = e.getX() - mid;
|
||||
int y = e.getY() - mid;
|
||||
|
||||
double distance = Math.hypot(x, y);
|
||||
|
||||
double theta = Math.atan2(y, x);
|
||||
theta = MathUtil.reduce360(theta + Math.PI / 2);
|
||||
|
||||
// Round the value appropriately
|
||||
theta = Math.toDegrees(theta);
|
||||
|
||||
if (distance > 50) {
|
||||
theta = Math.round(theta);
|
||||
} else if (distance > 10) {
|
||||
theta = 5 * Math.round(theta / 5);
|
||||
} else {
|
||||
// Do nothing if too close to center
|
||||
return;
|
||||
}
|
||||
theta = Math.toRadians(theta);
|
||||
|
||||
model.setValue(theta);
|
||||
}
|
||||
|
||||
}
|
66
src/net/sf/openrocket/gui/components/compass/Tester.java
Normal file
66
src/net/sf/openrocket/gui/components/compass/Tester.java
Normal file
@ -0,0 +1,66 @@
|
||||
package net.sf.openrocket.gui.components.compass;
|
||||
|
||||
import java.awt.Dimension;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JSpinner;
|
||||
import javax.swing.SwingUtilities;
|
||||
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
import net.sf.openrocket.gui.adaptors.DoubleModel;
|
||||
import net.sf.openrocket.l10n.ResourceBundleTranslator;
|
||||
import net.sf.openrocket.startup.Application;
|
||||
import net.sf.openrocket.unit.UnitGroup;
|
||||
import net.sf.openrocket.util.GUIUtil;
|
||||
|
||||
public class Tester {
|
||||
|
||||
|
||||
public static void main(String[] args) throws InterruptedException, InvocationTargetException {
|
||||
|
||||
Application.setBaseTranslator(new ResourceBundleTranslator("l10n.messages"));
|
||||
|
||||
GUIUtil.setBestLAF();
|
||||
|
||||
SwingUtilities.invokeAndWait(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
JFrame frame = new JFrame();
|
||||
|
||||
JPanel panel = new JPanel(new MigLayout("fill"));
|
||||
DoubleModel model = new DoubleModel(Math.toRadians(45), UnitGroup.UNITS_ANGLE);
|
||||
DoubleModel second = new DoubleModel(Math.toRadians(30), UnitGroup.UNITS_ANGLE);
|
||||
|
||||
|
||||
CompassPointer rose = new CompassSelector(model);
|
||||
rose.setPreferredSize(new Dimension(300, 300));
|
||||
rose.setSecondaryModel(second);
|
||||
panel.add(rose);
|
||||
|
||||
rose = new CompassPointer(model);
|
||||
rose.setPreferredSize(new Dimension(24, 24));
|
||||
panel.add(rose);
|
||||
rose.setMarkerFont(null);
|
||||
rose.setPointerArrow(false);
|
||||
rose.setPointerWidth(0.45f);
|
||||
rose.setScaler(1.0f);
|
||||
|
||||
JSpinner spin = new JSpinner(model.getSpinnerModel());
|
||||
spin.setPreferredSize(new Dimension(50, 20));
|
||||
panel.add(spin, "wrap para");
|
||||
|
||||
|
||||
CompassSelectionButton button = new CompassSelectionButton(model);
|
||||
panel.add(button);
|
||||
|
||||
|
||||
frame.add(panel);
|
||||
frame.pack();
|
||||
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||
frame.setVisible(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -1,10 +1,7 @@
|
||||
package net.sf.openrocket.gui.configdialog;
|
||||
|
||||
|
||||
import java.awt.Point;
|
||||
import java.awt.Window;
|
||||
import java.awt.event.ComponentAdapter;
|
||||
import java.awt.event.ComponentEvent;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
|
||||
@ -18,7 +15,6 @@ import net.sf.openrocket.rocketcomponent.RocketComponent;
|
||||
import net.sf.openrocket.startup.Application;
|
||||
import net.sf.openrocket.util.BugException;
|
||||
import net.sf.openrocket.util.GUIUtil;
|
||||
import net.sf.openrocket.util.Prefs;
|
||||
import net.sf.openrocket.util.Reflection;
|
||||
|
||||
/**
|
||||
@ -44,7 +40,7 @@ public class ComponentConfigDialog extends JDialog implements ComponentChangeLis
|
||||
|
||||
private final Window parent;
|
||||
private static final Translator trans = Application.getTranslator();
|
||||
|
||||
|
||||
private ComponentConfigDialog(Window parent, OpenRocketDocument document,
|
||||
RocketComponent component) {
|
||||
super(parent);
|
||||
@ -53,22 +49,7 @@ public class ComponentConfigDialog extends JDialog implements ComponentChangeLis
|
||||
setComponent(document, component);
|
||||
|
||||
GUIUtil.setDisposableDialogOptions(this, null);
|
||||
|
||||
// Set window position according to preferences, and set prefs when moving
|
||||
Point position = Prefs.getWindowPosition(this.getClass());
|
||||
if (position != null) {
|
||||
this.setLocationByPlatform(false);
|
||||
this.setLocation(position);
|
||||
}
|
||||
|
||||
this.addComponentListener(new ComponentAdapter() {
|
||||
@Override
|
||||
public void componentMoved(ComponentEvent e) {
|
||||
Prefs.setWindowPosition(ComponentConfigDialog.this.getClass(),
|
||||
ComponentConfigDialog.this.getLocation());
|
||||
}
|
||||
});
|
||||
|
||||
GUIUtil.rememberWindowPosition(this);
|
||||
}
|
||||
|
||||
|
||||
|
@ -113,6 +113,7 @@ public class OptimizationPlotDialog extends JDialog {
|
||||
this.add(panel);
|
||||
|
||||
GUIUtil.setDisposableDialogOptions(this, close);
|
||||
GUIUtil.rememberWindowSize(this);
|
||||
}
|
||||
|
||||
|
||||
|
@ -6,8 +6,6 @@ import java.awt.Toolkit;
|
||||
import java.awt.Window;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.ComponentAdapter;
|
||||
import java.awt.event.ComponentEvent;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
@ -233,19 +231,16 @@ public class BasicFrame extends JFrame {
|
||||
setTitle();
|
||||
this.pack();
|
||||
|
||||
Dimension size = Prefs.getWindowSize(this.getClass());
|
||||
if (size == null) {
|
||||
size = Toolkit.getDefaultToolkit().getScreenSize();
|
||||
size.width = size.width * 9 / 10;
|
||||
size.height = size.height * 9 / 10;
|
||||
}
|
||||
|
||||
// Set initial window size
|
||||
Dimension size = Toolkit.getDefaultToolkit().getScreenSize();
|
||||
size.width = size.width * 9 / 10;
|
||||
size.height = size.height * 9 / 10;
|
||||
this.setSize(size);
|
||||
this.addComponentListener(new ComponentAdapter() {
|
||||
@Override
|
||||
public void componentResized(ComponentEvent e) {
|
||||
Prefs.setWindowSize(BasicFrame.this.getClass(), BasicFrame.this.getSize());
|
||||
}
|
||||
});
|
||||
|
||||
// Remember changed size
|
||||
GUIUtil.rememberWindowSize(this);
|
||||
|
||||
this.setLocationByPlatform(true);
|
||||
|
||||
GUIUtil.setWindowIcons(this);
|
||||
@ -259,8 +254,8 @@ public class BasicFrame extends JFrame {
|
||||
closeAction();
|
||||
}
|
||||
});
|
||||
frames.add(this);
|
||||
|
||||
frames.add(this);
|
||||
log.debug("BasicFrame instantiation complete");
|
||||
}
|
||||
|
||||
@ -673,14 +668,13 @@ public class BasicFrame extends JFrame {
|
||||
|
||||
menu = new JMenu(trans.get("main.menu.help"));
|
||||
menu.setMnemonic(KeyEvent.VK_H);
|
||||
//// Information about OpenRocket
|
||||
menu.getAccessibleContext().setAccessibleDescription(trans.get("main.menu.help.desc"));
|
||||
menubar.add(menu);
|
||||
|
||||
|
||||
//// License
|
||||
item = new JMenuItem(trans.get("main.menu.help.license"), KeyEvent.VK_L);
|
||||
//// OpenRocket license information
|
||||
item.setIcon(Icons.HELP_LICENSE);
|
||||
item.getAccessibleContext().setAccessibleDescription(trans.get("main.menu.help.license.desc"));
|
||||
item.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
@ -695,7 +689,7 @@ public class BasicFrame extends JFrame {
|
||||
|
||||
//// Bug report
|
||||
item = new JMenuItem(trans.get("main.menu.help.bugReport"), KeyEvent.VK_B);
|
||||
//// Information about reporting bugs in OpenRocket
|
||||
item.setIcon(Icons.HELP_BUG_REPORT);
|
||||
item.getAccessibleContext().setAccessibleDescription(trans.get("main.menu.help.bugReport.desc"));
|
||||
item.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
@ -708,7 +702,7 @@ public class BasicFrame extends JFrame {
|
||||
|
||||
//// Debug log
|
||||
item = new JMenuItem(trans.get("main.menu.help.debugLog"));
|
||||
//// View the OpenRocket debug log
|
||||
item.setIcon(Icons.HELP_DEBUG_LOG);
|
||||
item.getAccessibleContext().setAccessibleDescription(trans.get("main.menu.help.debugLog.desc"));
|
||||
item.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
@ -723,7 +717,7 @@ public class BasicFrame extends JFrame {
|
||||
|
||||
//// About
|
||||
item = new JMenuItem(trans.get("main.menu.help.about"), KeyEvent.VK_A);
|
||||
//// About OpenRocket
|
||||
item.setIcon(Icons.HELP_ABOUT);
|
||||
item.getAccessibleContext().setAccessibleDescription(trans.get("main.menu.help.about.desc"));
|
||||
item.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
|
@ -461,6 +461,7 @@ public class SimulationPlotDialog extends JDialog {
|
||||
this.pack();
|
||||
|
||||
GUIUtil.setDisposableDialogOptions(this, button);
|
||||
GUIUtil.rememberWindowSize(this);
|
||||
}
|
||||
|
||||
private String getLabel(FlightDataType type, Unit unit) {
|
||||
|
@ -10,13 +10,10 @@ import net.sf.openrocket.util.WorldCoordinate;
|
||||
*/
|
||||
public class WGSGravityModel implements GravityModel {
|
||||
|
||||
// Cache the previously computed value
|
||||
private WorldCoordinate lastWorldCoordinate;
|
||||
private double lastg;
|
||||
|
||||
|
||||
private static int hit = 0;
|
||||
private static int miss = 0;
|
||||
|
||||
|
||||
@Override
|
||||
public double getGravity(WorldCoordinate wc) {
|
||||
@ -25,12 +22,7 @@ public class WGSGravityModel implements GravityModel {
|
||||
if (wc != this.lastWorldCoordinate) {
|
||||
this.lastg = calcGravity(wc);
|
||||
this.lastWorldCoordinate = wc;
|
||||
|
||||
miss++;
|
||||
} else {
|
||||
hit++;
|
||||
}
|
||||
System.out.println("GRAVITY MODEL: hit=" + hit + " miss=" + miss);
|
||||
|
||||
return this.lastg;
|
||||
|
||||
|
@ -266,6 +266,9 @@ public class RK4SimulationStepper extends AbstractSimulationStepper {
|
||||
if (status.getRocketVelocity().length2() > 1e18 ||
|
||||
status.getRocketPosition().length2() > 1e18 ||
|
||||
status.getRocketRotationVelocity().length2() > 1e18) {
|
||||
|
||||
// FIXME: Make error message better, recommend shortening time step
|
||||
|
||||
throw new SimulationCalculationException("Simulation values exceeded limits");
|
||||
}
|
||||
}
|
||||
@ -547,9 +550,9 @@ public class RK4SimulationStepper extends AbstractSimulationStepper {
|
||||
data.setValue(FlightDataType.TYPE_POSITION_X, status.getRocketPosition().x);
|
||||
data.setValue(FlightDataType.TYPE_POSITION_Y, status.getRocketPosition().y);
|
||||
|
||||
if (status.getSimulationConditions().getGeodeticComputation() != GeodeticComputationStrategy.NONE) {
|
||||
data.setValue(FlightDataType.TYPE_LATITUDE, status.getRocketWorldPosition().getLatitudeRad());
|
||||
data.setValue(FlightDataType.TYPE_LONGITUDE, status.getRocketWorldPosition().getLongitudeRad());
|
||||
data.setValue(FlightDataType.TYPE_LATITUDE, status.getRocketWorldPosition().getLatitudeRad());
|
||||
data.setValue(FlightDataType.TYPE_LONGITUDE, status.getRocketWorldPosition().getLongitudeRad());
|
||||
if (status.getSimulationConditions().getGeodeticComputation() != GeodeticComputationStrategy.FLAT) {
|
||||
data.setValue(FlightDataType.TYPE_CORIOLIS_ACCELERATION, store.coriolisAcceleration.length());
|
||||
}
|
||||
|
||||
|
@ -62,6 +62,7 @@ public class SimulationOptions implements ChangeSource, Cloneable {
|
||||
private double windAverage = 2.0;
|
||||
private double windTurbulence = 0.1;
|
||||
|
||||
|
||||
/*
|
||||
* SimulationOptions maintains the launch site parameters as separate double values,
|
||||
* and converts them into a WorldCoordinate when converting to SimulationConditions.
|
||||
|
@ -2,6 +2,7 @@ package net.sf.openrocket.util;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.awt.Container;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Font;
|
||||
import java.awt.Image;
|
||||
import java.awt.KeyboardFocusManager;
|
||||
@ -9,6 +10,8 @@ import java.awt.Point;
|
||||
import java.awt.Window;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.ComponentAdapter;
|
||||
import java.awt.event.ComponentEvent;
|
||||
import java.awt.event.ComponentListener;
|
||||
import java.awt.event.FocusListener;
|
||||
import java.awt.event.KeyEvent;
|
||||
@ -39,6 +42,7 @@ import javax.swing.JButton;
|
||||
import javax.swing.JComboBox;
|
||||
import javax.swing.JComponent;
|
||||
import javax.swing.JDialog;
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JRootPane;
|
||||
import javax.swing.JSlider;
|
||||
@ -267,6 +271,60 @@ public class GUIUtil {
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Automatically remember the size of a window. This stores the window size in the user
|
||||
* preferences when resizing/maximizing the window and sets the state on the first call.
|
||||
*/
|
||||
public static void rememberWindowSize(final Window window) {
|
||||
window.addComponentListener(new ComponentAdapter() {
|
||||
@Override
|
||||
public void componentResized(ComponentEvent e) {
|
||||
log.debug("Storing size of " + window.getClass().getName() + ": " + window.getSize());
|
||||
Prefs.setWindowSize(window.getClass(), window.getSize());
|
||||
if (window instanceof JFrame) {
|
||||
if ((((JFrame) window).getExtendedState() & JFrame.MAXIMIZED_BOTH) == JFrame.MAXIMIZED_BOTH) {
|
||||
log.debug("Storing maximized state of " + window.getClass().getName());
|
||||
Prefs.setWindowMaximized(window.getClass());
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (Prefs.isWindowMaximized(window.getClass())) {
|
||||
if (window instanceof JFrame) {
|
||||
((JFrame) window).setExtendedState(JFrame.MAXIMIZED_BOTH);
|
||||
}
|
||||
} else {
|
||||
Dimension dim = Prefs.getWindowSize(window.getClass());
|
||||
if (dim != null) {
|
||||
window.setSize(dim);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Automatically remember the position of a window. The position is stored in the user preferences
|
||||
* every time the window is moved and set from there when first calling this method.
|
||||
*/
|
||||
public static void rememberWindowPosition(final Window window) {
|
||||
window.addComponentListener(new ComponentAdapter() {
|
||||
@Override
|
||||
public void componentMoved(ComponentEvent e) {
|
||||
Prefs.setWindowPosition(window.getClass(), window.getLocation());
|
||||
}
|
||||
});
|
||||
|
||||
// Set window position according to preferences, and set prefs when moving
|
||||
Point position = Prefs.getWindowPosition(window.getClass());
|
||||
if (position != null) {
|
||||
window.setLocationByPlatform(false);
|
||||
window.setLocation(position);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Changes the style of the font of the specified border.
|
||||
*
|
||||
|
@ -5,22 +5,41 @@ import net.sf.openrocket.startup.Application;
|
||||
|
||||
/**
|
||||
* A strategy that performs computations on WorldCoordinates.
|
||||
* <p>
|
||||
* The directions of the coordinate is:
|
||||
* positive X = EAST
|
||||
* positive Y = NORTH
|
||||
* positive Z = UPWARDS
|
||||
*/
|
||||
public enum GeodeticComputationStrategy {
|
||||
|
||||
|
||||
/**
|
||||
* Perform no geodetic computations. The addCoordinate method does nothing and
|
||||
* getCoriolisAcceleration returns Coordinate.NUL.
|
||||
* Perform computations using a flat Earth approximation. addCoordinate computes the
|
||||
* location using a direct meters-per-degree scaling and getCoriolisAcceleration always
|
||||
* returns NUL.
|
||||
*/
|
||||
NONE {
|
||||
FLAT {
|
||||
private static final double METERS_PER_DEGREE_LATITUDE = 111325; // "standard figure"
|
||||
private static final double METERS_PER_DEGREE_LONGITUDE_EQUATOR = 111050;
|
||||
|
||||
|
||||
@Override
|
||||
public WorldCoordinate addCoordinate(WorldCoordinate latlon, Coordinate delta) {
|
||||
return latlon;
|
||||
public WorldCoordinate addCoordinate(WorldCoordinate location, Coordinate delta) {
|
||||
|
||||
double metersPerDegreeLongitude = METERS_PER_DEGREE_LONGITUDE_EQUATOR * Math.cos(location.getLatitudeRad());
|
||||
// Limit to 1 meter per degree near poles
|
||||
metersPerDegreeLongitude = MathUtil.max(metersPerDegreeLongitude, 1);
|
||||
|
||||
double newLat = location.getLatitudeDeg() + delta.y / METERS_PER_DEGREE_LATITUDE;
|
||||
double newLon = location.getLongitudeDeg() + delta.x / metersPerDegreeLongitude;
|
||||
double newAlt = location.getAltitude() + delta.z;
|
||||
|
||||
return new WorldCoordinate(newLat, newLon, newAlt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Coordinate getCoriolisAcceleration(WorldCoordinate latlon, Coordinate velocity) {
|
||||
public Coordinate getCoriolisAcceleration(WorldCoordinate location, Coordinate velocity) {
|
||||
return Coordinate.NUL;
|
||||
}
|
||||
},
|
||||
@ -37,9 +56,13 @@ public enum GeodeticComputationStrategy {
|
||||
// bearing (in radians, clockwise from north);
|
||||
// d/R is the angular distance (in radians), where d is the distance traveled and R is the earth’s radius
|
||||
double d = MathUtil.hypot(delta.x, delta.y);
|
||||
double bearing = Math.atan(delta.x / delta.y);
|
||||
if (delta.y < 0)
|
||||
bearing = bearing + Math.PI;
|
||||
|
||||
// Check for zero movement before computing bearing
|
||||
if (MathUtil.equals(d, 0)) {
|
||||
return new WorldCoordinate(location.getLatitudeDeg(), location.getLongitudeDeg(), newAlt);
|
||||
}
|
||||
|
||||
double bearing = Math.atan2(delta.x, delta.y);
|
||||
|
||||
// Calculate the new lat and lon
|
||||
double newLat, newLon;
|
||||
@ -51,20 +74,17 @@ public enum GeodeticComputationStrategy {
|
||||
newLat = Math.asin(sinLat * cosDR + cosLat * sinDR * Math.cos(bearing));
|
||||
newLon = location.getLongitudeRad() + Math.atan2(Math.sin(bearing) * sinDR * cosLat, cosDR - sinLat * Math.sin(newLat));
|
||||
|
||||
if (Double.isNaN(newLat)) {
|
||||
newLat = location.getLatitudeRad();
|
||||
}
|
||||
|
||||
if (Double.isNaN(newLon)) {
|
||||
newLon = location.getLongitudeRad();
|
||||
if (Double.isNaN(newLat) || Double.isNaN(newLon)) {
|
||||
throw new BugException("addCoordinate resulted in NaN location: location=" + location + " delta=" + delta
|
||||
+ " newLat=" + newLat + " newLon=" + newLon);
|
||||
}
|
||||
|
||||
return new WorldCoordinate(Math.toDegrees(newLat), Math.toDegrees(newLon), newAlt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Coordinate getCoriolisAcceleration(WorldCoordinate latlon, Coordinate velocity) {
|
||||
return computeCoriolisAcceleration(latlon, velocity);
|
||||
public Coordinate getCoriolisAcceleration(WorldCoordinate location, Coordinate velocity) {
|
||||
return computeCoriolisAcceleration(location, velocity);
|
||||
}
|
||||
|
||||
},
|
||||
@ -81,6 +101,12 @@ public enum GeodeticComputationStrategy {
|
||||
// bearing (in radians, clockwise from north);
|
||||
// d/R is the angular distance (in radians), where d is the distance traveled and R is the earth’s radius
|
||||
double d = MathUtil.hypot(delta.x, delta.y);
|
||||
|
||||
// Check for zero movement before computing bearing
|
||||
if (MathUtil.equals(d, 0)) {
|
||||
return new WorldCoordinate(location.getLatitudeDeg(), location.getLongitudeDeg(), newAlt);
|
||||
}
|
||||
|
||||
double bearing = Math.atan(delta.x / delta.y);
|
||||
if (delta.y < 0)
|
||||
bearing = bearing + Math.PI;
|
||||
@ -91,19 +117,17 @@ public enum GeodeticComputationStrategy {
|
||||
newLat = ret[0];
|
||||
newLon = ret[1];
|
||||
|
||||
if (Double.isNaN(newLat)) {
|
||||
newLat = location.getLatitudeRad();
|
||||
if (Double.isNaN(newLat) || Double.isNaN(newLon)) {
|
||||
throw new BugException("addCoordinate resulted in NaN location: location=" + location + " delta=" + delta
|
||||
+ " newLat=" + newLat + " newLon=" + newLon);
|
||||
}
|
||||
|
||||
if (Double.isNaN(newLon)) {
|
||||
newLon = location.getLongitudeRad();
|
||||
}
|
||||
return new WorldCoordinate(newLat, newLon, newAlt);
|
||||
return new WorldCoordinate(Math.toDegrees(newLat), Math.toDegrees(newLon), newAlt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Coordinate getCoriolisAcceleration(WorldCoordinate latlon, Coordinate velocity) {
|
||||
return computeCoriolisAcceleration(latlon, velocity);
|
||||
public Coordinate getCoriolisAcceleration(WorldCoordinate location, Coordinate velocity) {
|
||||
return computeCoriolisAcceleration(location, velocity);
|
||||
}
|
||||
};
|
||||
|
||||
@ -136,13 +160,13 @@ public enum GeodeticComputationStrategy {
|
||||
/**
|
||||
* Add a cartesian movement coordinate to a WorldCoordinate.
|
||||
*/
|
||||
public abstract WorldCoordinate addCoordinate(WorldCoordinate latlon, Coordinate delta);
|
||||
public abstract WorldCoordinate addCoordinate(WorldCoordinate location, Coordinate delta);
|
||||
|
||||
|
||||
/**
|
||||
* Compute the coriolis acceleration at a specified WorldCoordinate and velocity.
|
||||
*/
|
||||
public abstract Coordinate getCoriolisAcceleration(WorldCoordinate latlon, Coordinate velocity);
|
||||
public abstract Coordinate getCoriolisAcceleration(WorldCoordinate location, Coordinate velocity);
|
||||
|
||||
|
||||
|
||||
|
@ -62,6 +62,11 @@ public class Icons {
|
||||
public static final Icon EDIT_DELETE = loadImageIcon("pix/icons/edit-delete.png", "Delete");
|
||||
public static final Icon EDIT_SCALE = loadImageIcon("pix/icons/edit-scale.png", "Scale");
|
||||
|
||||
public static final Icon HELP_ABOUT = loadImageIcon("pix/icons/help-about.png", "About");
|
||||
public static final Icon HELP_BUG_REPORT = loadImageIcon("pix/icons/help-bug.png", "Bug report");
|
||||
public static final Icon HELP_DEBUG_LOG = loadImageIcon("pix/icons/help-log.png", "Debug log");
|
||||
public static final Icon HELP_LICENSE = loadImageIcon("pix/icons/help-license.png", "License");
|
||||
|
||||
public static final Icon ZOOM_IN = loadImageIcon("pix/icons/zoom-in.png", "Zoom in");
|
||||
public static final Icon ZOOM_OUT = loadImageIcon("pix/icons/zoom-out.png", "Zoom out");
|
||||
|
||||
|
@ -38,8 +38,8 @@ import net.sf.openrocket.rocketcomponent.RecoveryDevice;
|
||||
import net.sf.openrocket.rocketcomponent.Rocket;
|
||||
import net.sf.openrocket.rocketcomponent.RocketComponent;
|
||||
import net.sf.openrocket.simulation.FlightDataType;
|
||||
import net.sf.openrocket.simulation.SimulationOptions;
|
||||
import net.sf.openrocket.simulation.RK4SimulationStepper;
|
||||
import net.sf.openrocket.simulation.SimulationOptions;
|
||||
import net.sf.openrocket.startup.Application;
|
||||
import net.sf.openrocket.unit.UnitGroup;
|
||||
|
||||
@ -715,11 +715,22 @@ public class Prefs {
|
||||
return new Dimension(x, y);
|
||||
}
|
||||
|
||||
|
||||
public static boolean isWindowMaximized(Class<?> c) {
|
||||
String pref = PREFNODE.node("windows").get("size." + c.getCanonicalName(), null);
|
||||
return "max".equals(pref);
|
||||
}
|
||||
|
||||
public static void setWindowSize(Class<?> c, Dimension d) {
|
||||
PREFNODE.node("windows").put("size." + c.getCanonicalName(), "" + d.width + "," + d.height);
|
||||
storeVersion();
|
||||
}
|
||||
|
||||
public static void setWindowMaximized(Class<?> c) {
|
||||
PREFNODE.node("windows").put("size." + c.getCanonicalName(), "max");
|
||||
storeVersion();
|
||||
}
|
||||
|
||||
|
||||
//// Printing
|
||||
|
||||
|
@ -15,9 +15,10 @@ public class WorldCoordinate {
|
||||
|
||||
/**
|
||||
* Constructs a new WorldCoordinate
|
||||
*
|
||||
* @param lat latitude in degrees north. From -90 to 90, values outside are clamped.
|
||||
* @param lon longitude in degrees east. From -180 to 180, values outside are reduced to the range.
|
||||
* @param alt altitude in m. Unbounded.
|
||||
* @param alt altitude in meters. Unbounded.
|
||||
*/
|
||||
public WorldCoordinate(double lat, double lon, double alt) {
|
||||
this.lat = MathUtil.clamp(Math.toRadians(lat), -Math.PI / 2, Math.PI / 2);
|
||||
@ -26,33 +27,35 @@ public class WorldCoordinate {
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Returns the altitude.
|
||||
*/
|
||||
public double getAltitude() {
|
||||
return this.alt;
|
||||
}
|
||||
|
||||
/*
|
||||
/**
|
||||
* Returns Longitude in radians
|
||||
*/
|
||||
public double getLongitudeRad() {
|
||||
return this.lon;
|
||||
}
|
||||
|
||||
/*
|
||||
/**
|
||||
* Returns Longitude in degrees
|
||||
*/
|
||||
public double getLongitudeDeg() {
|
||||
return Math.toDegrees(this.lon);
|
||||
}
|
||||
|
||||
/*
|
||||
/**
|
||||
* Returns latitude in radians
|
||||
*/
|
||||
public double getLatitudeRad() {
|
||||
return this.lat;
|
||||
}
|
||||
|
||||
/*
|
||||
/**
|
||||
* Returns latitude in degrees
|
||||
*/
|
||||
public double getLatitudeDeg() {
|
||||
@ -60,9 +63,10 @@ public class WorldCoordinate {
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "WorldCoordinate[lat=" + lat + ", lon=" + lon + ", alt=" + alt + "]";
|
||||
return "WorldCoordinate[lat=" + getLatitudeDeg() + ", lon=" + getLongitudeDeg() + ", alt=" + getAltitude() + "]";
|
||||
}
|
||||
|
||||
|
||||
|
@ -7,7 +7,7 @@ import org.junit.Test;
|
||||
public class GeodeticComputationStrategyTest {
|
||||
|
||||
@Test
|
||||
public void testAddCoordinate() {
|
||||
public void testSpericalAddCoordinate() {
|
||||
|
||||
double arcmin = (1.0 / 60.0);
|
||||
double arcsec = (1.0 / (60.0 * 60.0));
|
||||
@ -36,8 +36,109 @@ public class GeodeticComputationStrategyTest {
|
||||
assertEquals(1000.0, wc.getAltitude(), 0.0);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testGetCoriolisAcceleration1() {
|
||||
public void testAddCoordinates() {
|
||||
|
||||
double min = 1 / 60.0;
|
||||
double sec = 1 / 3600.0;
|
||||
|
||||
|
||||
// Test zero movement
|
||||
testAddCoordinate(50.0, 20.0, 0, 123, 50.0, 20.0, false);
|
||||
|
||||
|
||||
/*
|
||||
* These example values have been computed using the calculator at
|
||||
* http://www.movable-type.co.uk/scripts/latlong.html
|
||||
*/
|
||||
|
||||
// Long distance NE over England, crosses Greenwich meridian
|
||||
// 50 03N 005 42W to 58 38N 003 04E is 1109km at 027 16'07"
|
||||
testAddCoordinate(50 + 3 * min, -5 - 42 * min, 1109000, 27 + 16 * min + 7 * sec, 58 + 38 * min, 3 + 4 * min, false);
|
||||
|
||||
// SW over Brazil
|
||||
// -10N -60E to -11N -61E is 155.9km at 224 25'34"
|
||||
testAddCoordinate(-10, -60, 155900, 224 + 25 * min + 34 * sec, -11, -61, true);
|
||||
|
||||
// NW over the 180 meridian
|
||||
// 63N -179E to 63 01N 179E is 100.9km at 271 56'34"
|
||||
testAddCoordinate(63, -179, 100900, 271 + 56 * min + 34 * sec, 63 + 1 * min, 179, true);
|
||||
|
||||
// NE near the north pole
|
||||
// 89 50N 0E to 89 45N 175E is 46.29 km at 003 00'01"
|
||||
testAddCoordinate(89 + 50 * min, 0, 46290, 3 + 0 * min + 1 * sec, 89 + 45 * min, 175, false);
|
||||
|
||||
// S directly over south pole
|
||||
// -89 50N 12E to -89 45N 192E is 46.33km at 180 00'00"
|
||||
testAddCoordinate(-89 - 50 * min, 12, 46330, 180, -89 - 45 * min, -168, false);
|
||||
|
||||
}
|
||||
|
||||
private void testAddCoordinate(double initialLatitude, double initialLongitude, double distance, double bearing,
|
||||
double finalLatitude, double finalLongitude, boolean testFlat) {
|
||||
|
||||
double tolerance;
|
||||
|
||||
bearing = Math.toRadians(bearing);
|
||||
|
||||
// positive X is EAST, positive Y is NORTH
|
||||
double deltaX = distance * Math.sin(bearing);
|
||||
double deltaY = distance * Math.cos(bearing);
|
||||
|
||||
Coordinate coord = new Coordinate(deltaX, deltaY, 1000.0);
|
||||
WorldCoordinate wc = new WorldCoordinate(initialLatitude, initialLongitude, 0.0);
|
||||
|
||||
// Test SPHERICAL
|
||||
tolerance = 0.0015 * distance / 111325;
|
||||
System.out.println("\nSpherical tolerance: " + tolerance);
|
||||
WorldCoordinate result = GeodeticComputationStrategy.SPHERICAL.addCoordinate(wc, coord);
|
||||
|
||||
System.out.println("Difference Lat: " + Math.abs(finalLatitude - result.getLatitudeDeg()));
|
||||
System.out.println("Difference Lon: " + Math.abs(finalLongitude - result.getLongitudeDeg()));
|
||||
assertEquals(finalLatitude, result.getLatitudeDeg(), tolerance);
|
||||
assertEquals(finalLongitude, result.getLongitudeDeg(), tolerance);
|
||||
assertEquals(1000.0, result.getAltitude(), 0.0);
|
||||
|
||||
|
||||
// Test WGS84
|
||||
/*
|
||||
* TODO: Since the example values are computed using a spherical earth approximation,
|
||||
* the WGS84 method will have significantly larger errors. The tolerance should be
|
||||
* increased correspondingly.
|
||||
*/
|
||||
//tolerance = ...
|
||||
System.out.println("\nWGS84 tolerance: " + tolerance);
|
||||
result = GeodeticComputationStrategy.WGS84.addCoordinate(result, coord);
|
||||
|
||||
System.out.println("Difference Lat: " + Math.abs(finalLatitude - result.getLatitudeDeg()));
|
||||
System.out.println("Difference Lon: " + Math.abs(finalLongitude - result.getLongitudeDeg()));
|
||||
// FIXME: Re-enable these when they function
|
||||
// assertEquals(finalLatitude, result.getLatitudeDeg(), tolerance);
|
||||
// assertEquals(finalLongitude, result.getLongitudeDeg(), tolerance);
|
||||
// assertEquals(1000.0, result.getAltitude(), 0.0);
|
||||
|
||||
|
||||
// Test FLAT
|
||||
if (testFlat) {
|
||||
tolerance = 0.02 * distance / 111325;
|
||||
System.out.println("\nFlat tolerance: " + tolerance);
|
||||
result = GeodeticComputationStrategy.FLAT.addCoordinate(wc, coord);
|
||||
|
||||
System.out.println("Difference Lat: " + Math.abs(finalLatitude - result.getLatitudeDeg()));
|
||||
System.out.println("Difference Lon: " + Math.abs(finalLongitude - result.getLongitudeDeg()));
|
||||
assertEquals(finalLatitude, result.getLatitudeDeg(), tolerance);
|
||||
assertEquals(finalLongitude, result.getLongitudeDeg(), tolerance);
|
||||
assertEquals(1000.0, result.getAltitude(), 0.0);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Test
|
||||
public void testSpericalGetCoriolisAcceleration() {
|
||||
|
||||
// For positive latitude and rotational velocity, a movement due east results in an acceleration due south
|
||||
Coordinate velocity = new Coordinate(-1000, 0, 0);
|
||||
|
Loading…
x
Reference in New Issue
Block a user