1 package net.sf.openrocket.gui.scalefigure;
5 import java.awt.Dimension;
7 import java.awt.Graphics;
8 import java.awt.Graphics2D;
9 import java.awt.Rectangle;
10 import java.awt.RenderingHints;
11 import java.awt.event.ComponentAdapter;
12 import java.awt.event.ComponentEvent;
13 import java.awt.event.MouseEvent;
14 import java.awt.event.MouseListener;
15 import java.awt.event.MouseMotionListener;
16 import java.util.EventObject;
18 import javax.swing.BorderFactory;
19 import javax.swing.JComponent;
20 import javax.swing.JPanel;
21 import javax.swing.JScrollPane;
22 import javax.swing.JViewport;
23 import javax.swing.ScrollPaneConstants;
24 import javax.swing.event.ChangeEvent;
25 import javax.swing.event.ChangeListener;
27 import net.sf.openrocket.gui.adaptors.DoubleModel;
28 import net.sf.openrocket.gui.components.UnitSelector;
29 import net.sf.openrocket.unit.Tick;
30 import net.sf.openrocket.unit.Unit;
31 import net.sf.openrocket.unit.UnitGroup;
32 import net.sf.openrocket.util.BugException;
33 import net.sf.openrocket.util.StateChangeListener;
38 * A scroll pane that holds a {@link ScaleFigure} and includes rulers that show
39 * natural units. The figure can be moved by dragging on the figure.
41 * This class implements both <code>MouseListener</code> and
42 * <code>MouseMotionListener</code>. If subclasses require extra functionality
43 * (e.g. checking for clicks) then these methods may be overridden, and only unhandled
44 * events passed to this class.
46 * @author Sampo Niskanen <sampo.niskanen@iki.fi>
48 public class ScaleScrollPane extends JScrollPane
49 implements MouseListener, MouseMotionListener {
51 public static final int RULER_SIZE = 20;
52 public static final int MINOR_TICKS = 3;
53 public static final int MAJOR_TICKS = 30;
56 private JComponent component;
57 private ScaleFigure figure;
58 private JViewport viewport;
60 private DoubleModel rulerUnit;
61 private Ruler horizontalRuler;
62 private Ruler verticalRuler;
64 private final boolean allowFit;
66 private boolean fit = false;
70 * Create a scale scroll pane that allows fitting.
72 * @param component the component to contain (must implement ScaleFigure)
74 public ScaleScrollPane(JComponent component) {
75 this(component, true);
79 * Create a scale scroll pane.
81 * @param component the component to contain (must implement ScaleFigure)
82 * @param allowFit whether automatic fitting of the figure is allowed
84 public ScaleScrollPane(JComponent component, boolean allowFit) {
87 if (!(component instanceof ScaleFigure)) {
88 throw new IllegalArgumentException("component must implement ScaleFigure");
91 this.component = component;
92 this.figure = (ScaleFigure) component;
93 this.allowFit = allowFit;
96 rulerUnit = new DoubleModel(0.0, UnitGroup.UNITS_LENGTH);
97 rulerUnit.addChangeListener(new ChangeListener() {
99 public void stateChanged(ChangeEvent e) {
100 ScaleScrollPane.this.component.repaint();
103 horizontalRuler = new Ruler(Ruler.HORIZONTAL);
104 verticalRuler = new Ruler(Ruler.VERTICAL);
105 this.setColumnHeaderView(horizontalRuler);
106 this.setRowHeaderView(verticalRuler);
108 UnitSelector selector = new UnitSelector(rulerUnit);
109 selector.setFont(new Font("SansSerif", Font.PLAIN, 8));
110 this.setCorner(JScrollPane.UPPER_LEFT_CORNER, selector);
111 this.setCorner(JScrollPane.UPPER_RIGHT_CORNER, new JPanel());
112 this.setCorner(JScrollPane.LOWER_LEFT_CORNER, new JPanel());
113 this.setCorner(JScrollPane.LOWER_RIGHT_CORNER, new JPanel());
115 this.setBorder(BorderFactory.createLineBorder(Color.LIGHT_GRAY));
118 viewport = this.getViewport();
119 viewport.addMouseListener(this);
120 viewport.addMouseMotionListener(this);
122 figure.addChangeListener(new StateChangeListener() {
124 public void stateChanged(EventObject e) {
125 horizontalRuler.updateSize();
126 verticalRuler.updateSize();
133 viewport.addComponentListener(new ComponentAdapter() {
135 public void componentResized(ComponentEvent e) {
144 public ScaleFigure getFigure() {
150 * Return whether automatic fitting of the figure is allowed.
152 public boolean isFittingAllowed() {
157 * Return whether the figure is currently automatically fitted within the component bounds.
159 public boolean isFitting() {
164 * Set whether the figure is automatically fitted within the component bounds.
166 * @throws BugException if automatic fitting is disallowed and <code>fit</code> is <code>true</code>
168 public void setFitting(boolean fit) {
169 if (fit && !allowFit) {
170 throw new BugException("Attempting to fit figure not allowing fit.");
174 setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
175 setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER);
177 Dimension view = viewport.getExtentSize();
178 figure.setScaling(view);
180 setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
181 setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
187 public double getScaling() {
188 return figure.getScaling();
191 public double getScale() {
192 return figure.getAbsoluteScale();
195 public void setScaling(double scale) {
199 figure.setScaling(scale);
200 horizontalRuler.repaint();
201 verticalRuler.repaint();
205 public Unit getCurrentUnit() {
206 return rulerUnit.getCurrentUnit();
210 //////////////// Mouse handlers ////////////////
213 private int dragStartX = 0;
214 private int dragStartY = 0;
215 private Rectangle dragRectangle = null;
218 public void mousePressed(MouseEvent e) {
219 dragStartX = e.getX();
220 dragStartY = e.getY();
221 dragRectangle = viewport.getViewRect();
225 public void mouseReleased(MouseEvent e) {
226 dragRectangle = null;
230 public void mouseDragged(MouseEvent e) {
231 if (dragRectangle == null) {
235 dragRectangle.setLocation(dragStartX - e.getX(), dragStartY - e.getY());
237 dragStartX = e.getX();
238 dragStartY = e.getY();
240 viewport.scrollRectToVisible(dragRectangle);
244 public void mouseClicked(MouseEvent e) {
248 public void mouseEntered(MouseEvent e) {
252 public void mouseExited(MouseEvent e) {
256 public void mouseMoved(MouseEvent e) {
261 //////////////// The view port rulers ////////////////
264 private class Ruler extends JComponent {
265 public static final int HORIZONTAL = 0;
266 public static final int VERTICAL = 1;
268 private final int orientation;
270 public Ruler(int orientation) {
271 this.orientation = orientation;
274 rulerUnit.addChangeListener(new ChangeListener() {
276 public void stateChanged(ChangeEvent e) {
277 Ruler.this.repaint();
283 public void updateSize() {
284 Dimension d = component.getPreferredSize();
285 if (orientation == HORIZONTAL) {
286 setPreferredSize(new Dimension(d.width + 10, RULER_SIZE));
288 setPreferredSize(new Dimension(RULER_SIZE, d.height + 10));
294 private double fromPx(int px) {
295 Dimension origin = figure.getOrigin();
296 if (orientation == HORIZONTAL) {
299 // px = -(px - origin.height);
302 return px / figure.getAbsoluteScale();
305 private int toPx(double l) {
306 Dimension origin = figure.getOrigin();
307 int px = (int) (l * figure.getAbsoluteScale() + 0.5);
308 if (orientation == HORIZONTAL) {
311 px = px + origin.height;
312 // px += origin.height;
319 protected void paintComponent(Graphics g) {
320 super.paintComponent(g);
321 Graphics2D g2 = (Graphics2D) g;
323 Rectangle area = g2.getClipBounds();
325 // Fill area with background color
326 g2.setColor(getBackground());
327 g2.fillRect(area.x, area.y, area.width, area.height + 100);
331 if (orientation == HORIZONTAL) {
333 endpx = area.x + area.width;
336 endpx = area.y + area.height;
339 Unit unit = rulerUnit.getCurrentUnit();
340 double start, end, minor, major;
341 start = fromPx(startpx);
343 minor = MINOR_TICKS / figure.getAbsoluteScale();
344 major = MAJOR_TICKS / figure.getAbsoluteScale();
346 Tick[] ticks = unit.getTicks(start, end, minor, major);
350 g2.setColor(Color.BLACK);
351 g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
352 RenderingHints.VALUE_STROKE_NORMALIZE);
353 g2.setRenderingHint(RenderingHints.KEY_RENDERING,
354 RenderingHints.VALUE_RENDER_QUALITY);
355 g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
356 RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
358 for (Tick t : ticks) {
359 int position = toPx(t.value);
360 drawTick(g2, position, t);
364 private void drawTick(Graphics g, int position, Tick t) {
368 length = RULER_SIZE / 2;
371 length = RULER_SIZE / 3;
373 length = RULER_SIZE / 6;
378 str = rulerUnit.getCurrentUnit().toString(t.value);
380 g.setFont(new Font("SansSerif", Font.BOLD, 9));
382 g.setFont(new Font("SansSerif", Font.PLAIN, 9));
386 if (orientation == HORIZONTAL) {
387 g.drawLine(position, RULER_SIZE - length, position, RULER_SIZE);
389 g.drawString(str, position, RULER_SIZE - length - 1);
391 g.drawLine(RULER_SIZE - length, position, RULER_SIZE, position);
393 g.drawString(str, 1, position - 1);