/[schmitzm]/trunk/src/skrueger/geotools/MouseSelectionTracker.java
ViewVC logotype

Annotation of /trunk/src/skrueger/geotools/MouseSelectionTracker.java

Parent Directory Parent Directory | Revision Log Revision Log


Revision 604 - (hide annotations)
Wed Dec 9 14:15:53 2009 UTC (15 years, 2 months ago) by alfonx
Original Path: branches/2.0-RC1/src/skrueger/geotools/MouseSelectionTracker.java
File size: 16675 byte(s)
2.0-RC1 branch ist für GP1.3 bugfixes...  1.0-gt2-2.6  ist der entwicklungs brnach 
1 mojays 320 // Migration process to Geotools 2.6:
2     // Because not included in gt2-2.6-M2 this class is taken 1:1
3     // from Geotools 2.4.5 and made public.
4     // TODO: It should be removed or included in SCHMITZM.
5    
6     /*
7     * GeoTools - OpenSource mapping toolkit
8     * http://geotools.org
9     * (C) 2003-2006, Geotools Project Managment Committee (PMC)
10     * (C) 2001, Institut de Recherche pour le D�veloppement
11     *
12     * This library is free software; you can redistribute it and/or
13     * modify it under the terms of the GNU Lesser General Public
14     * License as published by the Free Software Foundation; either
15     * version 2.1 of the License, or (at your option) any later version.
16     *
17     * This library is distributed in the hope that it will be useful,
18     * but WITHOUT ANY WARRANTY; without even the implied warranty of
19     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20     * Lesser General Public License for more details.
21     */
22 alfonx 531 package skrueger.geotools;
23 mojays 320
24     // Geometry
25 mojays 325 import java.awt.Color;
26     import java.awt.Component;
27     import java.awt.Graphics2D;
28     import java.awt.Rectangle;
29 mojays 320 import java.awt.Shape;
30 mojays 325 import java.awt.event.MouseEvent;
31     import java.awt.geom.AffineTransform;
32     import java.awt.geom.Ellipse2D;
33 mojays 320 import java.awt.geom.Line2D;
34 mojays 325 import java.awt.geom.NoninvertibleTransformException;
35 mojays 320 import java.awt.geom.Point2D;
36     import java.awt.geom.Rectangle2D;
37 mojays 325 import java.awt.geom.RectangularShape;
38 mojays 320 import java.awt.geom.RoundRectangle2D;
39    
40     import javax.swing.event.MouseInputAdapter;
41    
42    
43     /**
44     * Controller which allows the user to select a region of a component. The user must click on a
45     * point in the component, then drag the mouse pointer whilst keeping the button pressed. During
46     * the dragging, the shape which is drawn will normally be a rectangle. Other shapes could always
47     * be used such as, for example, an ellipse. To use this class, it is necessary to create a derived
48     * class which defines the following methods:
49     *
50     * <ul>
51     * <li>{@link #selectionPerformed} (obligatory)</li>
52     * <li>{@link #getModel} (optional)</li>
53     * </ul>
54     *
55     * This controller should then be registered with one, and only one, component
56     * using the following syntax:
57     *
58     * <blockquote><pre>
59     * {@link Component} component=...
60     * MouseSelectionTracker control=...
61     * component.addMouseListener(control);
62     * </pre></blockquote>
63     *
64     * @since 2.0
65     * @source $URL: http://svn.geotools.org/tags/2.4.5/modules/extension/widgets-swing/src/main/java/org/geotools/gui/swing/MouseSelectionTracker.java $
66     * @version $Id: MouseSelectionTracker.java 22482 2006-10-31 02:58:00Z desruisseaux $
67     * @author Martin Desruisseaux
68     */
69     public abstract class MouseSelectionTracker extends MouseInputAdapter {
70     /**
71     * Stippled rectangle representing the region which the user is currently
72     * selecting. This rectangle can be empty. These coordinates are only
73     * significant in the period between the user pressing the mouse button
74     * and then releasing it to outline a region. Conventionally, the
75     * {@code null} value indicates that a line should be used instead of
76     * a rectangular shape. The coordinates are always expressed in pixels.
77     */
78     private transient RectangularShape mouseSelectedArea;
79    
80     /**
81     * Colour to replace during XOR drawings on a graphic.
82     * This colour is specified in {@link Graphics2D#setColor}.
83     */
84     private Color backXORColor = Color.white;
85    
86     /**
87     * Colour to replace with during the XOR drawings on a graphic.
88     * This colour is specified in {@link Graphics2D#setXORMode}.
89     */
90     private Color lineXORColor = Color.black;
91    
92     /**
93     * <var>x</var> coordinate of the mouse when the button is pressed.
94     */
95     private transient int ox;
96    
97     /**
98     * <var>y</var> coordinate of the mouse when the button is pressed.
99     */
100     private transient int oy;
101    
102     /**
103     * <var>x</var> coordinate of the mouse during the last drag.
104     */
105     private transient int px;
106    
107     /**
108     * <var>y</var> coordinate of the mouse during the last drag.
109     */
110     private transient int py;
111    
112     /**
113     * Indicates whether a selection is underway.
114     */
115     private transient boolean isDragging;
116    
117     /**
118     * Constructs an object which will allow rectangular regions to be selected using the mouse.
119     */
120     public MouseSelectionTracker() {
121     }
122    
123     /**
124     * Specifies the colours to be used for drawing the outline of a box when
125     * the user selects a region. All {@code a} colours will be replaced
126     * by {@code b} colours and vice versa.
127     */
128     public void setXORColors(final Color a, final Color b) {
129     backXORColor = a;
130     lineXORColor = b;
131     }
132    
133     /**
134     * Returns the geometric shape to use for marking the boundaries of a region. This shape is
135     * normally a rectangle but could also be an ellipse, an arrow or even other shapes. The
136     * coordinates of the returned shape will not be taken into account. In fact, these coordinates
137     * will regularly be discarded. Only the class of the returned shape will count (for example,
138     * {@link java.awt.geom.Ellipse2D} vs {@link java.awt.geom.Rectangle2D}) and their parameters
139     * which are not linked to their position (for example, the rounding of a rectangle's
140     * corners).
141     * <p>
142     * The shape returned will normally be from a class derived from {@link RectangularShape},
143     * but could also be from the {@link Line2D} class. <strong>Any other class risks throwing a
144     * {@link ClassCastException} when executed</strong>.
145     *
146     * The default implementation always returns an object {@link Rectangle}.
147     *
148     * @param event Mouse coordinate when the button is pressed. This information can be used by
149     * the derived classes which like to be informed of the position of the mouse before
150     * chosing a geometric shape.
151     * @return Shape from the class {link RectangularShape} or {link Line2D}, or {@code null}
152     * to indicate that we do not want to make a selection.
153     */
154     protected Shape getModel(final MouseEvent event) {
155     return new Rectangle();
156     }
157    
158     /**
159     * Method which is automatically called after the user selects a region with the mouse.
160     * All coordinates passed in as parameters are expressed in pixels.
161     *
162     * @param ox <var>x</var> coordinate of the mouse when the user pressed the mouse button.
163     * @param oy <var>y</var> coordinate of the mouse when the user pressed the mouse button.
164     * @param px <var>x</var> coordinate of the mouse when the user released the mouse button.
165     * @param py <var>y</var> coordinate of the mouse when the user released the mouse button.
166     */
167     protected abstract void selectionPerformed(int ox, int oy, int px, int py);
168    
169     /**
170     * Returns the geometric shape surrounding the last region to be selected by the user. An
171     * optional affine transform can be specified to convert the region selected by the user
172     * into logical coordinates. The class of the shape returned depends on the model returned by
173     * {@link #getModel}:
174     *
175     * <ul>
176     * <li>If the model is null (which means that this {@code MouseSelectionTracker} object only
177     * draws a line between points), the object returned will belong to the {@link Line2D}
178     * class.</li>
179     * <li>If the model is not null, the object returned can be from the same class (most often
180     * {@link java.awt.geom.Rectangle2D}). There could always be situations where the object
181     * returned is from another class, for example if the affine transform carries out a
182     * rotation.</li>
183     * </ul>
184     *
185     * @param transform Affine transform which converts logical coordinates into pixel coordinates.
186     * It is usually an affine transform which is used in a {@code paint(...)} method to
187     * draw shapes expressed in logical coordinates.
188     * @return A geometric shape enclosing the last region to be selected by the user, or
189     * {@code null} if no selection has yet been made.
190     * @throws NoninvertibleTransformException If the affine transform can't be inverted.
191     */
192     public Shape getSelectedArea(final AffineTransform transform) throws NoninvertibleTransformException {
193     if (ox == px && oy == py) {
194     return null;
195     }
196     RectangularShape shape = mouseSelectedArea;
197     if (transform != null && !transform.isIdentity()) {
198     if (shape == null) {
199     final Point2D.Float po = new Point2D.Float(ox, oy);
200     final Point2D.Float pp = new Point2D.Float(px, py);
201     transform.inverseTransform(po, po);
202     transform.inverseTransform(pp, pp);
203     return new Line2D.Float(po, pp);
204     } else {
205     if (canReshape(shape, transform)) {
206     final Point2D.Double point = new Point2D.Double();
207     double xmin = Double.POSITIVE_INFINITY;
208     double ymin = Double.POSITIVE_INFINITY;
209     double xmax = Double.NEGATIVE_INFINITY;
210     double ymax = Double.NEGATIVE_INFINITY;
211     for (int i = 0; i < 4; i++) {
212     point.x = (i&1) == 0 ? shape.getMinX() : shape.getMaxX();
213     point.y = (i&2) == 0 ? shape.getMinY() : shape.getMaxY();
214     transform.inverseTransform(point, point);
215     if (point.x < xmin) xmin = point.x;
216     if (point.x > xmax) xmax = point.x;
217     if (point.y < ymin) ymin = point.y;
218     if (point.y > ymax) ymax = point.y;
219     }
220     if (shape instanceof Rectangle) {
221     return new Rectangle2D.Float((float) xmin,
222     (float) ymin,
223     (float) (xmax - xmin),
224     (float) (ymax - ymin));
225     } else {
226     shape = (RectangularShape) shape.clone();
227     shape.setFrame(xmin, ymin, xmax - xmin, ymax - ymin);
228     return shape;
229     }
230     }
231     else {
232     return transform.createInverse().createTransformedShape(shape);
233     }
234     }
235     }
236     else {
237     return (shape != null) ? (Shape) shape.clone() : new Line2D.Float(ox, oy, px, py);
238     }
239     }
240    
241     /**
242     * Indicates whether we can transform {@code shape} simply by calling its
243     * {@code shape.setFrame(...)} method rather than by using the heavy artillery
244     * that is the {@code transform.createTransformedShape(shape)} method.
245     */
246     private static boolean canReshape(final RectangularShape shape,
247     final AffineTransform transform) {
248     final int type=transform.getType();
249     if ((type & AffineTransform.TYPE_GENERAL_TRANSFORM) != 0) return false;
250     if ((type & AffineTransform.TYPE_MASK_ROTATION) != 0) return false;
251     if ((type & AffineTransform.TYPE_FLIP) != 0) {
252     if (shape instanceof Rectangle2D) return true;
253     if (shape instanceof Ellipse2D) return true;
254     if (shape instanceof RoundRectangle2D) return true;
255     return false;
256     }
257     return true;
258     }
259    
260     /**
261     * Returns a {@link Graphics2D} object to be used for drawing in the specified component. We
262     * must not forget to call {@link Graphics2D#dispose} when the graphics object is no longer
263     * needed.
264     */
265     private Graphics2D getGraphics(final Component c) {
266     final Graphics2D graphics = (Graphics2D) c.getGraphics();
267     graphics.setXORMode(lineXORColor);
268     graphics.setColor (backXORColor);
269     return graphics;
270     }
271    
272     /**
273     * Informs this controller that the mouse button has been pressed.
274     * The default implementation retains the mouse coordinate (which will
275     * become one of the corners of the future rectangle to be drawn)
276     * and prepares {@code this} to observe the mouse movements.
277     *
278     * @throws ClassCastException if {@link #getModel} doesn't return a shape
279     * from the class {link RectangularShape} or {link Line2D}.
280     */
281     public void mousePressed(final MouseEvent event) throws ClassCastException {
282     if (!event.isConsumed() && (event.getModifiers() & MouseEvent.BUTTON1_MASK) != 0) {
283     final Component source = event.getComponent();
284     if (source != null) {
285     Shape model = getModel(event);
286     if (model != null) {
287     isDragging = true;
288     ox = px = event.getX();
289     oy = py = event.getY();
290     if (model instanceof Line2D) {
291     model = null;
292     }
293     mouseSelectedArea = (RectangularShape) model;
294     if (mouseSelectedArea != null) {
295     mouseSelectedArea.setFrame(ox, oy, 0, 0);
296     }
297     source.addMouseMotionListener(this);
298     }
299     source.requestFocus();
300     event.consume();
301     }
302     }
303     }
304    
305     /**
306     * Informs this controller that the mouse has been dragged. The default
307     * implementation uses this to move a corner of the rectangle used to
308     * select the region. The other corner remains fixed at the point
309     * where the mouse was at the moment its button was pressed..
310     */
311     public void mouseDragged(final MouseEvent event) {
312     if (isDragging) {
313     final Graphics2D graphics = getGraphics(event.getComponent());
314     if (mouseSelectedArea == null) {
315     graphics.drawLine(ox, oy, px, py);
316     px = event.getX();
317     py = event.getY();
318     graphics.drawLine(ox, oy, px, py);
319     } else {
320     graphics.draw(mouseSelectedArea);
321     int xmin = this.ox;
322     int ymin = this.oy;
323     int xmax = px = event.getX();
324     int ymax = py = event.getY();
325     if (xmin > xmax) {
326     final int xtmp = xmin;
327     xmin = xmax; xmax = xtmp;
328     }
329     if (ymin > ymax) {
330     final int ytmp = ymin;
331     ymin = ymax; ymax = ytmp;
332     }
333     mouseSelectedArea.setFrame(xmin, ymin, xmax - xmin, ymax - ymin);
334     graphics.draw(mouseSelectedArea);
335     }
336     graphics.dispose();
337     event.consume();
338     }
339     }
340    
341     /**
342     * Informs this controller that the mouse button has been released.
343     * The default implementation calls {@link #selectionPerformed} with
344     * the bounds of the selected region as parameters.
345     */
346     public void mouseReleased(final MouseEvent event) {
347     if (isDragging && (event.getModifiers() & MouseEvent.BUTTON1_MASK) != 0) {
348     isDragging = false;
349     final Component component = event.getComponent();
350     component.removeMouseMotionListener(this);
351    
352     final Graphics2D graphics = getGraphics(event.getComponent());
353     if (mouseSelectedArea == null) {
354     graphics.drawLine(ox, oy, px, py);
355     } else {
356     graphics.draw(mouseSelectedArea);
357     }
358     graphics.dispose();
359     px = event.getX();
360     py = event.getY();
361     selectionPerformed(ox, oy, px, py);
362     event.consume();
363     }
364     }
365    
366     /**
367     * Informs this controller that the mouse has been moved but not as a
368     * result of the user selecting a region. The default implementation
369     * signals to the source component that {@code this} is no longer
370     * interested in being informed about mouse movements.
371     */
372     public void mouseMoved(final MouseEvent event) {
373     // Normally not necessary, but it seems that this "listener"
374     // sometimes stays in place when it shouldn't.
375     event.getComponent().removeMouseMotionListener(this);
376     }
377     }

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26