Updates for 0.9.5
[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         public ScaleScrollPane(JComponent component) {
68                 this(component, true);
69         }
70         
71         public ScaleScrollPane(JComponent component, boolean allowFit) {
72                 super(component);
73 //              super(component, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, 
74 //                              JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
75                 
76                 if (!(component instanceof ScaleFigure)) {
77                         throw new IllegalArgumentException("component must implement ScaleFigure");
78                 }
79                 
80                 this.component = component;
81                 this.figure = (ScaleFigure)component;
82                 this.allowFit = allowFit;
83                 
84
85                 rulerUnit = new DoubleModel(0.0,UnitGroup.UNITS_LENGTH);
86                 rulerUnit.addChangeListener(new ChangeListener() {
87                         @Override
88                         public void stateChanged(ChangeEvent e) {
89                                 ScaleScrollPane.this.component.repaint();
90                         }
91                 });
92                 horizontalRuler = new Ruler(Ruler.HORIZONTAL);
93                 verticalRuler = new Ruler(Ruler.VERTICAL);
94                 this.setColumnHeaderView(horizontalRuler);
95                 this.setRowHeaderView(verticalRuler);
96                 
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());
103
104                 this.setBorder(BorderFactory.createLineBorder(Color.LIGHT_GRAY));
105
106                 
107                 viewport = this.getViewport();
108                 viewport.addMouseListener(this);
109                 viewport.addMouseMotionListener(this);
110                 
111                 figure.addChangeListener(new ChangeListener() {
112                         @Override
113                         public void stateChanged(ChangeEvent e) {
114                                 horizontalRuler.updateSize();
115                                 verticalRuler.updateSize();
116                                 if (fit) {
117                                         setFitting(true);
118                                 }
119                         }
120                 });
121                 
122                 viewport.addComponentListener(new ComponentAdapter() {
123                         @Override
124                         public void componentResized(ComponentEvent e) {
125                                 if (fit) {
126                                         setFitting(true);
127                                 }
128                         }
129                 });
130                 
131         }
132         
133         public ScaleFigure getFigure() {
134                 return figure;
135         }
136         
137         
138         public boolean isFittingAllowed() {
139                 return allowFit;
140         }
141         
142         public boolean isFitting() {
143                 return fit;
144         }
145         
146         public void setFitting(boolean fit) {
147                 if (fit && !allowFit) {
148                         throw new BugException("Attempting to fit figure not allowing fit.");
149                 }
150                 this.fit = fit;
151                 if (fit) {
152                         setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
153                         setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER);
154                         validate();
155                         Dimension view = viewport.getExtentSize();
156                         figure.setScaling(view);
157                 } else {
158                         setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
159                         setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
160                 }
161         }
162         
163         
164
165         public double getScaling() {
166                 return figure.getScaling();
167         }
168         
169         public double getScale() {
170                 return figure.getAbsoluteScale();
171         }
172
173         public void setScaling(double scale) {
174                 if (fit) {
175                         setFitting(false);
176                 }
177                 figure.setScaling(scale);
178                 horizontalRuler.repaint();
179                 verticalRuler.repaint();
180         }
181         
182         
183         public Unit getCurrentUnit() {
184                 return rulerUnit.getCurrentUnit();
185         }
186         
187         
188         ////////////////  Mouse handlers  ////////////////
189         
190
191         private int dragStartX=0;
192         private int dragStartY=0;
193         private Rectangle dragRectangle = null;
194
195         @Override
196         public void mousePressed(MouseEvent e) {
197                 dragStartX = e.getX();
198                 dragStartY = e.getY();
199                 dragRectangle = viewport.getViewRect();
200         }
201
202         @Override
203         public void mouseReleased(MouseEvent e) {
204                 dragRectangle = null;
205         }
206
207         @Override
208         public void mouseDragged(MouseEvent e) {
209                 if (dragRectangle==null) {
210                         return;
211                 }
212
213                 dragRectangle.setLocation(dragStartX-e.getX(),dragStartY-e.getY());
214
215                 dragStartX = e.getX();
216                 dragStartY = e.getY();
217                 
218                 viewport.scrollRectToVisible(dragRectangle);
219         }
220
221         @Override
222         public void mouseClicked(MouseEvent e) {
223         }
224
225         @Override
226         public void mouseEntered(MouseEvent e) {
227         }
228
229         @Override
230         public void mouseExited(MouseEvent e) {
231         }
232
233         @Override
234         public void mouseMoved(MouseEvent e) {
235         }
236         
237
238         
239         ////////////////  The view port rulers  ////////////////
240         
241
242         private class Ruler extends JComponent {
243                 public static final int HORIZONTAL = 0;
244                 public static final int VERTICAL = 1;
245                 
246                 private final int orientation;
247                 
248                 public Ruler(int orientation) {
249                         this.orientation = orientation;
250                         updateSize();
251                         
252                         rulerUnit.addChangeListener(new ChangeListener() {
253                                 @Override
254                                 public void stateChanged(ChangeEvent e) {
255                                         Ruler.this.repaint();
256                                 }
257                         });
258                 }
259                 
260                 
261                 public void updateSize() {
262                         Dimension d = component.getPreferredSize();
263                         if (orientation == HORIZONTAL) {
264                                 setPreferredSize(new Dimension(d.width+10,RULER_SIZE));
265                         } else {
266                                 setPreferredSize(new Dimension(RULER_SIZE,d.height+10));
267                         }
268                         revalidate();
269                         repaint();
270                 }
271                 
272                 private double fromPx(int px) {
273                         Dimension origin = figure.getOrigin();
274                         if (orientation == HORIZONTAL) {
275                                 px -= origin.width;
276                         } else {
277 //                              px = -(px - origin.height);
278                                 px -= origin.height;
279                         }
280                         return px/figure.getAbsoluteScale();
281                 }
282                 
283                 private int toPx(double l) {
284                         Dimension origin = figure.getOrigin();
285                         int px = (int)(l * figure.getAbsoluteScale() + 0.5);
286                         if (orientation == HORIZONTAL) {
287                                 px += origin.width;
288                         } else {
289                                 px = px + origin.height;
290 //                              px += origin.height;
291                         }
292                         return px;
293                 }
294                 
295                 
296             @Override
297                 protected void paintComponent(Graphics g) {
298                 super.paintComponent(g);
299                         Graphics2D g2 = (Graphics2D)g;
300                         
301                         Rectangle area = g2.getClipBounds();
302
303                 // Fill area with background color
304                         g2.setColor(getBackground());
305                 g2.fillRect(area.x, area.y, area.width, area.height+100);
306
307
308                 int startpx,endpx;
309                 if (orientation == HORIZONTAL) {
310                         startpx = area.x;
311                         endpx = area.x+area.width;
312                 } else {
313                         startpx = area.y;
314                         endpx = area.y+area.height;
315                 }
316                 
317                 Unit unit = rulerUnit.getCurrentUnit();
318                 double start,end,minor,major;
319                 start = fromPx(startpx);
320                 end = fromPx(endpx);
321                 minor = MINOR_TICKS/figure.getAbsoluteScale();
322                 major = MAJOR_TICKS/figure.getAbsoluteScale();
323
324                 Tick[] ticks = unit.getTicks(start, end, minor, major);
325                 
326                 
327                 // Set color & hints
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);
335
336                         for (Tick t: ticks) {
337                         int position = toPx(t.value);
338                         drawTick(g2,position,t);
339                 }
340             }
341             
342             private void drawTick(Graphics g, int position, Tick t) {
343                 int length;
344                 String str = null;
345                 if (t.major) {
346                         length = RULER_SIZE/2;
347                 } else {
348                         if (t.notable)
349                                 length = RULER_SIZE/3;
350                         else
351                                 length = RULER_SIZE/6;
352                 }
353                 
354                 // Set font
355                 if (t.major) {
356                         str = rulerUnit.getCurrentUnit().toString(t.value);
357                         if (t.notable)
358                         g.setFont(new Font("SansSerif", Font.BOLD, 9));
359                         else 
360                                 g.setFont(new Font("SansSerif", Font.PLAIN, 9));
361                 }
362                 
363                 // Draw tick & text
364                 if (orientation == HORIZONTAL) {
365                         g.drawLine(position, RULER_SIZE-length, position, RULER_SIZE);
366                         if (str != null)
367                                 g.drawString(str, position, RULER_SIZE-length-1);
368                 } else {
369                         g.drawLine(RULER_SIZE-length, position, RULER_SIZE, position);
370                         if (str != null)
371                                 g.drawString(str, 1, position-1);
372                 }
373             }
374         }
375 }