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