diff --git a/swing/src/net/sf/openrocket/gui/configdialog/FreeformFinSetConfig.java b/swing/src/net/sf/openrocket/gui/configdialog/FreeformFinSetConfig.java
index cc25c704c..2f8cf3f35 100644
--- a/swing/src/net/sf/openrocket/gui/configdialog/FreeformFinSetConfig.java
+++ b/swing/src/net/sf/openrocket/gui/configdialog/FreeformFinSetConfig.java
@@ -367,7 +367,7 @@ public class FreeformFinSetConfig extends FinSetConfig {
private int dragIndex = -1;
private FinPointScrollPane( final FinPointFigure _figure) {
- super( _figure, true);
+ super( _figure);
}
@Override
diff --git a/swing/src/net/sf/openrocket/gui/dialogs/optimization/GeneralOptimizationDialog.java b/swing/src/net/sf/openrocket/gui/dialogs/optimization/GeneralOptimizationDialog.java
index 33ec67823..14cda1d8d 100644
--- a/swing/src/net/sf/openrocket/gui/dialogs/optimization/GeneralOptimizationDialog.java
+++ b/swing/src/net/sf/openrocket/gui/dialogs/optimization/GeneralOptimizationDialog.java
@@ -506,7 +506,6 @@ public class GeneralOptimizationDialog extends JDialog {
// // Rocket figure
figure = new RocketFigure( getSelectedSimulation().getRocket() );
- figure.setBorderPixels(1, 1);
ScaleScrollPane figureScrollPane = new ScaleScrollPane(figure);
figureScrollPane.setFitting(true);
panel.add(figureScrollPane, "span, split, height 200lp, grow");
diff --git a/swing/src/net/sf/openrocket/gui/print/DesignReport.java b/swing/src/net/sf/openrocket/gui/print/DesignReport.java
index 0a56cce7d..d95f9fb8f 100644
--- a/swing/src/net/sf/openrocket/gui/print/DesignReport.java
+++ b/swing/src/net/sf/openrocket/gui/print/DesignReport.java
@@ -177,7 +177,7 @@ public class DesignReport {
canvas.beginText();
canvas.setFontAndSize(ITextHelper.getBaseFont(), PrintUtilities.NORMAL_FONT_SIZE);
- int figHeightPts = (int) (PrintUnit.METERS.toPoints(figure.getFigureHeight()) * 0.4 * (scale / PrintUnit.METERS
+ int figHeightPts = (int) (PrintUnit.METERS.toPoints(figure.getHeight()) * 0.4 * (scale / PrintUnit.METERS
.toPoints(1)));
final int diagramHeight = pageImageableHeight * 2 - 70 - (figHeightPts);
canvas.moveText(document.leftMargin() + pageSize.getBorderWidthLeft(), diagramHeight);
@@ -274,7 +274,7 @@ public class DesignReport {
theFigure.updateFigure();
double scale =
- (thePageImageableWidth * 2.2) / theFigure.getFigureWidth();
+ (thePageImageableWidth * 2.2) / theFigure.getWidth();
theFigure.setScale(scale);
/*
* page dimensions are in points-per-inch, which, in Java2D, are the same as pixels-per-inch; thus we don't need any conversion
@@ -288,7 +288,7 @@ public class DesignReport {
int y = PrintUnit.POINTS_PER_INCH;
//If the y dimension is negative, then it will potentially be drawn off the top of the page. Move the origin
//to allow for this.
- if (theFigure.getDimensions().getY() < 0.0d) {
+ if (theFigure.getHeight() < 0.0d) {
y += (int) halfFigureHeight;
}
g2d.translate(20, y);
diff --git a/swing/src/net/sf/openrocket/gui/print/PrintFigure.java b/swing/src/net/sf/openrocket/gui/print/PrintFigure.java
index 13661be97..344713505 100644
--- a/swing/src/net/sf/openrocket/gui/print/PrintFigure.java
+++ b/swing/src/net/sf/openrocket/gui/print/PrintFigure.java
@@ -22,18 +22,12 @@ public class PrintFigure extends RocketFigure {
super(rkt);
}
- @Override
- protected double computeTy(int heightPx) {
- super.computeTy(heightPx);
- return 0;
- }
-
public void setScale(final double theScale) {
this.scale = theScale; //dpi/0.0254*scaling;
updateFigure();
}
public double getFigureHeightPx() {
- return this.figureHeightPx;
+ return this.getSize().height;
}
}
diff --git a/swing/src/net/sf/openrocket/gui/scalefigure/AbstractScaleFigure.java b/swing/src/net/sf/openrocket/gui/scalefigure/AbstractScaleFigure.java
index 2955b34d5..258e1f0dc 100644
--- a/swing/src/net/sf/openrocket/gui/scalefigure/AbstractScaleFigure.java
+++ b/swing/src/net/sf/openrocket/gui/scalefigure/AbstractScaleFigure.java
@@ -2,6 +2,8 @@ package net.sf.openrocket.gui.scalefigure;
import java.awt.Color;
import java.awt.Dimension;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Rectangle2D;
import java.util.EventListener;
import java.util.EventObject;
import java.util.LinkedList;
@@ -9,124 +11,194 @@ import java.util.List;
import javax.swing.JPanel;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
import net.sf.openrocket.gui.util.GUIUtil;
+import net.sf.openrocket.util.MathUtil;
import net.sf.openrocket.util.StateChangeListener;
-
@SuppressWarnings("serial")
-public abstract class AbstractScaleFigure extends JPanel implements ScaleFigure {
-
+public abstract class AbstractScaleFigure extends JPanel {
+
+ private final static Logger log = LoggerFactory.getLogger(AbstractScaleFigure.class);
+
+ /**
+ * Extra scaling applied to the figure. The f***ing Java JRE doesn't know
+ * how to draw shapes when using very large scaling factors, so this must
+ * be manually applied to every single shape used.
+ *
+ * The scaling factor used is divided by this value, and every coordinate used
+ * in the figures must be multiplied by this factor.
+ */
+ public static final double EXTRA_SCALE = 1.0;
+
+ public static final double INCHES_PER_METER = 39.3701;
+ public static final double METERS_PER_INCH = 0.0254;
+
+ public static final double MINIMUM_ZOOM = 0.01; // == 1 %
+ public static final double MAXIMUM_ZOOM = 1000.00; // == 10,000 %
+
// Number of pixels to leave at edges when fitting figure
private static final int DEFAULT_BORDER_PIXELS_WIDTH = 30;
private static final int DEFAULT_BORDER_PIXELS_HEIGHT = 20;
+ // constant factor that scales screen real-estate to rocket-space
+ private final double baseScale;
+ private double userScale = 1.0;
+ protected double scale = -1;
- protected final double dpi;
-
- protected double scale = 1.0;
- protected double scaling = 1.0;
-
- protected int borderPixelsWidth = DEFAULT_BORDER_PIXELS_WIDTH;
- protected int borderPixelsHeight = DEFAULT_BORDER_PIXELS_HEIGHT;
+ protected static final Dimension borderThickness_px = new Dimension(DEFAULT_BORDER_PIXELS_WIDTH, DEFAULT_BORDER_PIXELS_HEIGHT);
+ protected Dimension originLocation_px = new Dimension(0,0);
+ // ======= whatever this figure is drawing, in real-space coordinates: meters
+ protected Rectangle2D subjectBounds_m = null;
+
+
+ // combines the translation and scale in one place:
+ // which frames does this transform between ?
+ protected AffineTransform projection = null;
+
protected final List listeners = new LinkedList();
public AbstractScaleFigure() {
- this.dpi = GUIUtil.getDPI();
- this.scaling = 1.0;
- this.scale = dpi / 0.0254 * scaling;
-
+ // produces a pixels-per-meter scale factor
+ //
+ // dots dots inch
+ // ---- = ------ * -----
+ // meter inch meter
+ //
+ this.baseScale = GUIUtil.getDPI() * INCHES_PER_METER;
+ this.userScale = 1.0;
+ this.scale = baseScale * userScale;
+
+ this.setPreferredSize(new Dimension(100,100));
+ setSize(100,100);
+
setBackground(Color.WHITE);
setOpaque(true);
}
-
-
- public abstract void updateFigure();
-
- public abstract double getFigureWidth();
-
- public abstract double getFigureHeight();
-
-
- @Override
- public double getScaling() {
- return scaling;
+ public double getUserScale(){
+ return userScale;
}
- @Override
- public double getAbsoluteScale() {
- return scale;
+ public double getAbsoluteScale() {
+ return scale;
+ }
+
+ public Dimension getSubjectOrigin() {
+ return originLocation_px;
+ }
+
+ /**
+ * Set the scale level of the figure. A scale value of 1.0 is equivalent to 100 % scale.
+ * smaller scale display the subject smaller.
+ *
+ * @param newScaleRequest the scale level.
+ */
+ public void scaleTo(final double newScaleRequest) {
+ if (MathUtil.equals(this.userScale, newScaleRequest, 0.01)){
+ return;}
+ if (Double.isInfinite(newScaleRequest) || Double.isNaN(newScaleRequest)) {
+ return;}
+
+ log.warn(String.format("scaling Request from %g => %g @%s\n", this.userScale, newScaleRequest, this.getClass().getSimpleName()), new Throwable());
+
+ this.userScale = MathUtil.clamp( newScaleRequest, MINIMUM_ZOOM, MAXIMUM_ZOOM);
+
+ this.scale = baseScale * userScale;
}
- @Override
- public void setScaling(double scaling) {
- if (Double.isInfinite(scaling) || Double.isNaN(scaling))
- scaling = 1.0;
- if (scaling < 0.001)
- scaling = 0.001;
- if (scaling > 1000)
- scaling = 1000;
- if (Math.abs(this.scaling - scaling) < 0.01)
- return;
- this.scaling = scaling;
- this.scale = dpi / 0.0254 * scaling;
- updateFigure();
- }
-
- @Override
- public void setScaling(Dimension bounds) {
- double zh = 1, zv = 1;
- int w = bounds.width - 2 * borderPixelsWidth - 20;
- int h = bounds.height - 2 * borderPixelsHeight - 20;
-
- if (w < 10)
- w = 10;
- if (h < 10)
- h = 10;
-
- zh = (w) / getFigureWidth();
- zv = (h) / getFigureHeight();
-
- double s = Math.min(zh, zv) / dpi * 0.0254 - 0.001;
-
- // Restrict to 100%
- if (s > 1.0) {
- s = 1.0;
+ /**
+ * Set the scale level to display newBounds
+ *
+ * @param bounds the bounds of the figure.
+ */
+ public void scaleTo(Dimension newBounds) {
+ if( 0 == newBounds.getWidth() || 0 == newBounds.getHeight())
+ return;
+
+ updateSubjectDimensions();
+ updateCanvasOrigin();
+ updateCanvasSize();
+ updateTransform();
+
+ // dimensions within the viewable area, which are available to draw
+ final int drawable_width_px = newBounds.width - 2 * borderThickness_px.width;
+ final int drawable_height_px = newBounds.height - 2 * borderThickness_px.height;
+
+ if(( 0 < drawable_width_px ) && ( 0 < drawable_height_px)) {
+ final double width_scale = (drawable_width_px) / ( subjectBounds_m.getWidth() * baseScale);
+ final double height_scale = (drawable_height_px) / ( subjectBounds_m.getHeight() * baseScale);
+ final double minScale = Math.min(height_scale, width_scale);
+
+ scaleTo(minScale);
}
+ }
+
+ /**
+ * Return the pixel coordinates of the subject's origin.
+ *
+ * @return the pixel coordinates of the figure origin.
+ */
+ protected abstract void updateSubjectDimensions();
- setScaling(s);
+ protected abstract void updateCanvasOrigin();
+
+ /**
+ * update preferred figure Size
+
+ */
+ protected void updateCanvasSize() {
+ Dimension preferredFigureSize_px = new Dimension((int)(subjectBounds_m.getWidth()*scale) + 2*borderThickness_px.width,
+ (int)(subjectBounds_m.getHeight()*scale) + 2*borderThickness_px.height);
+
+ setPreferredSize(preferredFigureSize_px);
+ setMinimumSize(preferredFigureSize_px);
+ revalidate();
+ }
+
+ protected void updateTransform(){
+ // Calculate and store the transformation used
+ // (inverse is used in detecting clicks on objects)
+ projection = new AffineTransform();
+ projection.translate(this.originLocation_px.width, originLocation_px.height);
+ // Mirror position Y-axis upwards
+ projection.scale(scale, -scale);
+ }
+
+ /**
+ * Updates the figure shapes and figure size.
+ */
+ public void updateFigure() {
+ log.debug(String.format("____ Updating %s to: %g user scale, %g overall scale", this.getClass().getSimpleName(), this.getAbsoluteScale(), this.scale));
+
+ updateSubjectDimensions();
+ updateCanvasOrigin();
+ updateCanvasSize();
+ updateTransform();
+
+ revalidate();
+ repaint();
+ }
+
+ protected Dimension getBorderPixels() {
+ return borderThickness_px;
}
-
-
- @Override
- public Dimension getBorderPixels() {
- return new Dimension(borderPixelsWidth, borderPixelsHeight);
- }
-
- @Override
- public void setBorderPixels(int width, int height) {
- this.borderPixelsWidth = width;
- this.borderPixelsHeight = height;
- }
-
-
- @Override
+
public void addChangeListener(StateChangeListener listener) {
listeners.add(0, listener);
}
- @Override
public void removeChangeListener(StateChangeListener listener) {
listeners.remove(listener);
}
- private EventObject changeEvent = null;
-
protected void fireChangeEvent() {
- if (changeEvent == null)
- changeEvent = new EventObject(this);
+ final EventObject changeEvent = new EventObject(this);
+
// Copy the list before iterating to prevent concurrent modification exceptions.
EventListener[] list = listeners.toArray(new EventListener[0]);
for (EventListener l : list) {
@@ -135,5 +207,5 @@ public abstract class AbstractScaleFigure extends JPanel implements ScaleFigure
}
}
}
-
+
}
diff --git a/swing/src/net/sf/openrocket/gui/scalefigure/FinPointFigure.java b/swing/src/net/sf/openrocket/gui/scalefigure/FinPointFigure.java
index b6fdd2bef..1a275ae63 100644
--- a/swing/src/net/sf/openrocket/gui/scalefigure/FinPointFigure.java
+++ b/swing/src/net/sf/openrocket/gui/scalefigure/FinPointFigure.java
@@ -13,96 +13,73 @@ import java.awt.geom.NoninvertibleTransformException;
import java.awt.geom.Path2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
+import java.util.LinkedList;
+import java.util.List;
+import org.slf4j.*;
import net.sf.openrocket.rocketcomponent.FreeformFinSet;
+import net.sf.openrocket.rocketcomponent.RocketComponent;
+import net.sf.openrocket.rocketcomponent.SymmetricComponent;
+import net.sf.openrocket.rocketcomponent.Transition;
+import net.sf.openrocket.rocketcomponent.position.AxialMethod;
import net.sf.openrocket.unit.Tick;
import net.sf.openrocket.unit.Unit;
import net.sf.openrocket.unit.UnitGroup;
+import net.sf.openrocket.util.BoundingBox;
import net.sf.openrocket.util.Coordinate;
import net.sf.openrocket.util.MathUtil;
+import net.sf.openrocket.util.StateChangeListener;
-// TODO: MEDIUM: the figure jumps and bugs when using automatic fitting
-
@SuppressWarnings("serial")
public class FinPointFigure extends AbstractScaleFigure {
-
- private static final int BOX_SIZE = 4;
-
- private final FreeformFinSet finset;
+
+ private final static Logger log = LoggerFactory.getLogger(FinPointFigure.class);
+
+
+ private static final float MINIMUM_CANVAS_SIZE_METERS = 0.01f; // i.e. 1 cm
+
+ private static final Color GRID_LINE_COLOR = new Color( 137, 137, 137, 32);
+ private static final float GRID_LINE_BASE_WIDTH = 0.001f;
+
+ private static final int LINE_WIDTH_PIXELS = 1;
+
+ // the size of the boxes around each fin point vertex
+ private static final float BOX_WIDTH_PIXELS = 12;
+
+ private static final double MINOR_TICKS = 0.05;
+ private static final double MAJOR_TICKS = 0.1;
+
+ private final FreeformFinSet finset;
private int modID = -1;
- private double minX, maxX, maxY;
- private double figureWidth = 0;
- private double figureHeight = 0;
- private double translateX = 0;
- private double translateY = 0;
-
- private AffineTransform transform;
- private Rectangle2D.Double[] handles = null;
+ protected final List listeners = new LinkedList();
+
+ private Rectangle2D.Double[] finPointHandles = null;
+
public FinPointFigure(FreeformFinSet finset) {
this.finset = finset;
+
+ // useful for debugging -- shows a contrast against un-drawn space.
+ setBackground(Color.WHITE);
+ setOpaque(true);
+
+ updateTransform();
}
-
-
+
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
- Graphics2D g2 = (Graphics2D) g;
-
- if (modID != finset.getRocket().getAerodynamicModID()) {
- modID = finset.getRocket().getAerodynamicModID();
- calculateDimensions();
- }
-
-
- double tx, ty;
- // Calculate translation for figure centering
- if (figureWidth * scale + 2 * borderPixelsWidth < getWidth()) {
-
- // Figure fits in the viewport
- tx = (getWidth() - figureWidth * scale) / 2 - minX * scale;
-
- } else {
-
- // Figure does not fit in viewport
- tx = borderPixelsWidth - minX * scale;
-
- }
-
-
- if (figureHeight * scale + 2 * borderPixelsHeight < getHeight()) {
- ty = getHeight() - borderPixelsHeight;
- } else {
- ty = borderPixelsHeight + figureHeight * scale;
- }
-
- if (Math.abs(translateX - tx) > 1 || Math.abs(translateY - ty) > 1) {
- // Origin has changed, fire event
- translateX = tx;
- translateY = ty;
- fireChangeEvent();
- }
-
-
- if (Math.abs(translateX - tx) > 1 || Math.abs(translateY - ty) > 1) {
- // Origin has changed, fire event
- translateX = tx;
- translateY = ty;
- fireChangeEvent();
- }
-
-
- // Calculate and store the transformation used
- transform = new AffineTransform();
- transform.translate(translateX, translateY);
- transform.scale(scale / EXTRA_SCALE, -scale / EXTRA_SCALE);
-
- // TODO: HIGH: border Y-scale upwards
-
- g2.transform(transform);
+ Graphics2D g2 = (Graphics2D) g.create();
+
+ if (modID != finset.getRocket().getAerodynamicModID()) {
+ modID = finset.getRocket().getAerodynamicModID();
+ updateTransform();
+ }
+
+ g2.transform(projection);
// Set rendering hints appropriately
g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
@@ -113,128 +90,184 @@ public class FinPointFigure extends AbstractScaleFigure {
RenderingHints.VALUE_ANTIALIAS_ON);
+ // Background grid
+ paintBackgroundGrid( g2);
- Rectangle visible = g2.getClipBounds();
- double x0 = ((double) visible.x - 3) / EXTRA_SCALE;
- double x1 = ((double) visible.x + visible.width + 4) / EXTRA_SCALE;
- double y0 = ((double) visible.y - 3) / EXTRA_SCALE;
- double y1 = ((double) visible.y + visible.height + 4) / EXTRA_SCALE;
-
-
- // Background grid
-
- g2.setStroke(new BasicStroke((float) (1.0 * EXTRA_SCALE / scale),
- BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL));
- g2.setColor(new Color(0, 0, 255, 30));
-
- Unit unit;
- if (this.getParent() != null &&
- this.getParent().getParent() instanceof ScaleScrollPane) {
- unit = ((ScaleScrollPane) this.getParent().getParent()).getCurrentUnit();
- } else {
- unit = UnitGroup.UNITS_LENGTH.getDefaultUnit();
- }
-
- // vertical
- Tick[] ticks = unit.getTicks(x0, x1,
- ScaleScrollPane.MINOR_TICKS / scale,
- ScaleScrollPane.MAJOR_TICKS / scale);
- Line2D.Double line = new Line2D.Double();
- for (Tick t : ticks) {
- if (t.major) {
- line.setLine(t.value * EXTRA_SCALE, y0 * EXTRA_SCALE,
- t.value * EXTRA_SCALE, y1 * EXTRA_SCALE);
- g2.draw(line);
- }
- }
-
- // horizontal
- ticks = unit.getTicks(y0, y1,
- ScaleScrollPane.MINOR_TICKS / scale,
- ScaleScrollPane.MAJOR_TICKS / scale);
- for (Tick t : ticks) {
- if (t.major) {
- line.setLine(x0 * EXTRA_SCALE, t.value * EXTRA_SCALE,
- x1 * EXTRA_SCALE, t.value * EXTRA_SCALE);
- g2.draw(line);
- }
- }
-
-
-
-
-
- // Base rocket line
- g2.setStroke(new BasicStroke((float) (3.0 * EXTRA_SCALE / scale),
- BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL));
- g2.setColor(Color.GRAY);
-
- g2.drawLine((int) (x0 * EXTRA_SCALE), 0, (int) (x1 * EXTRA_SCALE), 0);
-
-
- // Fin shape
- Coordinate[] points = finset.getFinPoints();
- Path2D.Double shape = new Path2D.Double();
- shape.moveTo(0, 0);
- for (int i = 1; i < points.length; i++) {
- shape.lineTo(points[i].x * EXTRA_SCALE, points[i].y * EXTRA_SCALE);
- }
-
- g2.setStroke(new BasicStroke((float) (1.0 * EXTRA_SCALE / scale),
- BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL));
- g2.setColor(Color.BLACK);
- g2.draw(shape);
-
-
- // Fin point boxes
- g2.setColor(new Color(150, 0, 0));
- double s = BOX_SIZE * EXTRA_SCALE / scale;
- handles = new Rectangle2D.Double[points.length];
- for (int i = 0; i < points.length; i++) {
- Coordinate c = points[i];
- handles[i] = new Rectangle2D.Double(c.x * EXTRA_SCALE - s, c.y * EXTRA_SCALE - s, 2 * s, 2 * s);
- g2.draw(handles[i]);
- }
+ paintRocketBody(g2);
+ paintFinShape(g2);
+ paintFinHandles(g2);
}
-
+ public void paintBackgroundGrid( Graphics2D g2){
+ Rectangle visible = g2.getClipBounds();
+ int x0 = visible.x - 3;
+ int x1 = visible.x + visible.width + 4;
+ int y0 = visible.y - 3;
+ int y1 = visible.y + visible.height + 4;
- public int getIndexByPoint(double x, double y) {
- if (handles == null)
- return -1;
-
- // Calculate point in shapes' coordinates
- Point2D.Double p = new Point2D.Double(x, y);
- try {
- transform.inverseTransform(p, p);
- } catch (NoninvertibleTransformException e) {
- return -1;
- }
-
- for (int i = 0; i < handles.length; i++) {
- if (handles[i].contains(p))
- return i;
- }
- return -1;
+ final float grid_line_width = (float)(FinPointFigure.GRID_LINE_BASE_WIDTH/this.scale);
+ g2.setStroke(new BasicStroke( grid_line_width,
+ BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL));
+ g2.setColor(FinPointFigure.GRID_LINE_COLOR);
+
+ Unit unit;
+ if (this.getParent() != null && this.getParent().getParent() instanceof ScaleScrollPane) {
+ unit = ((ScaleScrollPane) this.getParent().getParent()).getCurrentUnit();
+ } else {
+ unit = UnitGroup.UNITS_LENGTH.getDefaultUnit();
+ }
+
+ // vertical
+ Tick[] verticalTicks = unit.getTicks(x0, x1, MINOR_TICKS, MAJOR_TICKS);
+ Line2D.Double line = new Line2D.Double();
+ for (Tick t : verticalTicks) {
+ if (t.major) {
+ line.setLine( t.value, y0, t.value, y1);
+ g2.draw(line);
+ }
+ }
+
+ // horizontal
+ Tick[] horizontalTicks = unit.getTicks(y0, y1, MINOR_TICKS, MAJOR_TICKS);
+ for (Tick t : horizontalTicks) {
+ if (t.major) {
+ line.setLine( x0, t.value, x1, t.value);
+ g2.draw(line);
+ }
+ }
+ }
+
+ private void paintRocketBody( Graphics2D g2){
+ RocketComponent comp = finset.getParent();
+ if( comp instanceof Transition ){
+ paintBodyTransition(g2);
+ }else{
+ paintBodyTube(g2);
+ }
+ }
+
+ // NOTE: This function drawns relative to the reference point of the BODY component
+ // In other words: 0,0 == the front, foreRadius of the body component
+ private void paintBodyTransition( Graphics2D g2){
+
+ // setup lines
+ final float bodyLineWidth = (float) ( LINE_WIDTH_PIXELS / scale );
+ g2.setStroke(new BasicStroke( bodyLineWidth, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL));
+ g2.setColor(Color.BLACK);
+
+ Transition body = (Transition) finset.getParent();
+ final float xResolution_m = 0.01f; // distance between draw points, in meters
+
+ final double xFinStart = finset.asPositionValue(AxialMethod.TOP); //<< in body frame
+
+ // vv in fin-frame == draw-frame vv
+ final double xOffset = -xFinStart;
+ final double yOffset = -body.getRadius(xFinStart);
+
+ Path2D.Double bodyShape = new Path2D.Double();
+ // draw front-cap:
+ bodyShape.moveTo( xOffset, yOffset);
+ bodyShape.lineTo( xOffset, yOffset + body.getForeRadius());
+
+ final float length_m = (float)( body.getLength());
+ Point2D.Double cur = new Point2D.Double ();
+ for( double xBody = xResolution_m ; xBody < length_m; xBody += xResolution_m ){
+ // xBody is distance from front of parent body
+ cur.x = xOffset + xBody; // offset from origin (front of fin)
+ cur.y = yOffset + body.getRadius( xBody); // offset from origin ( fin-front-point )
+
+ bodyShape.lineTo( cur.x, cur.y);
+ }
+
+ // draw end-cap
+ bodyShape.lineTo( xOffset + length_m, yOffset + body.getAftRadius());
+ bodyShape.lineTo( xOffset + length_m, yOffset);
+
+ g2.draw(bodyShape);
+ }
+
+ private void paintBodyTube( Graphics2D g2){
+ Rectangle visible = g2.getClipBounds();
+ int x0 = visible.x - 3;
+ int x1 = visible.x + visible.width + 4;
+
+ final float bodyLineWidth = (float) ( LINE_WIDTH_PIXELS / scale );
+ g2.setStroke(new BasicStroke( bodyLineWidth, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL));
+ g2.setColor(Color.BLACK);
+
+ g2.drawLine((int) x0, 0, (int)x1, 0);
+ }
+
+ private void paintFinShape(final Graphics2D g2){
+ // excludes fin tab points
+ final Coordinate[] drawPoints = finset.getFinPoints();
+
+ Path2D.Double shape = new Path2D.Double();
+ Coordinate startPoint= drawPoints[0];
+ shape.moveTo( startPoint.x, startPoint.y);
+ for (int i = 1; i < drawPoints.length; i++) {
+ shape.lineTo( drawPoints[i].x, drawPoints[i].y);
+ }
+
+ final float finEdgeWidth_m = (float) (LINE_WIDTH_PIXELS / scale );
+ g2.setStroke(new BasicStroke( finEdgeWidth_m, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL));
+ g2.setColor(Color.BLUE);
+ g2.draw(shape);
}
-
+ private void paintFinHandles(final Graphics2D g2) {
+ // excludes fin tab points
+ final Coordinate[] drawPoints = finset.getFinPoints();
+
+ // Fin point boxes
+ final float boxWidth = (float) (BOX_WIDTH_PIXELS / scale );
+ final float boxEdgeWidth_m = (float) ( LINE_WIDTH_PIXELS / scale );
+ g2.setStroke(new BasicStroke( boxEdgeWidth_m, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL));
+ g2.setColor(new Color(150, 0, 0));
+ final double boxHalfWidth = boxWidth/2;
+ finPointHandles = new Rectangle2D.Double[ drawPoints.length];
+ for (int i = 0; i < drawPoints.length; i++) {
+ Coordinate c = drawPoints[i];
+ finPointHandles[i] = new Rectangle2D.Double(c.x - boxHalfWidth, c.y - boxHalfWidth, boxWidth, boxWidth);
+ g2.draw(finPointHandles[i]);
+ }
+ }
+
+ public int getIndexByPoint(double x, double y) {
+ if (finPointHandles == null)
+ return -1;
+
+ // Calculate point in shapes' coordinates
+ Point2D.Double p = new Point2D.Double(x, y);
+ try {
+ projection.inverseTransform(p, p);
+ } catch (NoninvertibleTransformException e) {
+ return -1;
+ }
+
+ for (int i = 0; i < finPointHandles.length; i++) {
+ if (finPointHandles[i].contains(p))
+ return i;
+ }
+ return -1;
+ }
+
public int getSegmentByPoint(double x, double y) {
- if (handles == null)
+ if (finPointHandles == null)
return -1;
// Calculate point in shapes' coordinates
Point2D.Double p = new Point2D.Double(x, y);
try {
- transform.inverseTransform(p, p);
+ projection.inverseTransform(p, p);
} catch (NoninvertibleTransformException e) {
return -1;
}
- double x0 = p.x / EXTRA_SCALE;
- double y0 = p.y / EXTRA_SCALE;
- double delta = BOX_SIZE / scale;
+ double x0 = p.x;
+ double y0 = p.y;
+ double delta = BOX_WIDTH_PIXELS /*/ scale*/;
//System.out.println("Point: " + x0 + "," + y0);
//System.out.println("delta: " + (BOX_SIZE / scale));
@@ -262,84 +295,60 @@ public class FinPointFigure extends AbstractScaleFigure {
public Point2D.Double convertPoint(double x, double y) {
Point2D.Double p = new Point2D.Double(x, y);
try {
- transform.inverseTransform(p, p);
+ projection.inverseTransform(p, p);
} catch (NoninvertibleTransformException e) {
assert (false) : "Should not occur";
return new Point2D.Double(0, 0);
}
- p.setLocation(p.x / EXTRA_SCALE, p.y / EXTRA_SCALE);
+ p.setLocation(p.x, p.y);
return p;
}
-
-
- @Override
- public Dimension getOrigin() {
+ public Dimension getSubjectOrigin() {
if (modID != finset.getRocket().getAerodynamicModID()) {
modID = finset.getRocket().getAerodynamicModID();
- calculateDimensions();
+ updateTransform();
}
- return new Dimension((int) translateX, (int) translateY);
- }
-
+ return new Dimension(originLocation_px.width, originLocation_px.height);
+ }
+
@Override
- public double getFigureWidth() {
- if (modID != finset.getRocket().getAerodynamicModID()) {
- modID = finset.getRocket().getAerodynamicModID();
- calculateDimensions();
- }
- return figureWidth;
- }
-
- @Override
- public double getFigureHeight() {
- if (modID != finset.getRocket().getAerodynamicModID()) {
- modID = finset.getRocket().getAerodynamicModID();
- calculateDimensions();
- }
- return figureHeight;
- }
-
-
- private void calculateDimensions() {
- minX = 0;
- maxX = 0;
- maxY = 0;
-
- for (Coordinate c : finset.getFinPoints()) {
- if (c.x < minX)
- minX = c.x;
- if (c.x > maxX)
- maxX = c.x;
- if (c.y > maxY)
- maxY = c.y;
- }
-
- if (maxX < 0.01)
- maxX = 0.01;
-
- figureWidth = maxX - minX;
- figureHeight = maxY;
-
+ protected void updateSubjectDimensions(){
+ // update subject bounds
+ BoundingBox newBounds = new BoundingBox();
+
+ // subsequent updates can only increase the size of the bounds, so this is the minimum size.
+ newBounds.update( MINIMUM_CANVAS_SIZE_METERS);
+
+ SymmetricComponent parent = (SymmetricComponent)this.finset.getParent();
- Dimension d = new Dimension((int) (figureWidth * scale + 2 * borderPixelsWidth),
- (int) (figureHeight * scale + 2 * borderPixelsHeight));
-
- if (!d.equals(getPreferredSize()) || !d.equals(getMinimumSize())) {
- setPreferredSize(d);
- setMinimumSize(d);
- revalidate();
- }
+ // N.B.: (0,0) is the fin front-- where it meets the parent body.
+ final double xFinFront = finset.asPositionValue(AxialMethod.TOP); //<< in body frame
+
+ // update to bound the parent body:
+ final double xParentFront = -xFinFront;
+ newBounds.update( xParentFront);
+ final double xParentBack = -xFinFront + parent.getLength();
+ newBounds.update( xParentBack );
+ final double yParentCenterline = -parent.getRadius(xFinFront); // from parent centerline to fin front.
+ newBounds.update( yParentCenterline );
+
+ // in 99% of fins, this bound is redundant, buuuuut just in case.
+ final double yParentMax = yParentCenterline + Math.max( parent.getForeRadius(), parent.getAftRadius());
+ newBounds.update( yParentMax );
+
+ // update to bounds the fin points:
+ newBounds.update( finset.getFinPoints());
+
+ subjectBounds_m = newBounds.toRectangle();
}
-
-
@Override
- public void updateFigure() {
- repaint();
- }
-
-
+ protected void updateCanvasOrigin() {
+ originLocation_px.width = borderThickness_px.width - (int)(subjectBounds_m.getX()*scale);
+ originLocation_px.height = borderThickness_px.height + (int)(subjectBounds_m.getY()*scale);
+
+ }
}
diff --git a/swing/src/net/sf/openrocket/gui/scalefigure/RocketFigure.java b/swing/src/net/sf/openrocket/gui/scalefigure/RocketFigure.java
index c3a59d65d..8018d9c14 100644
--- a/swing/src/net/sf/openrocket/gui/scalefigure/RocketFigure.java
+++ b/swing/src/net/sf/openrocket/gui/scalefigure/RocketFigure.java
@@ -18,8 +18,12 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashSet;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
import net.sf.openrocket.gui.figureelements.FigureElement;
import net.sf.openrocket.gui.rocketfigure.RocketComponentShape;
+import net.sf.openrocket.gui.scalefigure.RocketPanel.VIEW_TYPE;
import net.sf.openrocket.gui.util.ColorConversion;
import net.sf.openrocket.gui.util.SwingPreferences;
import net.sf.openrocket.motor.Motor;
@@ -30,6 +34,7 @@ import net.sf.openrocket.rocketcomponent.MotorMount;
import net.sf.openrocket.rocketcomponent.Rocket;
import net.sf.openrocket.rocketcomponent.RocketComponent;
import net.sf.openrocket.startup.Application;
+import net.sf.openrocket.util.BoundingBox;
import net.sf.openrocket.util.BugException;
import net.sf.openrocket.util.Coordinate;
import net.sf.openrocket.util.LineStyle;
@@ -46,6 +51,8 @@ import net.sf.openrocket.util.Transformation;
*/
@SuppressWarnings("serial")
public class RocketFigure extends AbstractScaleFigure {
+
+ private final static Logger log = LoggerFactory.getLogger(FinPointFigure.class);
private static final String ROCKET_FIGURE_PACKAGE = "net.sf.openrocket.gui.rocketfigure";
private static final String ROCKET_FIGURE_SUFFIX = "Shapes";
@@ -61,28 +68,17 @@ public class RocketFigure extends AbstractScaleFigure {
private Rocket rocket;
private RocketComponent[] selection = new RocketComponent[0];
- private double figureWidth = 0, figureHeight = 0;
- protected int figureWidthPx = 0, figureHeightPx = 0;
private RocketPanel.VIEW_TYPE currentViewType = RocketPanel.VIEW_TYPE.SideView;
private double rotation;
- private Transformation transformation;
-
- private double translateX, translateY;
-
-
-
+ private Transformation axialRotation;
+
/*
* figureComponents contains the corresponding RocketComponents of the figureShapes
*/
private final ArrayList figureShapes = new ArrayList();
-
- private double minX = 0, maxX = 0, maxR = 0;
- // Figure width and height in SI-units and pixels
-
- private AffineTransform g2transformation = null;
private final ArrayList relativeExtra = new ArrayList();
private final ArrayList absoluteExtra = new ArrayList();
@@ -96,27 +92,11 @@ public class RocketFigure extends AbstractScaleFigure {
this.rocket = _rkt;
this.rotation = 0.0;
- this.transformation = Transformation.rotate_x(0.0);
+ this.axialRotation = Transformation.rotate_x(0.0);
updateFigure();
}
- @Override
- public Dimension getOrigin() {
- return new Dimension((int) translateX, (int) translateY);
- }
-
- @Override
- public double getFigureHeight() {
- return figureHeight;
- }
-
- @Override
- public double getFigureWidth() {
- return figureWidth;
- }
-
-
public RocketComponent[] getSelection() {
return selection;
}
@@ -136,14 +116,14 @@ public class RocketFigure extends AbstractScaleFigure {
}
public Transformation getRotateTransformation() {
- return transformation;
+ return axialRotation;
}
public void setRotation(double rot) {
if (MathUtil.equals(rotation, rot))
return;
this.rotation = rot;
- this.transformation = Transformation.rotate_x(rotation);
+ this.axialRotation = Transformation.rotate_x(rotation);
updateFigure();
}
@@ -163,22 +143,6 @@ public class RocketFigure extends AbstractScaleFigure {
}
- /**
- * Updates the figure shapes and figure size.
- */
- @Override
- public void updateFigure() {
- figureShapes.clear();
-
- calculateSize();
-
- getShapeTree( this.figureShapes, rocket, this.transformation, Coordinate.ZERO);
-
- repaint();
- fireChangeEvent();
- }
-
-
public void addRelativeExtra(FigureElement p) {
relativeExtra.add(p);
}
@@ -219,49 +183,15 @@ public class RocketFigure extends AbstractScaleFigure {
AffineTransform baseTransform = g2.getTransform();
- // Update figure shapes if necessary
- if (figureShapes == null)
- updateFigure();
+ updateSubjectDimensions();
+ updateCanvasOrigin();
+ updateCanvasSize();
+ updateTransform();
+
+ figureShapes.clear();
+ updateShapeTree( this.figureShapes, rocket, this.axialRotation, Coordinate.ZERO);
-
- double tx, ty;
- // Calculate translation for figure centering
- if (figureWidthPx + 2 * borderPixelsWidth < getWidth()) {
-
- // Figure fits in the viewport
- if (currentViewType == RocketPanel.VIEW_TYPE.BackView){
- tx = getWidth() / 2;
- }else{
- tx = (getWidth() - figureWidthPx) / 2 - minX * scale;
- }
- } else {
-
- // Figure does not fit in viewport
- if (currentViewType == RocketPanel.VIEW_TYPE.BackView){
- tx = borderPixelsWidth + figureWidthPx / 2;
- }else{
- tx = borderPixelsWidth - minX * scale;
- }
- }
-
- ty = computeTy(figureHeightPx);
-
- if (Math.abs(translateX - tx) > 1 || Math.abs(translateY - ty) > 1) {
- // Origin has changed, fire event
- translateX = tx;
- translateY = ty;
- fireChangeEvent();
- }
-
-
- // Calculate and store the transformation used
- // (inverse is used in detecting clicks on objects)
- g2transformation = new AffineTransform();
- g2transformation.translate(translateX, translateY);
- // Mirror position Y-axis upwards
- g2transformation.scale(scale / EXTRA_SCALE, -scale / EXTRA_SCALE);
-
- g2.transform(g2transformation);
+ g2.transform(projection);
// Set rendering hints appropriately
g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
@@ -378,22 +308,11 @@ public class RocketFigure extends AbstractScaleFigure {
}
- protected double computeTy(int heightPx) {
- final double ty;
- if (heightPx + 2 * borderPixelsHeight < getHeight()) {
- ty = getHeight() / 2;
- } else {
- ty = borderPixelsHeight + heightPx / 2;
- }
- return ty;
- }
-
-
public RocketComponent[] getComponentsByPoint(double x, double y) {
// Calculate point in shapes' coordinates
Point2D.Double p = new Point2D.Double(x, y);
try {
- g2transformation.inverseTransform(p, p);
+ projection.inverseTransform(p, p);
} catch (NoninvertibleTransformException e) {
return new RocketComponent[0];
}
@@ -408,52 +327,54 @@ public class RocketFigure extends AbstractScaleFigure {
return l.toArray(new RocketComponent[0]);
}
- // NOTE: Recursive function
- private void getShapeTree(
- ArrayList allShapes, // output parameter
- final RocketComponent comp,
- final Transformation parentTransform,
- final Coordinate parentLocation){
+ // NOTE: Recursive function
+ private ArrayList updateShapeTree(
+ ArrayList allShapes, // output parameter
+ final RocketComponent comp,
+ final Transformation parentTransform,
+ final Coordinate parentLocation){
-
- final int instanceCount = comp.getInstanceCount();
- Coordinate[] instanceLocations = comp.getInstanceLocations();
- instanceLocations = parentTransform.transform( instanceLocations );
- double[] instanceAngles = comp.getInstanceAngles();
- if( instanceLocations.length != instanceAngles.length ){
- throw new ArrayIndexOutOfBoundsException(String.format("lengths of location array (%d) and angle arrays (%d) differs! (in: %s) ", instanceLocations.length, instanceAngles.length, comp.getName()));
- }
-
- // iterate over the aggregated instances *for the whole* tree.
- for( int index = 0; instanceCount > index ; ++index ){
- final double currentAngle = instanceAngles[index];
- Transformation currentTransform = parentTransform;
- if( 0.00001 < Math.abs( currentAngle )) {
- Transformation currentAngleTransform = Transformation.rotate_x( currentAngle );
- currentTransform = currentAngleTransform.applyTransformation( parentTransform );
- }
-
- Coordinate currentLocation = parentLocation.add( instanceLocations[index] );
-
-// System.err.println(String.format("@%s: %s -- inst: [%d/%d]", comp.getClass().getSimpleName(), comp.getName(), index+1, instanceCount));
-// System.err.println(String.format(" -- stage: %d, active: %b, config: (%d) %s", comp.getStageNumber(), this.getConfiguration().isComponentActive(comp), this.getConfiguration().instanceNumber, this.getConfiguration().getId()));
-// System.err.println(String.format(" -- %s + %s = %s", parentLocation.toString(), instanceLocations[index].toString(), currentLocation.toString()));
-// if( 0.00001 < Math.abs( currentAngle )) {
-// System.err.println(String.format(" -- at: %6.4f radians", currentAngle));
-// }
-
- // generate shape for this component, if active
- if( this.rocket.getSelectedConfiguration().isComponentActive( comp )){
- allShapes = addThisShape( allShapes, this.currentViewType, comp, currentLocation, currentTransform);
- }
-
- // recurse into component's children
- for( RocketComponent child: comp.getChildren() ){
- // draw a tree for each instance subcomponent
- getShapeTree( allShapes, child, currentTransform, currentLocation );
- }
- }
+ final int instanceCount = comp.getInstanceCount();
+ Coordinate[] instanceLocations = comp.getInstanceLocations();
+ instanceLocations = parentTransform.transform( instanceLocations );
+ double[] instanceAngles = comp.getInstanceAngles();
+ if( instanceLocations.length != instanceAngles.length ){
+ throw new ArrayIndexOutOfBoundsException(String.format("lengths of location array (%d) and angle arrays (%d) differs! (in: %s) ", instanceLocations.length, instanceAngles.length, comp.getName()));
+ }
+
+ // iterate over the aggregated instances *for the whole* tree.
+ for( int index = 0; instanceCount > index ; ++index ){
+ final double currentAngle = instanceAngles[index];
+
+ Transformation currentTransform = parentTransform;
+ if( 0.00001 < Math.abs( currentAngle )) {
+ Transformation currentAngleTransform = Transformation.rotate_x( currentAngle );
+ currentTransform = currentAngleTransform.applyTransformation( parentTransform );
+ }
+
+ Coordinate currentLocation = parentLocation.add( instanceLocations[index] );
+
+ // System.err.println(String.format("@%s: %s -- inst: [%d/%d]", comp.getClass().getSimpleName(), comp.getName(), index+1, instanceCount));
+ // System.err.println(String.format(" -- stage: %d, active: %b, config: (%d) %s", comp.getStageNumber(), this.getConfiguration().isComponentActive(comp), this.getConfiguration().instanceNumber, this.getConfiguration().getId()));
+ // System.err.println(String.format(" -- %s + %s = %s", parentLocation.toString(), instanceLocations[index].toString(), currentLocation.toString()));
+ // if( 0.00001 < Math.abs( currentAngle )) {
+ // System.err.println(String.format(" -- at: %6.4f radians", currentAngle));
+ // }
+
+ // generate shape for this component, if active
+ if( this.rocket.getSelectedConfiguration().isComponentActive( comp )){
+ allShapes = addThisShape( allShapes, this.currentViewType, comp, currentLocation, currentTransform);
+ }
+
+ // recurse into component's children
+ for( RocketComponent child: comp.getChildren() ){
+ // draw a tree for each instance subcomponent
+ updateShapeTree( allShapes, child, currentTransform, currentLocation );
+ }
+ }
+
+ return allShapes;
}
/**
@@ -508,82 +429,50 @@ public class RocketFigure extends AbstractScaleFigure {
- /**
- * Gets the bounds of the figure, i.e. the maximum extents in the selected dimensions.
- * The bounds are stored in the variables minX, maxX and maxR.
- */
- private void calculateFigureBounds() {
- Collection bounds = rocket.getSelectedConfiguration().getBounds();
-
- if (bounds.isEmpty()) {
- minX = 0;
- maxX = 0;
- maxR = 0;
- return;
- }
-
- minX = Double.MAX_VALUE;
- maxX = Double.MIN_VALUE;
- maxR = 0;
- for (Coordinate c : bounds) {
- double x = c.x, r = MathUtil.hypot(c.y, c.z);
- if (x < minX)
- minX = x;
- if (x > maxX)
- maxX = x;
- if (r > maxR)
- maxR = r;
- }
- }
-
-// public double getBestZoom(Rectangle2D bounds) {
-// double zh = 1, zv = 1;
-// if (bounds.getWidth() > 0.0001)
-// zh = (getWidth() - 2 * borderPixelsWidth) / bounds.getWidth();
-// if (bounds.getHeight() > 0.0001)
-// zv = (getHeight() - 2 * borderPixelsHeight) / bounds.getHeight();
-// return Math.min(zh, zv);
-// }
-//
-
-
+ /**
+ * Gets the bounds of the drawn subject in Model-Space
+ *
+ * i.e. the maximum extents in the selected dimensions.
+ * The bounds are stored in the variables minX, maxX and maxR.
+ *
+ * @return
+ */
+ @Override
+ protected void updateSubjectDimensions() {
+ // calculate bounds, and store in class variables
+ final BoundingBox bounds = rocket.getSelectedConfiguration().getBoundingBox();
+
+ switch (currentViewType) {
+ case SideView:
+ subjectBounds_m = new Rectangle2D.Double(bounds.min.x, bounds.min.y, bounds.span().x, bounds.span().y);
+ break;
+ case BackView:
+ final double maxR = Math.max(Math.hypot(bounds.min.y, bounds.min.z), Math.hypot(bounds.max.y, bounds.max.z));
+ subjectBounds_m = new Rectangle2D.Double(-maxR, -maxR, 2 * maxR, 2 * maxR);
+ break;
+ default:
+ throw new BugException("Illegal figure type = " + currentViewType);
+ }
+ }
+
/**
* Calculates the necessary size of the figure and set the PreferredSize
* property accordingly.
*/
- private void calculateSize() {
- Rectangle2D dimensions = this.getDimensions();
-
- figureHeight = dimensions.getHeight();
- figureWidth = dimensions.getWidth();
-
- figureWidthPx = (int) (figureWidth * scale);
- figureHeightPx = (int) (figureHeight * scale);
-
- Dimension dpx = new Dimension(
- figureWidthPx + 2 * borderPixelsWidth,
- figureHeightPx + 2 * borderPixelsHeight);
-
- if (!dpx.equals(getPreferredSize()) || !dpx.equals(getMinimumSize())) {
- setPreferredSize(dpx);
- setMinimumSize(dpx);
- revalidate();
- }
+ @Override
+ protected void updateCanvasOrigin() {
+
+ final Dimension subjectArea = new Dimension((int)(subjectBounds_m.getWidth()*scale),
+ (int)(subjectBounds_m.getHeight()*scale));
+
+ final int newOriginY = borderThickness_px.height + (int)(subjectArea.getHeight() / 2);
+ if (currentViewType == RocketPanel.VIEW_TYPE.BackView){
+ int newOriginX = borderThickness_px.width + getWidth() / 2;
+ originLocation_px = new Dimension(newOriginX, newOriginY);
+ }else {
+ int newOriginX = borderThickness_px.width + (getWidth() - subjectArea.width) / 2;
+ originLocation_px = new Dimension(newOriginX, newOriginY);
+ }
}
-
- public Rectangle2D getDimensions() {
- calculateFigureBounds();
-
- switch (currentViewType) {
- case SideView:
- return new Rectangle2D.Double(minX, -maxR, maxX - minX, 2 * maxR);
-
- case BackView:
- return new Rectangle2D.Double(-maxR, -maxR, 2 * maxR, 2 * maxR);
-
- default:
- throw new BugException("Illegal figure type = " + currentViewType);
- }
- }
-
+
}
diff --git a/swing/src/net/sf/openrocket/gui/scalefigure/ScaleFigure.java b/swing/src/net/sf/openrocket/gui/scalefigure/ScaleFigure.java
deleted file mode 100644
index 48440fe33..000000000
--- a/swing/src/net/sf/openrocket/gui/scalefigure/ScaleFigure.java
+++ /dev/null
@@ -1,82 +0,0 @@
-package net.sf.openrocket.gui.scalefigure;
-
-import java.awt.Dimension;
-
-import net.sf.openrocket.util.ChangeSource;
-
-
-public interface ScaleFigure extends ChangeSource {
-
- /**
- * Extra scaling applied to the figure. The f***ing Java JRE doesn't know
- * how to draw shapes when using very large scaling factors, so this must
- * be manually applied to every single shape used.
- *
- * The scaling factor used is divided by this value, and every coordinate used
- * in the figures must be multiplied by this factor.
- */
- public static final double EXTRA_SCALE = 1000;
-
- /**
- * Shorthand for {@link #EXTRA_SCALE}.
- */
- public static final double S = EXTRA_SCALE;
-
-
- /**
- * Set the scale level of the figure. A scale value of 1.0 indicates an original
- * size when using the current DPI level.
- *
- * @param scale the scale level.
- */
- public void setScaling(double scale);
-
-
- /**
- * Set the scale level so that the figure fits into the given bounds.
- *
- * @param bounds the bounds of the figure.
- */
- public void setScaling(Dimension bounds);
-
-
- /**
- * Return the scale level of the figure. A scale value of 1.0 indicates an original
- * size when using the current DPI level.
- *
- * @return the current scale level.
- */
- public double getScaling();
-
-
- /**
- * Return the scale of the figure on px/m.
- *
- * @return the current scale value.
- */
- public double getAbsoluteScale();
-
-
- /**
- * Return the pixel coordinates of the figure origin.
- *
- * @return the pixel coordinates of the figure origin.
- */
- public Dimension getOrigin();
-
-
- /**
- * Get the amount of blank space left around the figure.
- *
- * @return the amount of horizontal and vertical space left on both sides of the figure.
- */
- public Dimension getBorderPixels();
-
- /**
- * Set the amount of blank space left around the figure.
- *
- * @param width the amount of horizontal space left on both sides of the figure.
- * @param height the amount of vertical space left on both sides of the figure.
- */
- public void setBorderPixels(int width, int height);
-}
diff --git a/swing/src/net/sf/openrocket/gui/scalefigure/ScaleScrollPane.java b/swing/src/net/sf/openrocket/gui/scalefigure/ScaleScrollPane.java
index a45dd51a8..bc973938d 100644
--- a/swing/src/net/sf/openrocket/gui/scalefigure/ScaleScrollPane.java
+++ b/swing/src/net/sf/openrocket/gui/scalefigure/ScaleScrollPane.java
@@ -17,7 +17,6 @@ import java.util.EventObject;
import javax.swing.BorderFactory;
import javax.swing.JComponent;
-import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.ScrollPaneConstants;
import javax.swing.event.ChangeEvent;
@@ -29,6 +28,7 @@ import net.sf.openrocket.unit.Tick;
import net.sf.openrocket.unit.Unit;
import net.sf.openrocket.unit.UnitGroup;
import net.sf.openrocket.util.BugException;
+import net.sf.openrocket.util.MathUtil;
import net.sf.openrocket.util.StateChangeListener;
@@ -44,6 +44,7 @@ import net.sf.openrocket.util.StateChangeListener;
*
* @author Sampo Niskanen
*/
+@SuppressWarnings("serial")
public class ScaleScrollPane extends JScrollPane
implements MouseListener, MouseMotionListener {
@@ -51,45 +52,33 @@ public class ScaleScrollPane extends JScrollPane
public static final int MINOR_TICKS = 3;
public static final int MAJOR_TICKS = 30;
+ public static final String USER_SCALE_PROPERTY = "UserScale";
- private JComponent component;
- private ScaleFigure figure;
+ private final JComponent component;
+ private final AbstractScaleFigure figure;
private DoubleModel rulerUnit;
private Ruler horizontalRuler;
private Ruler verticalRuler;
- private final boolean allowFit;
-
+ // is the subject *currently* being fitting
private boolean fit = false;
-
- /**
- * Create a scale scroll pane that allows fitting.
- *
- * @param component the component to contain (must implement ScaleFigure)
- */
- public ScaleScrollPane(JComponent component) {
- this(component, true);
- }
-
/**
* Create a scale scroll pane.
*
* @param component the component to contain (must implement ScaleFigure)
* @param allowFit whether automatic fitting of the figure is allowed
*/
- public ScaleScrollPane(JComponent component, boolean allowFit) {
+ public ScaleScrollPane(final JComponent component) {
super(component);
- if (!(component instanceof ScaleFigure)) {
+ if (!(component instanceof AbstractScaleFigure)) {
throw new IllegalArgumentException("component must implement ScaleFigure");
}
this.component = component;
- this.figure = (ScaleFigure) component;
- this.allowFit = allowFit;
-
+ this.figure = (AbstractScaleFigure) component;
rulerUnit = new DoubleModel(0.0, UnitGroup.UNITS_LENGTH);
rulerUnit.addChangeListener(new ChangeListener() {
@@ -106,50 +95,45 @@ public class ScaleScrollPane extends JScrollPane
UnitSelector selector = new UnitSelector(rulerUnit);
selector.setFont(new Font("SansSerif", Font.PLAIN, 8));
this.setCorner(JScrollPane.UPPER_LEFT_CORNER, selector);
- this.setCorner(JScrollPane.UPPER_RIGHT_CORNER, new JPanel());
- this.setCorner(JScrollPane.LOWER_LEFT_CORNER, new JPanel());
- this.setCorner(JScrollPane.LOWER_RIGHT_CORNER, new JPanel());
this.setBorder(BorderFactory.createLineBorder(Color.LIGHT_GRAY));
-
+ setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
+ setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
+
viewport.addMouseListener(this);
viewport.addMouseMotionListener(this);
figure.addChangeListener(new StateChangeListener() {
@Override
public void stateChanged(EventObject e) {
- horizontalRuler.updateSize();
+ horizontalRuler.updateSize();
verticalRuler.updateSize();
- if (fit) {
- setFitting(true);
- }
+ if(fit) {
+ figure.scaleTo(viewport.getExtentSize());
+ }
}
});
viewport.addComponentListener(new ComponentAdapter() {
@Override
public void componentResized(ComponentEvent e) {
- if (fit) {
- setFitting(true);
- }
+ if(fit) {
+ figure.scaleTo(viewport.getExtentSize());
+ }
+ figure.updateFigure();
+
+ horizontalRuler.updateSize();
+ verticalRuler.updateSize();
}
});
}
- public ScaleFigure getFigure() {
+ public AbstractScaleFigure getFigure() {
return figure;
}
-
- /**
- * Return whether automatic fitting of the figure is allowed.
- */
- public boolean isFittingAllowed() {
- return allowFit;
- }
-
/**
* Return whether the figure is currently automatically fitted within the component bounds.
*/
@@ -159,54 +143,69 @@ public class ScaleScrollPane extends JScrollPane
/**
* Set whether the figure is automatically fitted within the component bounds.
- *
- * @throws BugException if automatic fitting is disallowed and fit is true
*/
- public void setFitting(boolean fit) {
- if (fit && !allowFit) {
- throw new BugException("Attempting to fit figure not allowing fit.");
- }
- this.fit = fit;
- if (fit) {
- setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
- setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER);
+ public void setFitting(final boolean shouldFit) {
+ this.fit = shouldFit;
+ if (shouldFit) {
validate();
- Dimension view = viewport.getExtentSize();
- figure.setScaling(view);
- } else {
- setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
- setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
+
+ Dimension view = viewport.getExtentSize();
+ figure.scaleTo(view);
+ this.firePropertyChange( USER_SCALE_PROPERTY, 1.0, figure.getUserScale());
+
+ revalidate();
}
}
-
-
- public double getScaling() {
- return figure.getScaling();
+ public double getUserScale() {
+ return figure.getUserScale();
}
- public double getScale() {
- return figure.getAbsoluteScale();
- }
-
- public void setScaling(double scale) {
- if (fit) {
- setFitting(false);
- }
- figure.setScaling(scale);
- horizontalRuler.repaint();
- verticalRuler.repaint();
+ public void setScaling(final double newScale) {
+ // match if closer than 1%:
+ if( MathUtil.equals(newScale, figure.getUserScale(), 0.01)){
+ return;
+ }
+
+ // if explicitly setting a zoom level, turn off fitting
+ this.fit = false;
+ figure.scaleTo(newScale);
+
+ revalidate();
}
public Unit getCurrentUnit() {
return rulerUnit.getCurrentUnit();
}
-
+
+ public String toViewportString(){
+ Rectangle view = this.getViewport().getViewRect();
+ return ("Viewport::("+view.getWidth()+","+view.getHeight()+")"
+ +"@("+view.getX()+", "+view.getY()+")");
+ }
+
+ @Override
+ public void revalidate() {
+ if( null != component ) {
+ component.revalidate();
+ figure.updateFigure();
+ }
+
+ if( null != horizontalRuler ){
+ horizontalRuler.revalidate();
+ horizontalRuler.repaint();
+ }
+ if( null != verticalRuler ){
+ verticalRuler.revalidate();
+ verticalRuler.repaint();
+ }
+
+ super.revalidate();
+ }
+
//////////////// Mouse handlers ////////////////
-
-
private int dragStartX = 0;
private int dragStartY = 0;
private Rectangle dragRectangle = null;
@@ -288,27 +287,25 @@ public class ScaleScrollPane extends JScrollPane
repaint();
}
- private double fromPx(int px) {
- Dimension origin = figure.getOrigin();
- if (orientation == HORIZONTAL) {
- px -= origin.width;
- } else {
- // px = -(px - origin.height);
- px -= origin.height;
- }
- return px / figure.getAbsoluteScale();
+ private double fromPx(final int px) {
+ Dimension origin = figure.getSubjectOrigin();
+ double realValue = Double.NaN;
+ if (orientation == HORIZONTAL) {
+ realValue = px - origin.width;
+ } else {
+ realValue = origin.height - px;
+ }
+ return realValue / figure.getAbsoluteScale();
}
- private int toPx(double l) {
- Dimension origin = figure.getOrigin();
- int px = (int) (l * figure.getAbsoluteScale() + 0.5);
+ private int toPx(final double value) {
+ final Dimension origin = figure.getSubjectOrigin();
+ final int px = (int) (value * figure.getAbsoluteScale() + 0.5);
if (orientation == HORIZONTAL) {
- px += origin.width;
+ return (px + origin.width);
} else {
- px = px + origin.height;
- // px += origin.height;
+ return (origin.height - px);
}
- return px;
}
@@ -322,8 +319,7 @@ public class ScaleScrollPane extends JScrollPane
// Fill area with background color
g2.setColor(getBackground());
g2.fillRect(area.x, area.y, area.width, area.height + 100);
-
-
+
int startpx, endpx;
if (orientation == HORIZONTAL) {
startpx = area.x;
@@ -337,11 +333,19 @@ public class ScaleScrollPane extends JScrollPane
double start, end, minor, major;
start = fromPx(startpx);
end = fromPx(endpx);
+
minor = MINOR_TICKS / figure.getAbsoluteScale();
major = MAJOR_TICKS / figure.getAbsoluteScale();
-
- Tick[] ticks = unit.getTicks(start, end, minor, major);
-
+
+ Tick[] ticks = null;
+ if( VERTICAL == orientation ){
+ // the parameters are *intended* to be backwards: because 'getTicks(...)' can only
+ // create increasing arrays (where the start < end)
+ ticks = unit.getTicks(end, start, minor, major);
+ }else if(HORIZONTAL == orientation ){
+ // normal parameter order
+ ticks = unit.getTicks(start, end, minor, major);
+ }
// Set color & hints
g2.setColor(Color.BLACK);
diff --git a/swing/src/net/sf/openrocket/gui/scalefigure/ScaleSelector.java b/swing/src/net/sf/openrocket/gui/scalefigure/ScaleSelector.java
index 7fd68c0d2..71cc55b03 100644
--- a/swing/src/net/sf/openrocket/gui/scalefigure/ScaleSelector.java
+++ b/swing/src/net/sf/openrocket/gui/scalefigure/ScaleSelector.java
@@ -4,7 +4,6 @@ import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.text.DecimalFormat;
-import java.util.Arrays;
import java.util.EventObject;
import java.util.Locale;
@@ -19,6 +18,9 @@ import net.sf.openrocket.util.StateChangeListener;
@SuppressWarnings("serial")
public class ScaleSelector extends JPanel {
+ public static final double MINIMUM_ZOOM = 0.01; // == 1 %
+ public static final double MAXIMUM_ZOOM = 1000.00; // == 10,000 %
+
// Ready zoom settings
private static final DecimalFormat PERCENT_FORMAT = new DecimalFormat("0.#%");
@@ -45,19 +47,16 @@ public class ScaleSelector extends JPanel {
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
- double scale = scrollPane.getScaling();
- scale = getNextLargerScale(scale);
- scrollPane.setScaling(scale);
+ final double oldScale = scrollPane.getUserScale();
+ final double newScale = getNextLargerScale(oldScale);
+ scrollPane.setScaling(newScale);
}
});
add(button, "gap");
// Zoom level selector
String[] settings = SCALE_LABELS;
- if (!scrollPane.isFittingAllowed()) {
- settings = Arrays.copyOf(settings, settings.length - 1);
- }
-
+
scaleSelector = new JComboBox<>(settings);
scaleSelector.setEditable(true);
setZoomText();
@@ -68,8 +67,7 @@ public class ScaleSelector extends JPanel {
String text = (String) scaleSelector.getSelectedItem();
text = text.replaceAll("%", "").trim();
- if (text.toLowerCase(Locale.getDefault()).startsWith(SCALE_FIT.toLowerCase(Locale.getDefault())) &&
- scrollPane.isFittingAllowed()) {
+ if (text.toLowerCase(Locale.getDefault()).startsWith(SCALE_FIT.toLowerCase(Locale.getDefault()))){
scrollPane.setFitting(true);
setZoomText();
return;
@@ -101,7 +99,7 @@ public class ScaleSelector extends JPanel {
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
- double scale = scrollPane.getScaling();
+ double scale = scrollPane.getUserScale();
scale = getNextSmallerScale(scale);
scrollPane.setScaling(scale);
}
@@ -111,7 +109,8 @@ public class ScaleSelector extends JPanel {
}
private void setZoomText() {
- String text = PERCENT_FORMAT.format(scrollPane.getScaling());
+ final double userScale = scrollPane.getUserScale();
+ String text = PERCENT_FORMAT.format(userScale);
if (scrollPane.isFitting()) {
text = "Fit (" + text + ")";
}