/[schmitzm]/branches/2.0-RC2/src/skrueger/geotools/MouseSelectionTracker.java
ViewVC logotype

Contents of /branches/2.0-RC2/src/skrueger/geotools/MouseSelectionTracker.java

Parent Directory Parent Directory | Revision Log Revision Log


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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26