create changelog entry
[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.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 import net.sf.openrocket.util.StateChangeListener;
33
34
35
36 /**
37  * A scroll pane that holds a {@link ScaleFigure} and includes rulers that show
38  * natural units.  The figure can be moved by dragging on the figure.
39  * <p>
40  * This class implements both <code>MouseListener</code> and 
41  * <code>MouseMotionListener</code>.  If subclasses require extra functionality
42  * (e.g. checking for clicks) then these methods may be overridden, and only unhandled
43  * events passed to this class.
44  * 
45  * @author Sampo Niskanen <sampo.niskanen@iki.fi>
46  */
47 public class ScaleScrollPane extends JScrollPane
48                 implements MouseListener, MouseMotionListener {
49         
50         public static final int RULER_SIZE = 20;
51         public static final int MINOR_TICKS = 3;
52         public static final int MAJOR_TICKS = 30;
53         
54         
55         private JComponent component;
56         private ScaleFigure figure;
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.addMouseListener(this);
117                 viewport.addMouseMotionListener(this);
118                 
119                 figure.addChangeListener(new StateChangeListener() {
120                         @Override
121                         public void stateChanged(EventObject e) {
122                                 horizontalRuler.updateSize();
123                                 verticalRuler.updateSize();
124                                 if (fit) {
125                                         setFitting(true);
126                                 }
127                         }
128                 });
129                 
130                 viewport.addComponentListener(new ComponentAdapter() {
131                         @Override
132                         public void componentResized(ComponentEvent e) {
133                                 if (fit) {
134                                         setFitting(true);
135                                 }
136                         }
137                 });
138                 
139         }
140         
141         public ScaleFigure getFigure() {
142                 return figure;
143         }
144         
145         
146         /**
147          * Return whether automatic fitting of the figure is allowed.
148          */
149         public boolean isFittingAllowed() {
150                 return allowFit;
151         }
152         
153         /**
154          * Return whether the figure is currently automatically fitted within the component bounds.
155          */
156         public boolean isFitting() {
157                 return fit;
158         }
159         
160         /**
161          * Set whether the figure is automatically fitted within the component bounds.
162          * 
163          * @throws BugException         if automatic fitting is disallowed and <code>fit</code> is <code>true</code>
164          */
165         public void setFitting(boolean fit) {
166                 if (fit && !allowFit) {
167                         throw new BugException("Attempting to fit figure not allowing fit.");
168                 }
169                 this.fit = fit;
170                 if (fit) {
171                         setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
172                         setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER);
173                         validate();
174                         Dimension view = viewport.getExtentSize();
175                         figure.setScaling(view);
176                 } else {
177                         setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
178                         setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
179                 }
180         }
181         
182         
183         
184         public double getScaling() {
185                 return figure.getScaling();
186         }
187         
188         public double getScale() {
189                 return figure.getAbsoluteScale();
190         }
191         
192         public void setScaling(double scale) {
193                 if (fit) {
194                         setFitting(false);
195                 }
196                 figure.setScaling(scale);
197                 horizontalRuler.repaint();
198                 verticalRuler.repaint();
199         }
200         
201         
202         public Unit getCurrentUnit() {
203                 return rulerUnit.getCurrentUnit();
204         }
205         
206         
207         ////////////////  Mouse handlers  ////////////////
208         
209         
210         private int dragStartX = 0;
211         private int dragStartY = 0;
212         private Rectangle dragRectangle = null;
213         
214         @Override
215         public void mousePressed(MouseEvent e) {
216                 dragStartX = e.getX();
217                 dragStartY = e.getY();
218                 dragRectangle = viewport.getViewRect();
219         }
220         
221         @Override
222         public void mouseReleased(MouseEvent e) {
223                 dragRectangle = null;
224         }
225         
226         @Override
227         public void mouseDragged(MouseEvent e) {
228                 if (dragRectangle == null) {
229                         return;
230                 }
231                 
232                 dragRectangle.setLocation(dragStartX - e.getX(), dragStartY - e.getY());
233                 
234                 dragStartX = e.getX();
235                 dragStartY = e.getY();
236                 
237                 viewport.scrollRectToVisible(dragRectangle);
238         }
239         
240         @Override
241         public void mouseClicked(MouseEvent e) {
242         }
243         
244         @Override
245         public void mouseEntered(MouseEvent e) {
246         }
247         
248         @Override
249         public void mouseExited(MouseEvent e) {
250         }
251         
252         @Override
253         public void mouseMoved(MouseEvent e) {
254         }
255         
256         
257         
258         ////////////////  The view port rulers  ////////////////
259         
260         
261         private class Ruler extends JComponent {
262                 public static final int HORIZONTAL = 0;
263                 public static final int VERTICAL = 1;
264                 
265                 private final int orientation;
266                 
267                 public Ruler(int orientation) {
268                         this.orientation = orientation;
269                         updateSize();
270                         
271                         rulerUnit.addChangeListener(new ChangeListener() {
272                                 @Override
273                                 public void stateChanged(ChangeEvent e) {
274                                         Ruler.this.repaint();
275                                 }
276                         });
277                 }
278                 
279                 
280                 public void updateSize() {
281                         Dimension d = component.getPreferredSize();
282                         if (orientation == HORIZONTAL) {
283                                 setPreferredSize(new Dimension(d.width + 10, RULER_SIZE));
284                         } else {
285                                 setPreferredSize(new Dimension(RULER_SIZE, d.height + 10));
286                         }
287                         revalidate();
288                         repaint();
289                 }
290                 
291                 private double fromPx(int px) {
292                         Dimension origin = figure.getOrigin();
293                         if (orientation == HORIZONTAL) {
294                                 px -= origin.width;
295                         } else {
296                                 //                              px = -(px - origin.height);
297                                 px -= origin.height;
298                         }
299                         return px / figure.getAbsoluteScale();
300                 }
301                 
302                 private int toPx(double l) {
303                         Dimension origin = figure.getOrigin();
304                         int px = (int) (l * figure.getAbsoluteScale() + 0.5);
305                         if (orientation == HORIZONTAL) {
306                                 px += origin.width;
307                         } else {
308                                 px = px + origin.height;
309                                 //                              px += origin.height;
310                         }
311                         return px;
312                 }
313                 
314                 
315                 @Override
316                 protected void paintComponent(Graphics g) {
317                         super.paintComponent(g);
318                         Graphics2D g2 = (Graphics2D) g;
319                         
320                         Rectangle area = g2.getClipBounds();
321                         
322                         // Fill area with background color
323                         g2.setColor(getBackground());
324                         g2.fillRect(area.x, area.y, area.width, area.height + 100);
325                         
326                         
327                         int startpx, endpx;
328                         if (orientation == HORIZONTAL) {
329                                 startpx = area.x;
330                                 endpx = area.x + area.width;
331                         } else {
332                                 startpx = area.y;
333                                 endpx = area.y + area.height;
334                         }
335                         
336                         Unit unit = rulerUnit.getCurrentUnit();
337                         double start, end, minor, major;
338                         start = fromPx(startpx);
339                         end = fromPx(endpx);
340                         minor = MINOR_TICKS / figure.getAbsoluteScale();
341                         major = MAJOR_TICKS / figure.getAbsoluteScale();
342                         
343                         Tick[] ticks = unit.getTicks(start, end, minor, major);
344                         
345                         
346                         // Set color & hints
347                         g2.setColor(Color.BLACK);
348                         g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
349                                         RenderingHints.VALUE_STROKE_NORMALIZE);
350                         g2.setRenderingHint(RenderingHints.KEY_RENDERING,
351                                         RenderingHints.VALUE_RENDER_QUALITY);
352                         g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
353                                         RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
354                         
355                         for (Tick t : ticks) {
356                                 int position = toPx(t.value);
357                                 drawTick(g2, position, t);
358                         }
359                 }
360                 
361                 private void drawTick(Graphics g, int position, Tick t) {
362                         int length;
363                         String str = null;
364                         if (t.major) {
365                                 length = RULER_SIZE / 2;
366                         } else {
367                                 if (t.notable)
368                                         length = RULER_SIZE / 3;
369                                 else
370                                         length = RULER_SIZE / 6;
371                         }
372                         
373                         // Set font
374                         if (t.major) {
375                                 str = rulerUnit.getCurrentUnit().toString(t.value);
376                                 if (t.notable)
377                                         g.setFont(new Font("SansSerif", Font.BOLD, 9));
378                                 else
379                                         g.setFont(new Font("SansSerif", Font.PLAIN, 9));
380                         }
381                         
382                         // Draw tick & text
383                         if (orientation == HORIZONTAL) {
384                                 g.drawLine(position, RULER_SIZE - length, position, RULER_SIZE);
385                                 if (str != null)
386                                         g.drawString(str, position, RULER_SIZE - length - 1);
387                         } else {
388                                 g.drawLine(RULER_SIZE - length, position, RULER_SIZE, position);
389                                 if (str != null)
390                                         g.drawString(str, 1, position - 1);
391                         }
392                 }
393         }
394 }