[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:
parent
9d76ece128
commit
885df6ce04
@ -367,7 +367,7 @@ public class FreeformFinSetConfig extends FinSetConfig {
|
|||||||
private int dragIndex = -1;
|
private int dragIndex = -1;
|
||||||
|
|
||||||
private FinPointScrollPane( final FinPointFigure _figure) {
|
private FinPointScrollPane( final FinPointFigure _figure) {
|
||||||
super( _figure, true);
|
super( _figure);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@ -506,7 +506,6 @@ public class GeneralOptimizationDialog extends JDialog {
|
|||||||
|
|
||||||
// // Rocket figure
|
// // Rocket figure
|
||||||
figure = new RocketFigure( getSelectedSimulation().getRocket() );
|
figure = new RocketFigure( getSelectedSimulation().getRocket() );
|
||||||
figure.setBorderPixels(1, 1);
|
|
||||||
ScaleScrollPane figureScrollPane = new ScaleScrollPane(figure);
|
ScaleScrollPane figureScrollPane = new ScaleScrollPane(figure);
|
||||||
figureScrollPane.setFitting(true);
|
figureScrollPane.setFitting(true);
|
||||||
panel.add(figureScrollPane, "span, split, height 200lp, grow");
|
panel.add(figureScrollPane, "span, split, height 200lp, grow");
|
||||||
|
|||||||
@ -177,7 +177,7 @@ public class DesignReport {
|
|||||||
|
|
||||||
canvas.beginText();
|
canvas.beginText();
|
||||||
canvas.setFontAndSize(ITextHelper.getBaseFont(), PrintUtilities.NORMAL_FONT_SIZE);
|
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)));
|
.toPoints(1)));
|
||||||
final int diagramHeight = pageImageableHeight * 2 - 70 - (figHeightPts);
|
final int diagramHeight = pageImageableHeight * 2 - 70 - (figHeightPts);
|
||||||
canvas.moveText(document.leftMargin() + pageSize.getBorderWidthLeft(), diagramHeight);
|
canvas.moveText(document.leftMargin() + pageSize.getBorderWidthLeft(), diagramHeight);
|
||||||
@ -274,7 +274,7 @@ public class DesignReport {
|
|||||||
theFigure.updateFigure();
|
theFigure.updateFigure();
|
||||||
|
|
||||||
double scale =
|
double scale =
|
||||||
(thePageImageableWidth * 2.2) / theFigure.getFigureWidth();
|
(thePageImageableWidth * 2.2) / theFigure.getWidth();
|
||||||
theFigure.setScale(scale);
|
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
|
* 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;
|
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
|
//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.
|
//to allow for this.
|
||||||
if (theFigure.getDimensions().getY() < 0.0d) {
|
if (theFigure.getHeight() < 0.0d) {
|
||||||
y += (int) halfFigureHeight;
|
y += (int) halfFigureHeight;
|
||||||
}
|
}
|
||||||
g2d.translate(20, y);
|
g2d.translate(20, y);
|
||||||
|
|||||||
@ -22,18 +22,12 @@ public class PrintFigure extends RocketFigure {
|
|||||||
super(rkt);
|
super(rkt);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected double computeTy(int heightPx) {
|
|
||||||
super.computeTy(heightPx);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setScale(final double theScale) {
|
public void setScale(final double theScale) {
|
||||||
this.scale = theScale; //dpi/0.0254*scaling;
|
this.scale = theScale; //dpi/0.0254*scaling;
|
||||||
updateFigure();
|
updateFigure();
|
||||||
}
|
}
|
||||||
|
|
||||||
public double getFigureHeightPx() {
|
public double getFigureHeightPx() {
|
||||||
return this.figureHeightPx;
|
return this.getSize().height;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,6 +2,8 @@ package net.sf.openrocket.gui.scalefigure;
|
|||||||
|
|
||||||
import java.awt.Color;
|
import java.awt.Color;
|
||||||
import java.awt.Dimension;
|
import java.awt.Dimension;
|
||||||
|
import java.awt.geom.AffineTransform;
|
||||||
|
import java.awt.geom.Rectangle2D;
|
||||||
import java.util.EventListener;
|
import java.util.EventListener;
|
||||||
import java.util.EventObject;
|
import java.util.EventObject;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
@ -9,124 +11,194 @@ import java.util.List;
|
|||||||
|
|
||||||
import javax.swing.JPanel;
|
import javax.swing.JPanel;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import net.sf.openrocket.gui.util.GUIUtil;
|
import net.sf.openrocket.gui.util.GUIUtil;
|
||||||
|
import net.sf.openrocket.util.MathUtil;
|
||||||
import net.sf.openrocket.util.StateChangeListener;
|
import net.sf.openrocket.util.StateChangeListener;
|
||||||
|
|
||||||
|
|
||||||
@SuppressWarnings("serial")
|
@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
|
// 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_WIDTH = 30;
|
||||||
private static final int DEFAULT_BORDER_PIXELS_HEIGHT = 20;
|
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 static final Dimension borderThickness_px = new Dimension(DEFAULT_BORDER_PIXELS_WIDTH, DEFAULT_BORDER_PIXELS_HEIGHT);
|
||||||
|
protected Dimension originLocation_px = new Dimension(0,0);
|
||||||
protected double scale = 1.0;
|
|
||||||
protected double scaling = 1.0;
|
|
||||||
|
|
||||||
protected int borderPixelsWidth = DEFAULT_BORDER_PIXELS_WIDTH;
|
|
||||||
protected int borderPixelsHeight = DEFAULT_BORDER_PIXELS_HEIGHT;
|
|
||||||
|
|
||||||
|
// ======= 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>();
|
protected final List<EventListener> listeners = new LinkedList<EventListener>();
|
||||||
|
|
||||||
|
|
||||||
public AbstractScaleFigure() {
|
public AbstractScaleFigure() {
|
||||||
this.dpi = GUIUtil.getDPI();
|
// produces a pixels-per-meter scale factor
|
||||||
this.scaling = 1.0;
|
//
|
||||||
this.scale = dpi / 0.0254 * scaling;
|
// 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);
|
setBackground(Color.WHITE);
|
||||||
setOpaque(true);
|
setOpaque(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public double getUserScale(){
|
||||||
|
return userScale;
|
||||||
public abstract void updateFigure();
|
|
||||||
|
|
||||||
public abstract double getFigureWidth();
|
|
||||||
|
|
||||||
public abstract double getFigureHeight();
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public double getScaling() {
|
|
||||||
return scaling;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public double getAbsoluteScale() {
|
||||||
public double getAbsoluteScale() {
|
return scale;
|
||||||
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) {
|
* Set the scale level to display newBounds
|
||||||
if (Double.isInfinite(scaling) || Double.isNaN(scaling))
|
*
|
||||||
scaling = 1.0;
|
* @param bounds the bounds of the figure.
|
||||||
if (scaling < 0.001)
|
*/
|
||||||
scaling = 0.001;
|
public void scaleTo(Dimension newBounds) {
|
||||||
if (scaling > 1000)
|
if( 0 == newBounds.getWidth() || 0 == newBounds.getHeight())
|
||||||
scaling = 1000;
|
return;
|
||||||
if (Math.abs(this.scaling - scaling) < 0.01)
|
|
||||||
return;
|
updateSubjectDimensions();
|
||||||
this.scaling = scaling;
|
updateCanvasOrigin();
|
||||||
this.scale = dpi / 0.0254 * scaling;
|
updateCanvasSize();
|
||||||
updateFigure();
|
updateTransform();
|
||||||
}
|
|
||||||
|
// dimensions within the viewable area, which are available to draw
|
||||||
@Override
|
final int drawable_width_px = newBounds.width - 2 * borderThickness_px.width;
|
||||||
public void setScaling(Dimension bounds) {
|
final int drawable_height_px = newBounds.height - 2 * borderThickness_px.height;
|
||||||
double zh = 1, zv = 1;
|
|
||||||
int w = bounds.width - 2 * borderPixelsWidth - 20;
|
if(( 0 < drawable_width_px ) && ( 0 < drawable_height_px)) {
|
||||||
int h = bounds.height - 2 * borderPixelsHeight - 20;
|
final double width_scale = (drawable_width_px) / ( subjectBounds_m.getWidth() * baseScale);
|
||||||
|
final double height_scale = (drawable_height_px) / ( subjectBounds_m.getHeight() * baseScale);
|
||||||
if (w < 10)
|
final double minScale = Math.min(height_scale, width_scale);
|
||||||
w = 10;
|
|
||||||
if (h < 10)
|
scaleTo(minScale);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) {
|
public void addChangeListener(StateChangeListener listener) {
|
||||||
listeners.add(0, listener);
|
listeners.add(0, listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void removeChangeListener(StateChangeListener listener) {
|
public void removeChangeListener(StateChangeListener listener) {
|
||||||
listeners.remove(listener);
|
listeners.remove(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
private EventObject changeEvent = null;
|
|
||||||
|
|
||||||
protected void fireChangeEvent() {
|
protected void fireChangeEvent() {
|
||||||
if (changeEvent == null)
|
final EventObject changeEvent = new EventObject(this);
|
||||||
changeEvent = new EventObject(this);
|
|
||||||
// Copy the list before iterating to prevent concurrent modification exceptions.
|
// Copy the list before iterating to prevent concurrent modification exceptions.
|
||||||
EventListener[] list = listeners.toArray(new EventListener[0]);
|
EventListener[] list = listeners.toArray(new EventListener[0]);
|
||||||
for (EventListener l : list) {
|
for (EventListener l : list) {
|
||||||
@ -135,5 +207,5 @@ public abstract class AbstractScaleFigure extends JPanel implements ScaleFigure
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,96 +13,73 @@ import java.awt.geom.NoninvertibleTransformException;
|
|||||||
import java.awt.geom.Path2D;
|
import java.awt.geom.Path2D;
|
||||||
import java.awt.geom.Point2D;
|
import java.awt.geom.Point2D;
|
||||||
import java.awt.geom.Rectangle2D;
|
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.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.Tick;
|
||||||
import net.sf.openrocket.unit.Unit;
|
import net.sf.openrocket.unit.Unit;
|
||||||
import net.sf.openrocket.unit.UnitGroup;
|
import net.sf.openrocket.unit.UnitGroup;
|
||||||
|
import net.sf.openrocket.util.BoundingBox;
|
||||||
import net.sf.openrocket.util.Coordinate;
|
import net.sf.openrocket.util.Coordinate;
|
||||||
import net.sf.openrocket.util.MathUtil;
|
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")
|
@SuppressWarnings("serial")
|
||||||
public class FinPointFigure extends AbstractScaleFigure {
|
public class FinPointFigure extends AbstractScaleFigure {
|
||||||
|
|
||||||
private static final int BOX_SIZE = 4;
|
private final static Logger log = LoggerFactory.getLogger(FinPointFigure.class);
|
||||||
|
|
||||||
private final FreeformFinSet finset;
|
|
||||||
|
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 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) {
|
public FinPointFigure(FreeformFinSet finset) {
|
||||||
this.finset = finset;
|
this.finset = finset;
|
||||||
|
|
||||||
|
// useful for debugging -- shows a contrast against un-drawn space.
|
||||||
|
setBackground(Color.WHITE);
|
||||||
|
setOpaque(true);
|
||||||
|
|
||||||
|
updateTransform();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void paintComponent(Graphics g) {
|
public void paintComponent(Graphics g) {
|
||||||
super.paintComponent(g);
|
super.paintComponent(g);
|
||||||
Graphics2D g2 = (Graphics2D) g;
|
Graphics2D g2 = (Graphics2D) g.create();
|
||||||
|
|
||||||
if (modID != finset.getRocket().getAerodynamicModID()) {
|
if (modID != finset.getRocket().getAerodynamicModID()) {
|
||||||
modID = finset.getRocket().getAerodynamicModID();
|
modID = finset.getRocket().getAerodynamicModID();
|
||||||
calculateDimensions();
|
updateTransform();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
g2.transform(projection);
|
||||||
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);
|
|
||||||
|
|
||||||
// Set rendering hints appropriately
|
// Set rendering hints appropriately
|
||||||
g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
|
g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
|
||||||
@ -113,128 +90,184 @@ public class FinPointFigure extends AbstractScaleFigure {
|
|||||||
RenderingHints.VALUE_ANTIALIAS_ON);
|
RenderingHints.VALUE_ANTIALIAS_ON);
|
||||||
|
|
||||||
|
|
||||||
|
// Background grid
|
||||||
|
paintBackgroundGrid( g2);
|
||||||
|
|
||||||
Rectangle visible = g2.getClipBounds();
|
paintRocketBody(g2);
|
||||||
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]);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
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) {
|
final float grid_line_width = (float)(FinPointFigure.GRID_LINE_BASE_WIDTH/this.scale);
|
||||||
if (handles == null)
|
g2.setStroke(new BasicStroke( grid_line_width,
|
||||||
return -1;
|
BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL));
|
||||||
|
g2.setColor(FinPointFigure.GRID_LINE_COLOR);
|
||||||
// Calculate point in shapes' coordinates
|
|
||||||
Point2D.Double p = new Point2D.Double(x, y);
|
Unit unit;
|
||||||
try {
|
if (this.getParent() != null && this.getParent().getParent() instanceof ScaleScrollPane) {
|
||||||
transform.inverseTransform(p, p);
|
unit = ((ScaleScrollPane) this.getParent().getParent()).getCurrentUnit();
|
||||||
} catch (NoninvertibleTransformException e) {
|
} else {
|
||||||
return -1;
|
unit = UnitGroup.UNITS_LENGTH.getDefaultUnit();
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < handles.length; i++) {
|
// vertical
|
||||||
if (handles[i].contains(p))
|
Tick[] verticalTicks = unit.getTicks(x0, x1, MINOR_TICKS, MAJOR_TICKS);
|
||||||
return i;
|
Line2D.Double line = new Line2D.Double();
|
||||||
}
|
for (Tick t : verticalTicks) {
|
||||||
return -1;
|
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) {
|
public int getSegmentByPoint(double x, double y) {
|
||||||
if (handles == null)
|
if (finPointHandles == null)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
// Calculate point in shapes' coordinates
|
// Calculate point in shapes' coordinates
|
||||||
Point2D.Double p = new Point2D.Double(x, y);
|
Point2D.Double p = new Point2D.Double(x, y);
|
||||||
try {
|
try {
|
||||||
transform.inverseTransform(p, p);
|
projection.inverseTransform(p, p);
|
||||||
} catch (NoninvertibleTransformException e) {
|
} catch (NoninvertibleTransformException e) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
double x0 = p.x / EXTRA_SCALE;
|
double x0 = p.x;
|
||||||
double y0 = p.y / EXTRA_SCALE;
|
double y0 = p.y;
|
||||||
double delta = BOX_SIZE / scale;
|
double delta = BOX_WIDTH_PIXELS /*/ scale*/;
|
||||||
|
|
||||||
//System.out.println("Point: " + x0 + "," + y0);
|
//System.out.println("Point: " + x0 + "," + y0);
|
||||||
//System.out.println("delta: " + (BOX_SIZE / scale));
|
//System.out.println("delta: " + (BOX_SIZE / scale));
|
||||||
@ -262,84 +295,60 @@ public class FinPointFigure extends AbstractScaleFigure {
|
|||||||
public Point2D.Double convertPoint(double x, double y) {
|
public Point2D.Double convertPoint(double x, double y) {
|
||||||
Point2D.Double p = new Point2D.Double(x, y);
|
Point2D.Double p = new Point2D.Double(x, y);
|
||||||
try {
|
try {
|
||||||
transform.inverseTransform(p, p);
|
projection.inverseTransform(p, p);
|
||||||
} catch (NoninvertibleTransformException e) {
|
} catch (NoninvertibleTransformException e) {
|
||||||
assert (false) : "Should not occur";
|
assert (false) : "Should not occur";
|
||||||
return new Point2D.Double(0, 0);
|
return new Point2D.Double(0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
p.setLocation(p.x / EXTRA_SCALE, p.y / EXTRA_SCALE);
|
p.setLocation(p.x, p.y);
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
public Dimension getSubjectOrigin() {
|
||||||
public Dimension getOrigin() {
|
|
||||||
if (modID != finset.getRocket().getAerodynamicModID()) {
|
if (modID != finset.getRocket().getAerodynamicModID()) {
|
||||||
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
|
@Override
|
||||||
public double getFigureWidth() {
|
protected void updateSubjectDimensions(){
|
||||||
if (modID != finset.getRocket().getAerodynamicModID()) {
|
// update subject bounds
|
||||||
modID = finset.getRocket().getAerodynamicModID();
|
BoundingBox newBounds = new BoundingBox();
|
||||||
calculateDimensions();
|
|
||||||
}
|
// subsequent updates can only increase the size of the bounds, so this is the minimum size.
|
||||||
return figureWidth;
|
newBounds.update( MINIMUM_CANVAS_SIZE_METERS);
|
||||||
}
|
|
||||||
|
SymmetricComponent parent = (SymmetricComponent)this.finset.getParent();
|
||||||
@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;
|
|
||||||
|
|
||||||
|
|
||||||
Dimension d = new Dimension((int) (figureWidth * scale + 2 * borderPixelsWidth),
|
// N.B.: (0,0) is the fin front-- where it meets the parent body.
|
||||||
(int) (figureHeight * scale + 2 * borderPixelsHeight));
|
final double xFinFront = finset.asPositionValue(AxialMethod.TOP); //<< in body frame
|
||||||
|
|
||||||
if (!d.equals(getPreferredSize()) || !d.equals(getMinimumSize())) {
|
// update to bound the parent body:
|
||||||
setPreferredSize(d);
|
final double xParentFront = -xFinFront;
|
||||||
setMinimumSize(d);
|
newBounds.update( xParentFront);
|
||||||
revalidate();
|
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
|
@Override
|
||||||
public void updateFigure() {
|
protected void updateCanvasOrigin() {
|
||||||
repaint();
|
originLocation_px.width = borderThickness_px.width - (int)(subjectBounds_m.getX()*scale);
|
||||||
}
|
originLocation_px.height = borderThickness_px.height + (int)(subjectBounds_m.getY()*scale);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,8 +18,12 @@ import java.util.ArrayList;
|
|||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import net.sf.openrocket.gui.figureelements.FigureElement;
|
import net.sf.openrocket.gui.figureelements.FigureElement;
|
||||||
import net.sf.openrocket.gui.rocketfigure.RocketComponentShape;
|
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.ColorConversion;
|
||||||
import net.sf.openrocket.gui.util.SwingPreferences;
|
import net.sf.openrocket.gui.util.SwingPreferences;
|
||||||
import net.sf.openrocket.motor.Motor;
|
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.Rocket;
|
||||||
import net.sf.openrocket.rocketcomponent.RocketComponent;
|
import net.sf.openrocket.rocketcomponent.RocketComponent;
|
||||||
import net.sf.openrocket.startup.Application;
|
import net.sf.openrocket.startup.Application;
|
||||||
|
import net.sf.openrocket.util.BoundingBox;
|
||||||
import net.sf.openrocket.util.BugException;
|
import net.sf.openrocket.util.BugException;
|
||||||
import net.sf.openrocket.util.Coordinate;
|
import net.sf.openrocket.util.Coordinate;
|
||||||
import net.sf.openrocket.util.LineStyle;
|
import net.sf.openrocket.util.LineStyle;
|
||||||
@ -46,6 +51,8 @@ import net.sf.openrocket.util.Transformation;
|
|||||||
*/
|
*/
|
||||||
@SuppressWarnings("serial")
|
@SuppressWarnings("serial")
|
||||||
public class RocketFigure extends AbstractScaleFigure {
|
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_PACKAGE = "net.sf.openrocket.gui.rocketfigure";
|
||||||
private static final String ROCKET_FIGURE_SUFFIX = "Shapes";
|
private static final String ROCKET_FIGURE_SUFFIX = "Shapes";
|
||||||
@ -61,28 +68,17 @@ public class RocketFigure extends AbstractScaleFigure {
|
|||||||
private Rocket rocket;
|
private Rocket rocket;
|
||||||
|
|
||||||
private RocketComponent[] selection = new RocketComponent[0];
|
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 RocketPanel.VIEW_TYPE currentViewType = RocketPanel.VIEW_TYPE.SideView;
|
||||||
|
|
||||||
private double rotation;
|
private double rotation;
|
||||||
private Transformation transformation;
|
private Transformation axialRotation;
|
||||||
|
|
||||||
private double translateX, translateY;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* figureComponents contains the corresponding RocketComponents of the figureShapes
|
* figureComponents contains the corresponding RocketComponents of the figureShapes
|
||||||
*/
|
*/
|
||||||
private final ArrayList<RocketComponentShape> figureShapes = new ArrayList<RocketComponentShape>();
|
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> relativeExtra = new ArrayList<FigureElement>();
|
||||||
private final ArrayList<FigureElement> absoluteExtra = new ArrayList<FigureElement>();
|
private final ArrayList<FigureElement> absoluteExtra = new ArrayList<FigureElement>();
|
||||||
@ -96,27 +92,11 @@ public class RocketFigure extends AbstractScaleFigure {
|
|||||||
this.rocket = _rkt;
|
this.rocket = _rkt;
|
||||||
|
|
||||||
this.rotation = 0.0;
|
this.rotation = 0.0;
|
||||||
this.transformation = Transformation.rotate_x(0.0);
|
this.axialRotation = Transformation.rotate_x(0.0);
|
||||||
|
|
||||||
updateFigure();
|
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() {
|
public RocketComponent[] getSelection() {
|
||||||
return selection;
|
return selection;
|
||||||
}
|
}
|
||||||
@ -136,14 +116,14 @@ public class RocketFigure extends AbstractScaleFigure {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Transformation getRotateTransformation() {
|
public Transformation getRotateTransformation() {
|
||||||
return transformation;
|
return axialRotation;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setRotation(double rot) {
|
public void setRotation(double rot) {
|
||||||
if (MathUtil.equals(rotation, rot))
|
if (MathUtil.equals(rotation, rot))
|
||||||
return;
|
return;
|
||||||
this.rotation = rot;
|
this.rotation = rot;
|
||||||
this.transformation = Transformation.rotate_x(rotation);
|
this.axialRotation = Transformation.rotate_x(rotation);
|
||||||
updateFigure();
|
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) {
|
public void addRelativeExtra(FigureElement p) {
|
||||||
relativeExtra.add(p);
|
relativeExtra.add(p);
|
||||||
}
|
}
|
||||||
@ -219,49 +183,15 @@ public class RocketFigure extends AbstractScaleFigure {
|
|||||||
|
|
||||||
AffineTransform baseTransform = g2.getTransform();
|
AffineTransform baseTransform = g2.getTransform();
|
||||||
|
|
||||||
// Update figure shapes if necessary
|
updateSubjectDimensions();
|
||||||
if (figureShapes == null)
|
updateCanvasOrigin();
|
||||||
updateFigure();
|
updateCanvasSize();
|
||||||
|
updateTransform();
|
||||||
|
|
||||||
|
figureShapes.clear();
|
||||||
|
updateShapeTree( this.figureShapes, rocket, this.axialRotation, Coordinate.ZERO);
|
||||||
|
|
||||||
|
g2.transform(projection);
|
||||||
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);
|
|
||||||
|
|
||||||
// Set rendering hints appropriately
|
// Set rendering hints appropriately
|
||||||
g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
|
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) {
|
public RocketComponent[] getComponentsByPoint(double x, double y) {
|
||||||
// Calculate point in shapes' coordinates
|
// Calculate point in shapes' coordinates
|
||||||
Point2D.Double p = new Point2D.Double(x, y);
|
Point2D.Double p = new Point2D.Double(x, y);
|
||||||
try {
|
try {
|
||||||
g2transformation.inverseTransform(p, p);
|
projection.inverseTransform(p, p);
|
||||||
} catch (NoninvertibleTransformException e) {
|
} catch (NoninvertibleTransformException e) {
|
||||||
return new RocketComponent[0];
|
return new RocketComponent[0];
|
||||||
}
|
}
|
||||||
@ -408,52 +327,54 @@ public class RocketFigure extends AbstractScaleFigure {
|
|||||||
return l.toArray(new RocketComponent[0]);
|
return l.toArray(new RocketComponent[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: Recursive function
|
// NOTE: Recursive function
|
||||||
private void getShapeTree(
|
private ArrayList<RocketComponentShape> updateShapeTree(
|
||||||
ArrayList<RocketComponentShape> allShapes, // output parameter
|
ArrayList<RocketComponentShape> allShapes, // output parameter
|
||||||
final RocketComponent comp,
|
final RocketComponent comp,
|
||||||
final Transformation parentTransform,
|
final Transformation parentTransform,
|
||||||
final Coordinate parentLocation){
|
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;
|
final int instanceCount = comp.getInstanceCount();
|
||||||
if( 0.00001 < Math.abs( currentAngle )) {
|
Coordinate[] instanceLocations = comp.getInstanceLocations();
|
||||||
Transformation currentAngleTransform = Transformation.rotate_x( currentAngle );
|
instanceLocations = parentTransform.transform( instanceLocations );
|
||||||
currentTransform = currentAngleTransform.applyTransformation( parentTransform );
|
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()));
|
||||||
Coordinate currentLocation = parentLocation.add( instanceLocations[index] );
|
}
|
||||||
|
|
||||||
// System.err.println(String.format("@%s: %s -- inst: [%d/%d]", comp.getClass().getSimpleName(), comp.getName(), index+1, instanceCount));
|
// iterate over the aggregated instances *for the whole* tree.
|
||||||
// System.err.println(String.format(" -- stage: %d, active: %b, config: (%d) %s", comp.getStageNumber(), this.getConfiguration().isComponentActive(comp), this.getConfiguration().instanceNumber, this.getConfiguration().getId()));
|
for( int index = 0; instanceCount > index ; ++index ){
|
||||||
// System.err.println(String.format(" -- %s + %s = %s", parentLocation.toString(), instanceLocations[index].toString(), currentLocation.toString()));
|
final double currentAngle = instanceAngles[index];
|
||||||
// if( 0.00001 < Math.abs( currentAngle )) {
|
|
||||||
// System.err.println(String.format(" -- at: %6.4f radians", currentAngle));
|
Transformation currentTransform = parentTransform;
|
||||||
// }
|
if( 0.00001 < Math.abs( currentAngle )) {
|
||||||
|
Transformation currentAngleTransform = Transformation.rotate_x( currentAngle );
|
||||||
// generate shape for this component, if active
|
currentTransform = currentAngleTransform.applyTransformation( parentTransform );
|
||||||
if( this.rocket.getSelectedConfiguration().isComponentActive( comp )){
|
}
|
||||||
allShapes = addThisShape( allShapes, this.currentViewType, comp, currentLocation, currentTransform);
|
|
||||||
}
|
Coordinate currentLocation = parentLocation.add( instanceLocations[index] );
|
||||||
|
|
||||||
// recurse into component's children
|
// System.err.println(String.format("@%s: %s -- inst: [%d/%d]", comp.getClass().getSimpleName(), comp.getName(), index+1, instanceCount));
|
||||||
for( RocketComponent child: comp.getChildren() ){
|
// System.err.println(String.format(" -- stage: %d, active: %b, config: (%d) %s", comp.getStageNumber(), this.getConfiguration().isComponentActive(comp), this.getConfiguration().instanceNumber, this.getConfiguration().getId()));
|
||||||
// draw a tree for each instance subcomponent
|
// System.err.println(String.format(" -- %s + %s = %s", parentLocation.toString(), instanceLocations[index].toString(), currentLocation.toString()));
|
||||||
getShapeTree( allShapes, child, currentTransform, currentLocation );
|
// 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.
|
* Gets the bounds of the drawn subject in Model-Space
|
||||||
* The bounds are stored in the variables minX, maxX and maxR.
|
*
|
||||||
*/
|
* i.e. the maximum extents in the selected dimensions.
|
||||||
private void calculateFigureBounds() {
|
* The bounds are stored in the variables minX, maxX and maxR.
|
||||||
Collection<Coordinate> bounds = rocket.getSelectedConfiguration().getBounds();
|
*
|
||||||
|
* @return
|
||||||
if (bounds.isEmpty()) {
|
*/
|
||||||
minX = 0;
|
@Override
|
||||||
maxX = 0;
|
protected void updateSubjectDimensions() {
|
||||||
maxR = 0;
|
// calculate bounds, and store in class variables
|
||||||
return;
|
final BoundingBox bounds = rocket.getSelectedConfiguration().getBoundingBox();
|
||||||
}
|
|
||||||
|
switch (currentViewType) {
|
||||||
minX = Double.MAX_VALUE;
|
case SideView:
|
||||||
maxX = Double.MIN_VALUE;
|
subjectBounds_m = new Rectangle2D.Double(bounds.min.x, bounds.min.y, bounds.span().x, bounds.span().y);
|
||||||
maxR = 0;
|
break;
|
||||||
for (Coordinate c : bounds) {
|
case BackView:
|
||||||
double x = c.x, r = MathUtil.hypot(c.y, c.z);
|
final double maxR = Math.max(Math.hypot(bounds.min.y, bounds.min.z), Math.hypot(bounds.max.y, bounds.max.z));
|
||||||
if (x < minX)
|
subjectBounds_m = new Rectangle2D.Double(-maxR, -maxR, 2 * maxR, 2 * maxR);
|
||||||
minX = x;
|
break;
|
||||||
if (x > maxX)
|
default:
|
||||||
maxX = x;
|
throw new BugException("Illegal figure type = " + currentViewType);
|
||||||
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);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculates the necessary size of the figure and set the PreferredSize
|
* Calculates the necessary size of the figure and set the PreferredSize
|
||||||
* property accordingly.
|
* property accordingly.
|
||||||
*/
|
*/
|
||||||
private void calculateSize() {
|
@Override
|
||||||
Rectangle2D dimensions = this.getDimensions();
|
protected void updateCanvasOrigin() {
|
||||||
|
|
||||||
figureHeight = dimensions.getHeight();
|
final Dimension subjectArea = new Dimension((int)(subjectBounds_m.getWidth()*scale),
|
||||||
figureWidth = dimensions.getWidth();
|
(int)(subjectBounds_m.getHeight()*scale));
|
||||||
|
|
||||||
figureWidthPx = (int) (figureWidth * scale);
|
final int newOriginY = borderThickness_px.height + (int)(subjectArea.getHeight() / 2);
|
||||||
figureHeightPx = (int) (figureHeight * scale);
|
if (currentViewType == RocketPanel.VIEW_TYPE.BackView){
|
||||||
|
int newOriginX = borderThickness_px.width + getWidth() / 2;
|
||||||
Dimension dpx = new Dimension(
|
originLocation_px = new Dimension(newOriginX, newOriginY);
|
||||||
figureWidthPx + 2 * borderPixelsWidth,
|
}else {
|
||||||
figureHeightPx + 2 * borderPixelsHeight);
|
int newOriginX = borderThickness_px.width + (getWidth() - subjectArea.width) / 2;
|
||||||
|
originLocation_px = new Dimension(newOriginX, newOriginY);
|
||||||
if (!dpx.equals(getPreferredSize()) || !dpx.equals(getMinimumSize())) {
|
}
|
||||||
setPreferredSize(dpx);
|
|
||||||
setMinimumSize(dpx);
|
|
||||||
revalidate();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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);
|
|
||||||
}
|
|
||||||
@ -17,7 +17,6 @@ import java.util.EventObject;
|
|||||||
|
|
||||||
import javax.swing.BorderFactory;
|
import javax.swing.BorderFactory;
|
||||||
import javax.swing.JComponent;
|
import javax.swing.JComponent;
|
||||||
import javax.swing.JPanel;
|
|
||||||
import javax.swing.JScrollPane;
|
import javax.swing.JScrollPane;
|
||||||
import javax.swing.ScrollPaneConstants;
|
import javax.swing.ScrollPaneConstants;
|
||||||
import javax.swing.event.ChangeEvent;
|
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.Unit;
|
||||||
import net.sf.openrocket.unit.UnitGroup;
|
import net.sf.openrocket.unit.UnitGroup;
|
||||||
import net.sf.openrocket.util.BugException;
|
import net.sf.openrocket.util.BugException;
|
||||||
|
import net.sf.openrocket.util.MathUtil;
|
||||||
import net.sf.openrocket.util.StateChangeListener;
|
import net.sf.openrocket.util.StateChangeListener;
|
||||||
|
|
||||||
|
|
||||||
@ -44,6 +44,7 @@ import net.sf.openrocket.util.StateChangeListener;
|
|||||||
*
|
*
|
||||||
* @author Sampo Niskanen <sampo.niskanen@iki.fi>
|
* @author Sampo Niskanen <sampo.niskanen@iki.fi>
|
||||||
*/
|
*/
|
||||||
|
@SuppressWarnings("serial")
|
||||||
public class ScaleScrollPane extends JScrollPane
|
public class ScaleScrollPane extends JScrollPane
|
||||||
implements MouseListener, MouseMotionListener {
|
implements MouseListener, MouseMotionListener {
|
||||||
|
|
||||||
@ -51,45 +52,33 @@ public class ScaleScrollPane extends JScrollPane
|
|||||||
public static final int MINOR_TICKS = 3;
|
public static final int MINOR_TICKS = 3;
|
||||||
public static final int MAJOR_TICKS = 30;
|
public static final int MAJOR_TICKS = 30;
|
||||||
|
|
||||||
|
public static final String USER_SCALE_PROPERTY = "UserScale";
|
||||||
|
|
||||||
private JComponent component;
|
private final JComponent component;
|
||||||
private ScaleFigure figure;
|
private final AbstractScaleFigure figure;
|
||||||
|
|
||||||
private DoubleModel rulerUnit;
|
private DoubleModel rulerUnit;
|
||||||
private Ruler horizontalRuler;
|
private Ruler horizontalRuler;
|
||||||
private Ruler verticalRuler;
|
private Ruler verticalRuler;
|
||||||
|
|
||||||
private final boolean allowFit;
|
// is the subject *currently* being fitting
|
||||||
|
|
||||||
private boolean fit = false;
|
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.
|
* Create a scale scroll pane.
|
||||||
*
|
*
|
||||||
* @param component the component to contain (must implement ScaleFigure)
|
* @param component the component to contain (must implement ScaleFigure)
|
||||||
* @param allowFit whether automatic fitting of the figure is allowed
|
* @param allowFit whether automatic fitting of the figure is allowed
|
||||||
*/
|
*/
|
||||||
public ScaleScrollPane(JComponent component, boolean allowFit) {
|
public ScaleScrollPane(final JComponent component) {
|
||||||
super(component);
|
super(component);
|
||||||
|
|
||||||
if (!(component instanceof ScaleFigure)) {
|
if (!(component instanceof AbstractScaleFigure)) {
|
||||||
throw new IllegalArgumentException("component must implement ScaleFigure");
|
throw new IllegalArgumentException("component must implement ScaleFigure");
|
||||||
}
|
}
|
||||||
|
|
||||||
this.component = component;
|
this.component = component;
|
||||||
this.figure = (ScaleFigure) component;
|
this.figure = (AbstractScaleFigure) component;
|
||||||
this.allowFit = allowFit;
|
|
||||||
|
|
||||||
|
|
||||||
rulerUnit = new DoubleModel(0.0, UnitGroup.UNITS_LENGTH);
|
rulerUnit = new DoubleModel(0.0, UnitGroup.UNITS_LENGTH);
|
||||||
rulerUnit.addChangeListener(new ChangeListener() {
|
rulerUnit.addChangeListener(new ChangeListener() {
|
||||||
@ -106,50 +95,45 @@ public class ScaleScrollPane extends JScrollPane
|
|||||||
UnitSelector selector = new UnitSelector(rulerUnit);
|
UnitSelector selector = new UnitSelector(rulerUnit);
|
||||||
selector.setFont(new Font("SansSerif", Font.PLAIN, 8));
|
selector.setFont(new Font("SansSerif", Font.PLAIN, 8));
|
||||||
this.setCorner(JScrollPane.UPPER_LEFT_CORNER, selector);
|
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));
|
this.setBorder(BorderFactory.createLineBorder(Color.LIGHT_GRAY));
|
||||||
|
|
||||||
|
setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
|
||||||
|
setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
|
||||||
|
|
||||||
viewport.addMouseListener(this);
|
viewport.addMouseListener(this);
|
||||||
viewport.addMouseMotionListener(this);
|
viewport.addMouseMotionListener(this);
|
||||||
|
|
||||||
figure.addChangeListener(new StateChangeListener() {
|
figure.addChangeListener(new StateChangeListener() {
|
||||||
@Override
|
@Override
|
||||||
public void stateChanged(EventObject e) {
|
public void stateChanged(EventObject e) {
|
||||||
horizontalRuler.updateSize();
|
horizontalRuler.updateSize();
|
||||||
verticalRuler.updateSize();
|
verticalRuler.updateSize();
|
||||||
if (fit) {
|
if(fit) {
|
||||||
setFitting(true);
|
figure.scaleTo(viewport.getExtentSize());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
viewport.addComponentListener(new ComponentAdapter() {
|
viewport.addComponentListener(new ComponentAdapter() {
|
||||||
@Override
|
@Override
|
||||||
public void componentResized(ComponentEvent e) {
|
public void componentResized(ComponentEvent e) {
|
||||||
if (fit) {
|
if(fit) {
|
||||||
setFitting(true);
|
figure.scaleTo(viewport.getExtentSize());
|
||||||
}
|
}
|
||||||
|
figure.updateFigure();
|
||||||
|
|
||||||
|
horizontalRuler.updateSize();
|
||||||
|
verticalRuler.updateSize();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ScaleFigure getFigure() {
|
public AbstractScaleFigure getFigure() {
|
||||||
return figure;
|
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.
|
* 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.
|
* 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) {
|
public void setFitting(final boolean shouldFit) {
|
||||||
if (fit && !allowFit) {
|
this.fit = shouldFit;
|
||||||
throw new BugException("Attempting to fit figure not allowing fit.");
|
if (shouldFit) {
|
||||||
}
|
|
||||||
this.fit = fit;
|
|
||||||
if (fit) {
|
|
||||||
setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
|
|
||||||
setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER);
|
|
||||||
validate();
|
validate();
|
||||||
Dimension view = viewport.getExtentSize();
|
|
||||||
figure.setScaling(view);
|
Dimension view = viewport.getExtentSize();
|
||||||
} else {
|
figure.scaleTo(view);
|
||||||
setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
|
this.firePropertyChange( USER_SCALE_PROPERTY, 1.0, figure.getUserScale());
|
||||||
setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
|
|
||||||
|
revalidate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public double getUserScale() {
|
||||||
|
return figure.getUserScale();
|
||||||
public double getScaling() {
|
|
||||||
return figure.getScaling();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public double getScale() {
|
public void setScaling(final double newScale) {
|
||||||
return figure.getAbsoluteScale();
|
// match if closer than 1%:
|
||||||
}
|
if( MathUtil.equals(newScale, figure.getUserScale(), 0.01)){
|
||||||
|
return;
|
||||||
public void setScaling(double scale) {
|
}
|
||||||
if (fit) {
|
|
||||||
setFitting(false);
|
// if explicitly setting a zoom level, turn off fitting
|
||||||
}
|
this.fit = false;
|
||||||
figure.setScaling(scale);
|
figure.scaleTo(newScale);
|
||||||
horizontalRuler.repaint();
|
|
||||||
verticalRuler.repaint();
|
revalidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public Unit getCurrentUnit() {
|
public Unit getCurrentUnit() {
|
||||||
return rulerUnit.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 ////////////////
|
//////////////// Mouse handlers ////////////////
|
||||||
|
|
||||||
|
|
||||||
private int dragStartX = 0;
|
private int dragStartX = 0;
|
||||||
private int dragStartY = 0;
|
private int dragStartY = 0;
|
||||||
private Rectangle dragRectangle = null;
|
private Rectangle dragRectangle = null;
|
||||||
@ -288,27 +287,25 @@ public class ScaleScrollPane extends JScrollPane
|
|||||||
repaint();
|
repaint();
|
||||||
}
|
}
|
||||||
|
|
||||||
private double fromPx(int px) {
|
private double fromPx(final int px) {
|
||||||
Dimension origin = figure.getOrigin();
|
Dimension origin = figure.getSubjectOrigin();
|
||||||
if (orientation == HORIZONTAL) {
|
double realValue = Double.NaN;
|
||||||
px -= origin.width;
|
if (orientation == HORIZONTAL) {
|
||||||
} else {
|
realValue = px - origin.width;
|
||||||
// px = -(px - origin.height);
|
} else {
|
||||||
px -= origin.height;
|
realValue = origin.height - px;
|
||||||
}
|
}
|
||||||
return px / figure.getAbsoluteScale();
|
return realValue / figure.getAbsoluteScale();
|
||||||
}
|
}
|
||||||
|
|
||||||
private int toPx(double l) {
|
private int toPx(final double value) {
|
||||||
Dimension origin = figure.getOrigin();
|
final Dimension origin = figure.getSubjectOrigin();
|
||||||
int px = (int) (l * figure.getAbsoluteScale() + 0.5);
|
final int px = (int) (value * figure.getAbsoluteScale() + 0.5);
|
||||||
if (orientation == HORIZONTAL) {
|
if (orientation == HORIZONTAL) {
|
||||||
px += origin.width;
|
return (px + origin.width);
|
||||||
} else {
|
} else {
|
||||||
px = px + origin.height;
|
return (origin.height - px);
|
||||||
// px += origin.height;
|
|
||||||
}
|
}
|
||||||
return px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -322,8 +319,7 @@ public class ScaleScrollPane extends JScrollPane
|
|||||||
// Fill area with background color
|
// Fill area with background color
|
||||||
g2.setColor(getBackground());
|
g2.setColor(getBackground());
|
||||||
g2.fillRect(area.x, area.y, area.width, area.height + 100);
|
g2.fillRect(area.x, area.y, area.width, area.height + 100);
|
||||||
|
|
||||||
|
|
||||||
int startpx, endpx;
|
int startpx, endpx;
|
||||||
if (orientation == HORIZONTAL) {
|
if (orientation == HORIZONTAL) {
|
||||||
startpx = area.x;
|
startpx = area.x;
|
||||||
@ -337,11 +333,19 @@ public class ScaleScrollPane extends JScrollPane
|
|||||||
double start, end, minor, major;
|
double start, end, minor, major;
|
||||||
start = fromPx(startpx);
|
start = fromPx(startpx);
|
||||||
end = fromPx(endpx);
|
end = fromPx(endpx);
|
||||||
|
|
||||||
minor = MINOR_TICKS / figure.getAbsoluteScale();
|
minor = MINOR_TICKS / figure.getAbsoluteScale();
|
||||||
major = MAJOR_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
|
// Set color & hints
|
||||||
g2.setColor(Color.BLACK);
|
g2.setColor(Color.BLACK);
|
||||||
|
|||||||
@ -4,7 +4,6 @@ import java.awt.Component;
|
|||||||
import java.awt.event.ActionEvent;
|
import java.awt.event.ActionEvent;
|
||||||
import java.awt.event.ActionListener;
|
import java.awt.event.ActionListener;
|
||||||
import java.text.DecimalFormat;
|
import java.text.DecimalFormat;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.EventObject;
|
import java.util.EventObject;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
@ -19,6 +18,9 @@ import net.sf.openrocket.util.StateChangeListener;
|
|||||||
@SuppressWarnings("serial")
|
@SuppressWarnings("serial")
|
||||||
public class ScaleSelector extends JPanel {
|
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
|
// Ready zoom settings
|
||||||
private static final DecimalFormat PERCENT_FORMAT = new DecimalFormat("0.#%");
|
private static final DecimalFormat PERCENT_FORMAT = new DecimalFormat("0.#%");
|
||||||
|
|
||||||
@ -45,19 +47,16 @@ public class ScaleSelector extends JPanel {
|
|||||||
button.addActionListener(new ActionListener() {
|
button.addActionListener(new ActionListener() {
|
||||||
@Override
|
@Override
|
||||||
public void actionPerformed(ActionEvent e) {
|
public void actionPerformed(ActionEvent e) {
|
||||||
double scale = scrollPane.getScaling();
|
final double oldScale = scrollPane.getUserScale();
|
||||||
scale = getNextLargerScale(scale);
|
final double newScale = getNextLargerScale(oldScale);
|
||||||
scrollPane.setScaling(scale);
|
scrollPane.setScaling(newScale);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
add(button, "gap");
|
add(button, "gap");
|
||||||
|
|
||||||
// Zoom level selector
|
// Zoom level selector
|
||||||
String[] settings = SCALE_LABELS;
|
String[] settings = SCALE_LABELS;
|
||||||
if (!scrollPane.isFittingAllowed()) {
|
|
||||||
settings = Arrays.copyOf(settings, settings.length - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
scaleSelector = new JComboBox<>(settings);
|
scaleSelector = new JComboBox<>(settings);
|
||||||
scaleSelector.setEditable(true);
|
scaleSelector.setEditable(true);
|
||||||
setZoomText();
|
setZoomText();
|
||||||
@ -68,8 +67,7 @@ public class ScaleSelector extends JPanel {
|
|||||||
String text = (String) scaleSelector.getSelectedItem();
|
String text = (String) scaleSelector.getSelectedItem();
|
||||||
text = text.replaceAll("%", "").trim();
|
text = text.replaceAll("%", "").trim();
|
||||||
|
|
||||||
if (text.toLowerCase(Locale.getDefault()).startsWith(SCALE_FIT.toLowerCase(Locale.getDefault())) &&
|
if (text.toLowerCase(Locale.getDefault()).startsWith(SCALE_FIT.toLowerCase(Locale.getDefault()))){
|
||||||
scrollPane.isFittingAllowed()) {
|
|
||||||
scrollPane.setFitting(true);
|
scrollPane.setFitting(true);
|
||||||
setZoomText();
|
setZoomText();
|
||||||
return;
|
return;
|
||||||
@ -101,7 +99,7 @@ public class ScaleSelector extends JPanel {
|
|||||||
button.addActionListener(new ActionListener() {
|
button.addActionListener(new ActionListener() {
|
||||||
@Override
|
@Override
|
||||||
public void actionPerformed(ActionEvent e) {
|
public void actionPerformed(ActionEvent e) {
|
||||||
double scale = scrollPane.getScaling();
|
double scale = scrollPane.getUserScale();
|
||||||
scale = getNextSmallerScale(scale);
|
scale = getNextSmallerScale(scale);
|
||||||
scrollPane.setScaling(scale);
|
scrollPane.setScaling(scale);
|
||||||
}
|
}
|
||||||
@ -111,7 +109,8 @@ public class ScaleSelector extends JPanel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void setZoomText() {
|
private void setZoomText() {
|
||||||
String text = PERCENT_FORMAT.format(scrollPane.getScaling());
|
final double userScale = scrollPane.getUserScale();
|
||||||
|
String text = PERCENT_FORMAT.format(userScale);
|
||||||
if (scrollPane.isFitting()) {
|
if (scrollPane.isFitting()) {
|
||||||
text = "Fit (" + text + ")";
|
text = "Fit (" + text + ")";
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user