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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 384 - (show annotations)
Fri Sep 11 16:16:38 2009 UTC (15 years, 5 months ago) by alfonx
Original Path: branches/1.0-gt2-2.6/src/gtmig/org/geotools/swing/MouseSelectionTracker.java
File size: 16682 byte(s)
Wow.. this now compiles and works with GT2.6 TRUNK! I will check-in the tunrk libs in AtlasFramework/lib/gt tonight. One big difference between M2 and trunk is that they moved the package
org.geotools.gui.swing to 
org.geotools.swing
So i did the same in schmitzm, especially with the JMapPane
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.swing;
23
24 // Geometry
25 import java.awt.Color;
26 import java.awt.Component;
27 import java.awt.Graphics2D;
28 import java.awt.Rectangle;
29 import java.awt.Shape;
30 import java.awt.event.MouseEvent;
31 import java.awt.geom.AffineTransform;
32 import java.awt.geom.Ellipse2D;
33 import java.awt.geom.Line2D;
34 import java.awt.geom.NoninvertibleTransformException;
35 import java.awt.geom.Point2D;
36 import java.awt.geom.Rectangle2D;
37 import java.awt.geom.RectangularShape;
38 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