75ec602f10f8f8d16d06c89968f6862bdaa9cf3b
[debian/openrocket] / src / net / sf / openrocket / gui / scalefigure / ScaleScrollPane.java
1 package net.sf.openrocket.gui.scalefigure;
2
3
4 import java.awt.Color;
5 import java.awt.Dimension;
6 import java.awt.Font;
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
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;
25
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;
32
33
34
35 /**
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.
38  * <p>
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.
43  * 
44  * @author Sampo Niskanen <sampo.niskanen@iki.fi>
45  */
46 public class ScaleScrollPane extends JScrollPane
47                 implements MouseListener, MouseMotionListener {
48         
49         public static final int RULER_SIZE = 20;
50         public static final int MINOR_TICKS = 3;
51         public static final int MAJOR_TICKS = 30;
52         
53
54         private JComponent component;
55         private ScaleFigure figure;
56         private JViewport viewport;
57         
58         private DoubleModel rulerUnit;
59         private Ruler horizontalRuler;
60         private Ruler verticalRuler;
61         
62         private final boolean allowFit;
63         
64         private boolean fit = false;
65         
66         
67         /**
68          * Create a scale scroll pane that allows fitting.
69          * 
70          * @param component             the component to contain (must implement ScaleFigure)
71          */
72         public ScaleScrollPane(JComponent component) {
73                 this(component, true);
74         }
75         
76         /**
77          * Create a scale scroll pane.
78          * 
79          * @param component             the component to contain (must implement ScaleFigure)
80          * @param allowFit              whether automatic fitting of the figure is allowed
81          */
82         public ScaleScrollPane(JComponent component, boolean allowFit) {
83                 super(component);
84                 
85                 if (!(component instanceof ScaleFigure)) {
86                         throw new IllegalArgumentException("component must implement ScaleFigure");
87                 }
88                 
89                 this.component = component;
90                 this.figure = (ScaleFigure) component;
91                 this.allowFit = allowFit;
92                 
93
94                 rulerUnit = new DoubleModel(0.0, UnitGroup.UNITS_LENGTH);
95                 rulerUnit.addChangeListener(new ChangeListener() {
96                         @Override
97                         public void stateChanged(ChangeEvent e) {
98                                 ScaleScrollPane.this.component.repaint();
99                         }
100                 });
101                 horizontalRuler = new Ruler(Ruler.HORIZONTAL);
102                 verticalRuler = new Ruler(Ruler.VERTICAL);
103                 this.setColumnHeaderView(horizontalRuler);
104                 this.setRowHeaderView(verticalRuler);
105                 
106                 UnitSelector selector = new UnitSelector(rulerUnit);
107                 selector.setFont(new Font("SansSerif", Font.PLAIN, 8));
108                 this.setCorner(JScrollPane.UPPER_LEFT_CORNER, selector);
109                 this.setCorner(JScrollPane.UPPER_RIGHT_CORNER, new JPanel());
110                 this.setCorner(JScrollPane.LOWER_LEFT_CORNER, new JPanel());
111                 this.setCorner(JScrollPane.LOWER_RIGHT_CORNER, new JPanel());
112                 
113                 this.setBorder(BorderFactory.createLineBorder(Color.LIGHT_GRAY));
114                 
115
116                 viewport = this.getViewport();
117                 viewport.addMouseListener(this);
118                 viewport.addMouseMotionListener(this);
119                 
120                 figure.addChangeListener(new ChangeListener() {
121                         @Override
122                         public void stateChanged(ChangeEvent e) {
123                                 horizontalRuler.updateSize();
124                                 verticalRuler.updateSize();
125                                 if (fit) {
126                                         setFitting(true);
127                                 }
128                         }
129                 });
130                 
131                 viewport.addComponentListener(new ComponentAdapter() {
132                         @Override
133                         public void componentResized(ComponentEvent e) {
134                                 if (fit) {
135                                         setFitting(true);
136                                 }
137                         }
138                 });
139                 
140         }
141         
142         public ScaleFigure getFigure() {
143                 return figure;
144         }
145         
146         
147         /**
148          * Return whether automatic fitting of the figure is allowed.
149          */
150         public boolean isFittingAllowed() {
151                 return allowFit;
152         }
153         
154         /**
155          * Return whether the figure is currently automatically fitted within the component bounds.
156          */
157         public boolean isFitting() {
158                 return fit;
159         }
160         
161         /**
162          * Set whether the figure is automatically fitted within the component bounds.
163          * 
164          * @throws BugException         if automatic fitting is disallowed and <code>fit</code> is <code>true</code>
165          */
166         public void setFitting(boolean fit) {
167                 if (fit && !allowFit) {
168                         throw new BugException("Attempting to fit figure not allowing fit.");
169                 }
170                 this.fit = fit;
171                 if (fit) {
172                         setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
173                         setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER);
174                         validate();
175                         Dimension view = viewport.getExtentSize();
176                         figure.setScaling(view);
177                 } else {
178                         setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
179                         setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
180                 }
181         }
182         
183         
184
185         public double getScaling() {
186                 return figure.getScaling();
187         }
188         
189         public double getScale() {
190                 return figure.getAbsoluteScale();
191         }
192         
193         public void setScaling(double scale) {
194                 if (fit) {
195                         setFitting(false);
196                 }
197                 figure.setScaling(scale);
198                 horizontalRuler.repaint();
199                 verticalRuler.repaint();
200         }
201         
202         
203         public Unit getCurrentUnit() {
204                 return rulerUnit.getCurrentUnit();
205         }
206         
207         
208         ////////////////  Mouse handlers  ////////////////
209         
210
211         private int dragStartX = 0;
212         private int dragStartY = 0;
213         private Rectangle dragRectangle = null;
214         
215         @Override
216         public void mousePressed(MouseEvent e) {
217                 dragStartX = e.getX();
218                 dragStartY = e.getY();
219                 dragRectangle = viewport.getViewRect();
220         }
221         
222         @Override
223         public void mouseReleased(MouseEvent e) {
224                 dragRectangle = null;
225         }
226         
227         @Override
228         public void mouseDragged(MouseEvent e) {
229                 if (dragRectangle == null) {
230                         return;
231                 }
232                 
233                 dragRectangle.setLocation(dragStartX - e.getX(), dragStartY - e.getY());
234                 
235                 dragStartX = e.getX();
236                 dragStartY = e.getY();
237                 
238                 viewport.scrollRectToVisible(dragRectangle);
239         }
240         
241         @Override
242         public void mouseClicked(MouseEvent e) {
243         }
244         
245         @Override
246         public void mouseEntered(MouseEvent e) {
247         }
248         
249         @Override
250         public void mouseExited(MouseEvent e) {
251         }
252         
253         @Override
254         public void mouseMoved(MouseEvent e) {
255         }
256         
257         
258
259         ////////////////  The view port rulers  ////////////////
260         
261
262         private class Ruler extends JComponent {
263                 public static final int HORIZONTAL = 0;
264                 public static final int VERTICAL = 1;
265                 
266                 private final int orientation;
267                 
268                 public Ruler(int orientation) {
269                         this.orientation = orientation;
270                         updateSize();
271                         
272                         rulerUnit.addChangeListener(new ChangeListener() {
273                                 @Override
274                                 public void stateChanged(ChangeEvent e) {
275                                         Ruler.this.repaint();
276                                 }
277                         });
278                 }
279                 
280                 
281                 public void updateSize() {
282                         Dimension d = component.getPreferredSize();
283                         if (orientation == HORIZONTAL) {
284                                 setPreferredSize(new Dimension(d.width + 10, RULER_SIZE));
285                         } else {
286                                 setPreferredSize(new Dimension(RULER_SIZE, d.height + 10));
287                         }
288                         revalidate();
289                         repaint();
290                 }
291                 
292                 private double fromPx(int px) {
293                         Dimension origin = figure.getOrigin();
294                         if (orientation == HORIZONTAL) {
295                                 px -= origin.width;
296                         } else {
297                                 //                              px = -(px - origin.height);
298                                 px -= origin.height;
299                         }
300                         return px / figure.getAbsoluteScale();
301                 }
302                 
303                 private int toPx(double l) {
304                         Dimension origin = figure.getOrigin();
305                         int px = (int) (l * figure.getAbsoluteScale() + 0.5);
306                         if (orientation == HORIZONTAL) {
307                                 px += origin.width;
308                         } else {
309                                 px = px + origin.height;
310                                 //                              px += origin.height;
311                         }
312                         return px;
313                 }
314                 
315                 
316                 @Override
317                 protected void paintComponent(Graphics g) {
318                         super.paintComponent(g);
319                         Graphics2D g2 = (Graphics2D) g;
320                         
321                         Rectangle area = g2.getClipBounds();
322                         
323                         // Fill area with background color
324                         g2.setColor(getBackground());
325                         g2.fillRect(area.x, area.y, area.width, area.height + 100);
326                         
327
328                         int startpx, endpx;
329                         if (orientation == HORIZONTAL) {
330                                 startpx = area.x;
331                                 endpx = area.x + area.width;
332                         } else {
333                                 startpx = area.y;
334                                 endpx = area.y + area.height;
335                         }
336                         
337                         Unit unit = rulerUnit.getCurrentUnit();
338                         double start, end, minor, major;
339                         start = fromPx(startpx);
340                         end = fromPx(endpx);
341                         minor = MINOR_TICKS / figure.getAbsoluteScale();
342                         major = MAJOR_TICKS / figure.getAbsoluteScale();
343                         
344                         Tick[] ticks = unit.getTicks(start, end, minor, major);
345                         
346
347                         // Set color & hints
348                         g2.setColor(Color.BLACK);
349                         g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
350                                         RenderingHints.VALUE_STROKE_NORMALIZE);
351                         g2.setRenderingHint(RenderingHints.KEY_RENDERING,
352                                         RenderingHints.VALUE_RENDER_QUALITY);
353                         g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
354                                         RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
355                         
356                         for (Tick t : ticks) {
357                                 int position = toPx(t.value);
358                                 drawTick(g2, position, t);
359                         }
360                 }
361                 
362                 private void drawTick(Graphics g, int position, Tick t) {
363                         int length;
364                         String str = null;
365                         if (t.major) {
366                                 length = RULER_SIZE / 2;
367                         } else {
368                                 if (t.notable)
369                                         length = RULER_SIZE / 3;
370                                 else
371                                         length = RULER_SIZE / 6;
372                         }
373                         
374                         // Set font
375                         if (t.major) {
376                                 str = rulerUnit.getCurrentUnit().toString(t.value);
377                                 if (t.notable)
378                                         g.setFont(new Font("SansSerif", Font.BOLD, 9));
379                                 else
380                                         g.setFont(new Font("SansSerif", Font.PLAIN, 9));
381                         }
382                         
383                         // Draw tick & text
384                         if (orientation == HORIZONTAL) {
385                                 g.drawLine(position, RULER_SIZE - length, position, RULER_SIZE);
386                                 if (str != null)
387                                         g.drawString(str, position, RULER_SIZE - length - 1);
388                         } else {
389                                 g.drawLine(RULER_SIZE - length, position, RULER_SIZE, position);
390                                 if (str != null)
391                                         g.drawString(str, 1, position - 1);
392                         }
393                 }
394         }
395 }