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;
17 import javax.swing.BorderFactory;
18 import javax.swing.JComponent;
19 import javax.swing.JPanel;
20 import javax.swing.JScrollPane;
21 import javax.swing.JViewport;
22 import javax.swing.ScrollPaneConstants;
23 import javax.swing.event.ChangeEvent;
24 import javax.swing.event.ChangeListener;
26 import net.sf.openrocket.gui.adaptors.DoubleModel;
27 import net.sf.openrocket.gui.components.UnitSelector;
28 import net.sf.openrocket.unit.Tick;
29 import net.sf.openrocket.unit.Unit;
30 import net.sf.openrocket.unit.UnitGroup;
31 import net.sf.openrocket.util.BugException;
36 * A scroll pane that holds a {@link ScaleFigure} and includes rulers that show
37 * natural units. The figure can be moved by dragging on the figure.
39 * This class implements both <code>MouseListener</code> and
40 * <code>MouseMotionListener</code>. If subclasses require extra functionality
41 * (e.g. checking for clicks) then these methods may be overridden, and only unhandled
42 * events passed to this class.
44 * @author Sampo Niskanen <sampo.niskanen@iki.fi>
46 public class ScaleScrollPane extends JScrollPane
47 implements MouseListener, MouseMotionListener {
49 public static final int RULER_SIZE = 20;
50 public static final int MINOR_TICKS = 3;
51 public static final int MAJOR_TICKS = 30;
54 private JComponent component;
55 private ScaleFigure figure;
56 private JViewport viewport;
58 private DoubleModel rulerUnit;
59 private Ruler horizontalRuler;
60 private Ruler verticalRuler;
62 private final boolean allowFit;
64 private boolean fit = false;
67 public ScaleScrollPane(JComponent component) {
68 this(component, true);
71 public ScaleScrollPane(JComponent component, boolean allowFit) {
73 // super(component, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
74 // JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
76 if (!(component instanceof ScaleFigure)) {
77 throw new IllegalArgumentException("component must implement ScaleFigure");
80 this.component = component;
81 this.figure = (ScaleFigure)component;
82 this.allowFit = allowFit;
85 rulerUnit = new DoubleModel(0.0,UnitGroup.UNITS_LENGTH);
86 rulerUnit.addChangeListener(new ChangeListener() {
88 public void stateChanged(ChangeEvent e) {
89 ScaleScrollPane.this.component.repaint();
92 horizontalRuler = new Ruler(Ruler.HORIZONTAL);
93 verticalRuler = new Ruler(Ruler.VERTICAL);
94 this.setColumnHeaderView(horizontalRuler);
95 this.setRowHeaderView(verticalRuler);
97 UnitSelector selector = new UnitSelector(rulerUnit);
98 selector.setFont(new Font("SansSerif", Font.PLAIN, 8));
99 this.setCorner(JScrollPane.UPPER_LEFT_CORNER, selector);
100 this.setCorner(JScrollPane.UPPER_RIGHT_CORNER, new JPanel());
101 this.setCorner(JScrollPane.LOWER_LEFT_CORNER, new JPanel());
102 this.setCorner(JScrollPane.LOWER_RIGHT_CORNER, new JPanel());
104 this.setBorder(BorderFactory.createLineBorder(Color.LIGHT_GRAY));
107 viewport = this.getViewport();
108 viewport.addMouseListener(this);
109 viewport.addMouseMotionListener(this);
111 figure.addChangeListener(new ChangeListener() {
113 public void stateChanged(ChangeEvent e) {
114 horizontalRuler.updateSize();
115 verticalRuler.updateSize();
122 viewport.addComponentListener(new ComponentAdapter() {
124 public void componentResized(ComponentEvent e) {
133 public ScaleFigure getFigure() {
138 public boolean isFittingAllowed() {
142 public boolean isFitting() {
146 public void setFitting(boolean fit) {
147 if (fit && !allowFit) {
148 throw new BugException("Attempting to fit figure not allowing fit.");
152 setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
153 setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER);
155 Dimension view = viewport.getExtentSize();
156 figure.setScaling(view);
158 setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
159 setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
165 public double getScaling() {
166 return figure.getScaling();
169 public double getScale() {
170 return figure.getAbsoluteScale();
173 public void setScaling(double scale) {
177 figure.setScaling(scale);
178 horizontalRuler.repaint();
179 verticalRuler.repaint();
183 public Unit getCurrentUnit() {
184 return rulerUnit.getCurrentUnit();
188 //////////////// Mouse handlers ////////////////
191 private int dragStartX=0;
192 private int dragStartY=0;
193 private Rectangle dragRectangle = null;
196 public void mousePressed(MouseEvent e) {
197 dragStartX = e.getX();
198 dragStartY = e.getY();
199 dragRectangle = viewport.getViewRect();
203 public void mouseReleased(MouseEvent e) {
204 dragRectangle = null;
208 public void mouseDragged(MouseEvent e) {
209 if (dragRectangle==null) {
213 dragRectangle.setLocation(dragStartX-e.getX(),dragStartY-e.getY());
215 dragStartX = e.getX();
216 dragStartY = e.getY();
218 viewport.scrollRectToVisible(dragRectangle);
222 public void mouseClicked(MouseEvent e) {
226 public void mouseEntered(MouseEvent e) {
230 public void mouseExited(MouseEvent e) {
234 public void mouseMoved(MouseEvent e) {
239 //////////////// The view port rulers ////////////////
242 private class Ruler extends JComponent {
243 public static final int HORIZONTAL = 0;
244 public static final int VERTICAL = 1;
246 private final int orientation;
248 public Ruler(int orientation) {
249 this.orientation = orientation;
252 rulerUnit.addChangeListener(new ChangeListener() {
254 public void stateChanged(ChangeEvent e) {
255 Ruler.this.repaint();
261 public void updateSize() {
262 Dimension d = component.getPreferredSize();
263 if (orientation == HORIZONTAL) {
264 setPreferredSize(new Dimension(d.width+10,RULER_SIZE));
266 setPreferredSize(new Dimension(RULER_SIZE,d.height+10));
272 private double fromPx(int px) {
273 Dimension origin = figure.getOrigin();
274 if (orientation == HORIZONTAL) {
277 // px = -(px - origin.height);
280 return px/figure.getAbsoluteScale();
283 private int toPx(double l) {
284 Dimension origin = figure.getOrigin();
285 int px = (int)(l * figure.getAbsoluteScale() + 0.5);
286 if (orientation == HORIZONTAL) {
289 px = px + origin.height;
290 // px += origin.height;
297 protected void paintComponent(Graphics g) {
298 super.paintComponent(g);
299 Graphics2D g2 = (Graphics2D)g;
301 Rectangle area = g2.getClipBounds();
303 // Fill area with background color
304 g2.setColor(getBackground());
305 g2.fillRect(area.x, area.y, area.width, area.height+100);
309 if (orientation == HORIZONTAL) {
311 endpx = area.x+area.width;
314 endpx = area.y+area.height;
317 Unit unit = rulerUnit.getCurrentUnit();
318 double start,end,minor,major;
319 start = fromPx(startpx);
321 minor = MINOR_TICKS/figure.getAbsoluteScale();
322 major = MAJOR_TICKS/figure.getAbsoluteScale();
324 Tick[] ticks = unit.getTicks(start, end, minor, major);
328 g2.setColor(Color.BLACK);
329 g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
330 RenderingHints.VALUE_STROKE_NORMALIZE);
331 g2.setRenderingHint(RenderingHints.KEY_RENDERING,
332 RenderingHints.VALUE_RENDER_QUALITY);
333 g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
334 RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
336 for (Tick t: ticks) {
337 int position = toPx(t.value);
338 drawTick(g2,position,t);
342 private void drawTick(Graphics g, int position, Tick t) {
346 length = RULER_SIZE/2;
349 length = RULER_SIZE/3;
351 length = RULER_SIZE/6;
356 str = rulerUnit.getCurrentUnit().toString(t.value);
358 g.setFont(new Font("SansSerif", Font.BOLD, 9));
360 g.setFont(new Font("SansSerif", Font.PLAIN, 9));
364 if (orientation == HORIZONTAL) {
365 g.drawLine(position, RULER_SIZE-length, position, RULER_SIZE);
367 g.drawString(str, position, RULER_SIZE-length-1);
369 g.drawLine(RULER_SIZE-length, position, RULER_SIZE, position);
371 g.drawString(str, 1, position-1);