[refactor] Reduce redundant methods in Scalefigures, and harmonize common function names

- removed interface that was only inherited by the single AbstractBaseClass
- harmonizes  the border pixels variables in the scalefigure package
This commit is contained in:
Daniel_M_Williams 2018-06-24 19:01:28 -04:00
parent 9d76ece128
commit 885df6ce04
10 changed files with 625 additions and 741 deletions

View File

@ -367,7 +367,7 @@ public class FreeformFinSetConfig extends FinSetConfig {
private int dragIndex = -1;
private FinPointScrollPane( final FinPointFigure _figure) {
super( _figure, true);
super( _figure);
}
@Override

View File

@ -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");

View File

@ -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);

View File

@ -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;
}
}

View File

@ -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.
* <p>
* 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<EventListener> listeners = new LinkedList<EventListener>();
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
}
}
}
}

View File

@ -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<StateChangeListener> listeners = new LinkedList<StateChangeListener>();
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);
}
}

View File

@ -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<RocketComponentShape> figureShapes = new ArrayList<RocketComponentShape>();
private double minX = 0, maxX = 0, maxR = 0;
// Figure width and height in SI-units and pixels
private AffineTransform g2transformation = null;
private final ArrayList<FigureElement> relativeExtra = new ArrayList<FigureElement>();
private final ArrayList<FigureElement> absoluteExtra = new ArrayList<FigureElement>();
@ -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<RocketComponentShape> allShapes, // output parameter
final RocketComponent comp,
final Transformation parentTransform,
final Coordinate parentLocation){
// NOTE: Recursive function
private ArrayList<RocketComponentShape> updateShapeTree(
ArrayList<RocketComponentShape> 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<Coordinate> 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);
}
}
}

View File

@ -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.
* <p>
* 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);
}

View File

@ -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 <sampo.niskanen@iki.fi>
*/
@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 <code>fit</code> is <code>true</code>
*/
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);

View File

@ -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 + ")";
}