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