source: josm/trunk/src/org/openstreetmap/josm/gui/NavigatableComponent.java@ 5565

Last change on this file since 5565 was 5565, checked in by Don-vip, 11 years ago

see #8153 - fix area computation

  • Property svn:eol-style set to native
File size: 51.4 KB
Line 
1// License: GPL. See LICENSE file for details.
2package org.openstreetmap.josm.gui;
3
4import static org.openstreetmap.josm.tools.I18n.marktr;
5
6import java.awt.Cursor;
7import java.awt.Graphics;
8import java.awt.Point;
9import java.awt.Polygon;
10import java.awt.Rectangle;
11import java.awt.geom.AffineTransform;
12import java.awt.geom.Point2D;
13import java.util.ArrayList;
14import java.util.Collection;
15import java.util.Collections;
16import java.util.Date;
17import java.util.HashSet;
18import java.util.LinkedHashMap;
19import java.util.LinkedList;
20import java.util.List;
21import java.util.Locale;
22import java.util.Map;
23import java.util.Set;
24import java.util.Stack;
25import java.util.TreeMap;
26import java.util.concurrent.CopyOnWriteArrayList;
27
28import javax.swing.JComponent;
29
30import org.openstreetmap.josm.Main;
31import org.openstreetmap.josm.data.Bounds;
32import org.openstreetmap.josm.data.ProjectionBounds;
33import org.openstreetmap.josm.data.coor.CachedLatLon;
34import org.openstreetmap.josm.data.coor.EastNorth;
35import org.openstreetmap.josm.data.coor.LatLon;
36import org.openstreetmap.josm.data.osm.BBox;
37import org.openstreetmap.josm.data.osm.DataSet;
38import org.openstreetmap.josm.data.osm.Node;
39import org.openstreetmap.josm.data.osm.OsmPrimitive;
40import org.openstreetmap.josm.data.osm.Relation;
41import org.openstreetmap.josm.data.osm.Way;
42import org.openstreetmap.josm.data.osm.WaySegment;
43import org.openstreetmap.josm.data.osm.visitor.paint.PaintColors;
44import org.openstreetmap.josm.data.preferences.IntegerProperty;
45import org.openstreetmap.josm.data.projection.Projection;
46import org.openstreetmap.josm.data.projection.Projections;
47import org.openstreetmap.josm.gui.help.Helpful;
48import org.openstreetmap.josm.gui.preferences.projection.ProjectionPreference;
49import org.openstreetmap.josm.tools.Predicate;
50import org.openstreetmap.josm.tools.Utils;
51
52/**
53 * An component that can be navigated by a mapmover. Used as map view and for the
54 * zoomer in the download dialog.
55 *
56 * @author imi
57 */
58public class NavigatableComponent extends JComponent implements Helpful {
59
60 /**
61 * Interface to notify listeners of the change of the zoom area.
62 */
63 public interface ZoomChangeListener {
64 void zoomChanged();
65 }
66
67 public static final IntegerProperty PROP_SNAP_DISTANCE = new IntegerProperty("mappaint.node.snap-distance", 10);
68
69 public static final String PROPNAME_CENTER = "center";
70 public static final String PROPNAME_SCALE = "scale";
71
72 /**
73 * the zoom listeners
74 */
75 private static final CopyOnWriteArrayList<ZoomChangeListener> zoomChangeListeners = new CopyOnWriteArrayList<ZoomChangeListener>();
76
77 /**
78 * Removes a zoom change listener
79 *
80 * @param listener the listener. Ignored if null or already absent
81 */
82 public static void removeZoomChangeListener(NavigatableComponent.ZoomChangeListener listener) {
83 zoomChangeListeners.remove(listener);
84 }
85
86 /**
87 * Adds a zoom change listener
88 *
89 * @param listener the listener. Ignored if null or already registered.
90 */
91 public static void addZoomChangeListener(NavigatableComponent.ZoomChangeListener listener) {
92 if (listener != null) {
93 zoomChangeListeners.addIfAbsent(listener);
94 }
95 }
96
97 protected static void fireZoomChanged() {
98 for (ZoomChangeListener l : zoomChangeListeners) {
99 l.zoomChanged();
100 }
101 }
102
103 /**
104 * The scale factor in x or y-units per pixel. This means, if scale = 10,
105 * every physical pixel on screen are 10 x or 10 y units in the
106 * northing/easting space of the projection.
107 */
108 private double scale = Main.getProjection().getDefaultZoomInPPD();
109 /**
110 * Center n/e coordinate of the desired screen center.
111 */
112 protected EastNorth center = calculateDefaultCenter();
113
114 private final Object paintRequestLock = new Object();
115 private Rectangle paintRect = null;
116 private Polygon paintPoly = null;
117
118 public NavigatableComponent() {
119 setLayout(null);
120 }
121
122 protected DataSet getCurrentDataSet() {
123 return Main.main.getCurrentDataSet();
124 }
125
126 private EastNorth calculateDefaultCenter() {
127 Bounds b = Main.getProjection().getWorldBoundsLatLon();
128 double lat = (b.getMax().lat() + b.getMin().lat())/2;
129 double lon = (b.getMax().lon() + b.getMin().lon())/2;
130
131 return Main.getProjection().latlon2eastNorth(new LatLon(lat, lon));
132 }
133
134 /**
135 * Returns the text describing the given distance in the current system of measurement.
136 * @param dist The distance in metres.
137 * @return the text describing the given distance in the current system of measurement.
138 * @since 3406
139 */
140 public static String getDistText(double dist) {
141 return getSystemOfMeasurement().getDistText(dist);
142 }
143
144 /**
145 * Returns the text describing the given area in the current system of measurement.
146 * @param area The distance in square metres.
147 * @return the text describing the given area in the current system of measurement.
148 * @since 5560
149 */
150 public static String getAreaText(double area) {
151 return getSystemOfMeasurement().getAreaText(area);
152 }
153
154 public String getDist100PixelText()
155 {
156 return getDistText(getDist100Pixel());
157 }
158
159 public double getDist100Pixel()
160 {
161 int w = getWidth()/2;
162 int h = getHeight()/2;
163 LatLon ll1 = getLatLon(w-50,h);
164 LatLon ll2 = getLatLon(w+50,h);
165 return ll1.greatCircleDistance(ll2);
166 }
167
168 /**
169 * @return Returns the center point. A copy is returned, so users cannot
170 * change the center by accessing the return value. Use zoomTo instead.
171 */
172 public EastNorth getCenter() {
173 return center;
174 }
175
176 /**
177 * @param x X-Pixelposition to get coordinate from
178 * @param y Y-Pixelposition to get coordinate from
179 *
180 * @return Geographic coordinates from a specific pixel coordination
181 * on the screen.
182 */
183 public EastNorth getEastNorth(int x, int y) {
184 return new EastNorth(
185 center.east() + (x - getWidth()/2.0)*scale,
186 center.north() - (y - getHeight()/2.0)*scale);
187 }
188
189 public ProjectionBounds getProjectionBounds() {
190 return new ProjectionBounds(
191 new EastNorth(
192 center.east() - getWidth()/2.0*scale,
193 center.north() - getHeight()/2.0*scale),
194 new EastNorth(
195 center.east() + getWidth()/2.0*scale,
196 center.north() + getHeight()/2.0*scale));
197 }
198
199 /* FIXME: replace with better method - used by MapSlider */
200 public ProjectionBounds getMaxProjectionBounds() {
201 Bounds b = getProjection().getWorldBoundsLatLon();
202 return new ProjectionBounds(getProjection().latlon2eastNorth(b.getMin()),
203 getProjection().latlon2eastNorth(b.getMax()));
204 }
205
206 /* FIXME: replace with better method - used by Main to reset Bounds when projection changes, don't use otherwise */
207 public Bounds getRealBounds() {
208 return new Bounds(
209 getProjection().eastNorth2latlon(new EastNorth(
210 center.east() - getWidth()/2.0*scale,
211 center.north() - getHeight()/2.0*scale)),
212 getProjection().eastNorth2latlon(new EastNorth(
213 center.east() + getWidth()/2.0*scale,
214 center.north() + getHeight()/2.0*scale)));
215 }
216
217 /**
218 * @param x X-Pixelposition to get coordinate from
219 * @param y Y-Pixelposition to get coordinate from
220 *
221 * @return Geographic unprojected coordinates from a specific pixel coordination
222 * on the screen.
223 */
224 public LatLon getLatLon(int x, int y) {
225 return getProjection().eastNorth2latlon(getEastNorth(x, y));
226 }
227
228 public LatLon getLatLon(double x, double y) {
229 return getLatLon((int)x, (int)y);
230 }
231
232 /**
233 * @param r
234 * @return Minimum bounds that will cover rectangle
235 */
236 public Bounds getLatLonBounds(Rectangle r) {
237 // TODO Maybe this should be (optional) method of Projection implementation
238 EastNorth p1 = getEastNorth(r.x, r.y);
239 EastNorth p2 = getEastNorth(r.x + r.width, r.y + r.height);
240
241 Bounds result = new Bounds(Main.getProjection().eastNorth2latlon(p1));
242
243 double eastMin = Math.min(p1.east(), p2.east());
244 double eastMax = Math.max(p1.east(), p2.east());
245 double northMin = Math.min(p1.north(), p2.north());
246 double northMax = Math.max(p1.north(), p2.north());
247 double deltaEast = (eastMax - eastMin) / 10;
248 double deltaNorth = (northMax - northMin) / 10;
249
250 for (int i=0; i < 10; i++) {
251 result.extend(Main.getProjection().eastNorth2latlon(new EastNorth(eastMin + i * deltaEast, northMin)));
252 result.extend(Main.getProjection().eastNorth2latlon(new EastNorth(eastMin + i * deltaEast, northMax)));
253 result.extend(Main.getProjection().eastNorth2latlon(new EastNorth(eastMin, northMin + i * deltaNorth)));
254 result.extend(Main.getProjection().eastNorth2latlon(new EastNorth(eastMax, northMin + i * deltaNorth)));
255 }
256
257 return result;
258 }
259
260 public AffineTransform getAffineTransform() {
261 return new AffineTransform(
262 1.0/scale, 0.0, 0.0, -1.0/scale, getWidth()/2.0 - center.east()/scale, getHeight()/2.0 + center.north()/scale);
263 }
264
265 /**
266 * Return the point on the screen where this Coordinate would be.
267 * @param p The point, where this geopoint would be drawn.
268 * @return The point on screen where "point" would be drawn, relative
269 * to the own top/left.
270 */
271 public Point2D getPoint2D(EastNorth p) {
272 if (null == p)
273 return new Point();
274 double x = (p.east()-center.east())/scale + getWidth()/2;
275 double y = (center.north()-p.north())/scale + getHeight()/2;
276 return new Point2D.Double(x, y);
277 }
278
279 public Point2D getPoint2D(LatLon latlon) {
280 if (latlon == null)
281 return new Point();
282 else if (latlon instanceof CachedLatLon)
283 return getPoint2D(((CachedLatLon)latlon).getEastNorth());
284 else
285 return getPoint2D(getProjection().latlon2eastNorth(latlon));
286 }
287
288 public Point2D getPoint2D(Node n) {
289 return getPoint2D(n.getEastNorth());
290 }
291
292 // looses precision, may overflow (depends on p and current scale)
293 //@Deprecated
294 public Point getPoint(EastNorth p) {
295 Point2D d = getPoint2D(p);
296 return new Point((int) d.getX(), (int) d.getY());
297 }
298
299 // looses precision, may overflow (depends on p and current scale)
300 //@Deprecated
301 public Point getPoint(LatLon latlon) {
302 Point2D d = getPoint2D(latlon);
303 return new Point((int) d.getX(), (int) d.getY());
304 }
305
306 // looses precision, may overflow (depends on p and current scale)
307 //@Deprecated
308 public Point getPoint(Node n) {
309 Point2D d = getPoint2D(n);
310 return new Point((int) d.getX(), (int) d.getY());
311 }
312
313 /**
314 * Zoom to the given coordinate.
315 * @param newCenter The center x-value (easting) to zoom to.
316 * @param scale The scale to use.
317 */
318 public void zoomTo(EastNorth newCenter, double newScale) {
319 Bounds b = getProjection().getWorldBoundsLatLon();
320 LatLon cl = Projections.inverseProject(newCenter);
321 boolean changed = false;
322 double lat = cl.lat();
323 double lon = cl.lon();
324 if(lat < b.getMin().lat()) {changed = true; lat = b.getMin().lat(); }
325 else if(lat > b.getMax().lat()) {changed = true; lat = b.getMax().lat(); }
326 if(lon < b.getMin().lon()) {changed = true; lon = b.getMin().lon(); }
327 else if(lon > b.getMax().lon()) {changed = true; lon = b.getMax().lon(); }
328 if(changed) {
329 newCenter = Projections.project(new LatLon(lat,lon));
330 }
331 int width = getWidth()/2;
332 int height = getHeight()/2;
333 LatLon l1 = new LatLon(b.getMin().lat(), lon);
334 LatLon l2 = new LatLon(b.getMax().lat(), lon);
335 EastNorth e1 = getProjection().latlon2eastNorth(l1);
336 EastNorth e2 = getProjection().latlon2eastNorth(l2);
337 double d = e2.north() - e1.north();
338 if(d < height*newScale)
339 {
340 double newScaleH = d/height;
341 e1 = getProjection().latlon2eastNorth(new LatLon(lat, b.getMin().lon()));
342 e2 = getProjection().latlon2eastNorth(new LatLon(lat, b.getMax().lon()));
343 d = e2.east() - e1.east();
344 if(d < width*newScale) {
345 newScale = Math.max(newScaleH, d/width);
346 }
347 }
348 else
349 {
350 d = d/(l1.greatCircleDistance(l2)*height*10);
351 if(newScale < d) {
352 newScale = d;
353 }
354 }
355
356 if (!newCenter.equals(center) || (scale != newScale)) {
357 pushZoomUndo(center, scale);
358 zoomNoUndoTo(newCenter, newScale);
359 }
360 }
361
362 /**
363 * Zoom to the given coordinate without adding to the zoom undo buffer.
364 * @param newCenter The center x-value (easting) to zoom to.
365 * @param scale The scale to use.
366 */
367 private void zoomNoUndoTo(EastNorth newCenter, double newScale) {
368 if (!newCenter.equals(center)) {
369 EastNorth oldCenter = center;
370 center = newCenter;
371 firePropertyChange(PROPNAME_CENTER, oldCenter, newCenter);
372 }
373 if (scale != newScale) {
374 double oldScale = scale;
375 scale = newScale;
376 firePropertyChange(PROPNAME_SCALE, oldScale, newScale);
377 }
378
379 repaint();
380 fireZoomChanged();
381 }
382
383 public void zoomTo(EastNorth newCenter) {
384 zoomTo(newCenter, scale);
385 }
386
387 public void zoomTo(LatLon newCenter) {
388 zoomTo(Projections.project(newCenter));
389 }
390
391 public void smoothScrollTo(LatLon newCenter) {
392 smoothScrollTo(Projections.project(newCenter));
393 }
394
395 /**
396 * Create a thread that moves the viewport to the given center in an
397 * animated fashion.
398 */
399 public void smoothScrollTo(EastNorth newCenter) {
400 // fixme make these configurable.
401 final int fps = 20; // animation frames per second
402 final int speed = 1500; // milliseconds for full-screen-width pan
403 if (!newCenter.equals(center)) {
404 final EastNorth oldCenter = center;
405 final double distance = newCenter.distance(oldCenter) / scale;
406 final double milliseconds = distance / getWidth() * speed;
407 final double frames = milliseconds * fps / 1000;
408 final EastNorth finalNewCenter = newCenter;
409
410 new Thread(){
411 @Override
412 public void run() {
413 for (int i=0; i<frames; i++)
414 {
415 // fixme - not use zoom history here
416 zoomTo(oldCenter.interpolate(finalNewCenter, (i+1) / frames));
417 try { Thread.sleep(1000 / fps); } catch (InterruptedException ex) { };
418 }
419 }
420 }.start();
421 }
422 }
423
424 public void zoomToFactor(double x, double y, double factor) {
425 double newScale = scale*factor;
426 // New center position so that point under the mouse pointer stays the same place as it was before zooming
427 // You will get the formula by simplifying this expression: newCenter = oldCenter + mouseCoordinatesInNewZoom - mouseCoordinatesInOldZoom
428 zoomTo(new EastNorth(
429 center.east() - (x - getWidth()/2.0) * (newScale - scale),
430 center.north() + (y - getHeight()/2.0) * (newScale - scale)),
431 newScale);
432 }
433
434 public void zoomToFactor(EastNorth newCenter, double factor) {
435 zoomTo(newCenter, scale*factor);
436 }
437
438 public void zoomToFactor(double factor) {
439 zoomTo(center, scale*factor);
440 }
441
442 public void zoomTo(ProjectionBounds box) {
443 // -20 to leave some border
444 int w = getWidth()-20;
445 if (w < 20) {
446 w = 20;
447 }
448 int h = getHeight()-20;
449 if (h < 20) {
450 h = 20;
451 }
452
453 double scaleX = (box.maxEast-box.minEast)/w;
454 double scaleY = (box.maxNorth-box.minNorth)/h;
455 double newScale = Math.max(scaleX, scaleY);
456
457 zoomTo(box.getCenter(), newScale);
458 }
459
460 public void zoomTo(Bounds box) {
461 zoomTo(new ProjectionBounds(getProjection().latlon2eastNorth(box.getMin()),
462 getProjection().latlon2eastNorth(box.getMax())));
463 }
464
465 private class ZoomData {
466 LatLon center;
467 double scale;
468
469 public ZoomData(EastNorth center, double scale) {
470 this.center = Projections.inverseProject(center);
471 this.scale = scale;
472 }
473
474 public EastNorth getCenterEastNorth() {
475 return getProjection().latlon2eastNorth(center);
476 }
477
478 public double getScale() {
479 return scale;
480 }
481 }
482
483 private Stack<ZoomData> zoomUndoBuffer = new Stack<ZoomData>();
484 private Stack<ZoomData> zoomRedoBuffer = new Stack<ZoomData>();
485 private Date zoomTimestamp = new Date();
486
487 private void pushZoomUndo(EastNorth center, double scale) {
488 Date now = new Date();
489 if ((now.getTime() - zoomTimestamp.getTime()) > (Main.pref.getDouble("zoom.undo.delay", 1.0) * 1000)) {
490 zoomUndoBuffer.push(new ZoomData(center, scale));
491 if (zoomUndoBuffer.size() > Main.pref.getInteger("zoom.undo.max", 50)) {
492 zoomUndoBuffer.remove(0);
493 }
494 zoomRedoBuffer.clear();
495 }
496 zoomTimestamp = now;
497 }
498
499 public void zoomPrevious() {
500 if (!zoomUndoBuffer.isEmpty()) {
501 ZoomData zoom = zoomUndoBuffer.pop();
502 zoomRedoBuffer.push(new ZoomData(center, scale));
503 zoomNoUndoTo(zoom.getCenterEastNorth(), zoom.getScale());
504 }
505 }
506
507 public void zoomNext() {
508 if (!zoomRedoBuffer.isEmpty()) {
509 ZoomData zoom = zoomRedoBuffer.pop();
510 zoomUndoBuffer.push(new ZoomData(center, scale));
511 zoomNoUndoTo(zoom.getCenterEastNorth(), zoom.getScale());
512 }
513 }
514
515 public boolean hasZoomUndoEntries() {
516 return !zoomUndoBuffer.isEmpty();
517 }
518
519 public boolean hasZoomRedoEntries() {
520 return !zoomRedoBuffer.isEmpty();
521 }
522
523 private BBox getBBox(Point p, int snapDistance) {
524 return new BBox(getLatLon(p.x - snapDistance, p.y - snapDistance),
525 getLatLon(p.x + snapDistance, p.y + snapDistance));
526 }
527
528 /**
529 * The *result* does not depend on the current map selection state,
530 * neither does the result *order*.
531 * It solely depends on the distance to point p.
532 *
533 * @return a sorted map with the keys representing the distance of
534 * their associated nodes to point p.
535 */
536 private Map<Double, List<Node>> getNearestNodesImpl(Point p,
537 Predicate<OsmPrimitive> predicate) {
538 TreeMap<Double, List<Node>> nearestMap = new TreeMap<Double, List<Node>>();
539 DataSet ds = getCurrentDataSet();
540
541 if (ds != null) {
542 double dist, snapDistanceSq = PROP_SNAP_DISTANCE.get();
543 snapDistanceSq *= snapDistanceSq;
544
545 for (Node n : ds.searchNodes(getBBox(p, PROP_SNAP_DISTANCE.get()))) {
546 if (predicate.evaluate(n)
547 && (dist = getPoint2D(n).distanceSq(p)) < snapDistanceSq)
548 {
549 List<Node> nlist;
550 if (nearestMap.containsKey(dist)) {
551 nlist = nearestMap.get(dist);
552 } else {
553 nlist = new LinkedList<Node>();
554 nearestMap.put(dist, nlist);
555 }
556 nlist.add(n);
557 }
558 }
559 }
560
561 return nearestMap;
562 }
563
564 /**
565 * The *result* does not depend on the current map selection state,
566 * neither does the result *order*.
567 * It solely depends on the distance to point p.
568 *
569 * @return All nodes nearest to point p that are in a belt from
570 * dist(nearest) to dist(nearest)+4px around p and
571 * that are not in ignore.
572 *
573 * @param p the point for which to search the nearest segment.
574 * @param ignore a collection of nodes which are not to be returned.
575 * @param predicate the returned objects have to fulfill certain properties.
576 */
577 public final List<Node> getNearestNodes(Point p,
578 Collection<Node> ignore, Predicate<OsmPrimitive> predicate) {
579 List<Node> nearestList = Collections.emptyList();
580
581 if (ignore == null) {
582 ignore = Collections.emptySet();
583 }
584
585 Map<Double, List<Node>> nlists = getNearestNodesImpl(p, predicate);
586 if (!nlists.isEmpty()) {
587 Double minDistSq = null;
588 List<Node> nlist;
589 for (Double distSq : nlists.keySet()) {
590 nlist = nlists.get(distSq);
591
592 // filter nodes to be ignored before determining minDistSq..
593 nlist.removeAll(ignore);
594 if (minDistSq == null) {
595 if (!nlist.isEmpty()) {
596 minDistSq = distSq;
597 nearestList = new ArrayList<Node>();
598 nearestList.addAll(nlist);
599 }
600 } else {
601 if (distSq-minDistSq < (4)*(4)) {
602 nearestList.addAll(nlist);
603 }
604 }
605 }
606 }
607
608 return nearestList;
609 }
610
611 /**
612 * The *result* does not depend on the current map selection state,
613 * neither does the result *order*.
614 * It solely depends on the distance to point p.
615 *
616 * @return All nodes nearest to point p that are in a belt from
617 * dist(nearest) to dist(nearest)+4px around p.
618 * @see #getNearestNodes(Point, Collection, Predicate)
619 *
620 * @param p the point for which to search the nearest segment.
621 * @param predicate the returned objects have to fulfill certain properties.
622 */
623 public final List<Node> getNearestNodes(Point p, Predicate<OsmPrimitive> predicate) {
624 return getNearestNodes(p, null, predicate);
625 }
626
627 /**
628 * The *result* depends on the current map selection state IF use_selected is true.
629 *
630 * If more than one node within node.snap-distance pixels is found,
631 * the nearest node selected is returned IF use_selected is true.
632 *
633 * Else the nearest new/id=0 node within about the same distance
634 * as the true nearest node is returned.
635 *
636 * If no such node is found either, the true nearest
637 * node to p is returned.
638 *
639 * Finally, if a node is not found at all, null is returned.
640 *
641 * @return A node within snap-distance to point p,
642 * that is chosen by the algorithm described.
643 *
644 * @param p the screen point
645 * @param predicate this parameter imposes a condition on the returned object, e.g.
646 * give the nearest node that is tagged.
647 */
648 public final Node getNearestNode(Point p, Predicate<OsmPrimitive> predicate, boolean use_selected) {
649 Node n = null;
650
651 Map<Double, List<Node>> nlists = getNearestNodesImpl(p, predicate);
652 if (!nlists.isEmpty()) {
653 Node ntsel = null, ntnew = null;
654 double minDistSq = nlists.keySet().iterator().next();
655
656 for (Double distSq : nlists.keySet()) {
657 for (Node nd : nlists.get(distSq)) {
658 // find the nearest selected node
659 if (ntsel == null && nd.isSelected()) {
660 ntsel = nd;
661 // if there are multiple nearest nodes, prefer the one
662 // that is selected. This is required in order to drag
663 // the selected node if multiple nodes have the same
664 // coordinates (e.g. after unglue)
665 use_selected |= (distSq == minDistSq);
666 }
667 // find the nearest newest node that is within about the same
668 // distance as the true nearest node
669 if (ntnew == null && nd.isNew() && (distSq-minDistSq < 1)) {
670 ntnew = nd;
671 }
672 }
673 }
674
675 // take nearest selected, nearest new or true nearest node to p, in that order
676 n = (ntsel != null && use_selected) ? ntsel
677 : (ntnew != null) ? ntnew
678 : nlists.values().iterator().next().get(0);
679 }
680 return n;
681 }
682
683 /**
684 * Convenience method to {@link #getNearestNode(Point, Predicate, boolean)}.
685 *
686 * @return The nearest node to point p.
687 */
688 public final Node getNearestNode(Point p, Predicate<OsmPrimitive> predicate) {
689 return getNearestNode(p, predicate, true);
690 }
691
692 /**
693 * The *result* does not depend on the current map selection state,
694 * neither does the result *order*.
695 * It solely depends on the distance to point p.
696 *
697 * @return a sorted map with the keys representing the perpendicular
698 * distance of their associated way segments to point p.
699 */
700 private Map<Double, List<WaySegment>> getNearestWaySegmentsImpl(Point p,
701 Predicate<OsmPrimitive> predicate) {
702 Map<Double, List<WaySegment>> nearestMap = new TreeMap<Double, List<WaySegment>>();
703 DataSet ds = getCurrentDataSet();
704
705 if (ds != null) {
706 double snapDistanceSq = Main.pref.getInteger("mappaint.segment.snap-distance", 10);
707 snapDistanceSq *= snapDistanceSq;
708
709 for (Way w : ds.searchWays(getBBox(p, Main.pref.getInteger("mappaint.segment.snap-distance", 10)))) {
710 if (!predicate.evaluate(w)) {
711 continue;
712 }
713 Node lastN = null;
714 int i = -2;
715 for (Node n : w.getNodes()) {
716 i++;
717 if (n.isDeleted() || n.isIncomplete()) { //FIXME: This shouldn't happen, raise exception?
718 continue;
719 }
720 if (lastN == null) {
721 lastN = n;
722 continue;
723 }
724
725 Point2D A = getPoint2D(lastN);
726 Point2D B = getPoint2D(n);
727 double c = A.distanceSq(B);
728 double a = p.distanceSq(B);
729 double b = p.distanceSq(A);
730
731 /* perpendicular distance squared
732 * loose some precision to account for possible deviations in the calculation above
733 * e.g. if identical (A and B) come about reversed in another way, values may differ
734 * -- zero out least significant 32 dual digits of mantissa..
735 */
736 double perDistSq = Double.longBitsToDouble(
737 Double.doubleToLongBits( a - (a - b + c) * (a - b + c) / 4 / c )
738 >> 32 << 32); // resolution in numbers with large exponent not needed here..
739
740 if (perDistSq < snapDistanceSq && a < c + snapDistanceSq && b < c + snapDistanceSq) {
741 //System.err.println(Double.toHexString(perDistSq));
742
743 List<WaySegment> wslist;
744 if (nearestMap.containsKey(perDistSq)) {
745 wslist = nearestMap.get(perDistSq);
746 } else {
747 wslist = new LinkedList<WaySegment>();
748 nearestMap.put(perDistSq, wslist);
749 }
750 wslist.add(new WaySegment(w, i));
751 }
752
753 lastN = n;
754 }
755 }
756 }
757
758 return nearestMap;
759 }
760
761 /**
762 * The result *order* depends on the current map selection state.
763 * Segments within 10px of p are searched and sorted by their distance to @param p,
764 * then, within groups of equally distant segments, prefer those that are selected.
765 *
766 * @return all segments within 10px of p that are not in ignore,
767 * sorted by their perpendicular distance.
768 *
769 * @param p the point for which to search the nearest segments.
770 * @param ignore a collection of segments which are not to be returned.
771 * @param predicate the returned objects have to fulfill certain properties.
772 */
773 public final List<WaySegment> getNearestWaySegments(Point p,
774 Collection<WaySegment> ignore, Predicate<OsmPrimitive> predicate) {
775 List<WaySegment> nearestList = new ArrayList<WaySegment>();
776 List<WaySegment> unselected = new LinkedList<WaySegment>();
777
778 for (List<WaySegment> wss : getNearestWaySegmentsImpl(p, predicate).values()) {
779 // put selected waysegs within each distance group first
780 // makes the order of nearestList dependent on current selection state
781 for (WaySegment ws : wss) {
782 (ws.way.isSelected() ? nearestList : unselected).add(ws);
783 }
784 nearestList.addAll(unselected);
785 unselected.clear();
786 }
787 if (ignore != null) {
788 nearestList.removeAll(ignore);
789 }
790
791 return nearestList;
792 }
793
794 /**
795 * The result *order* depends on the current map selection state.
796 *
797 * @return all segments within 10px of p, sorted by their perpendicular distance.
798 * @see #getNearestWaySegments(Point, Collection, Predicate)
799 *
800 * @param p the point for which to search the nearest segments.
801 * @param predicate the returned objects have to fulfill certain properties.
802 */
803 public final List<WaySegment> getNearestWaySegments(Point p, Predicate<OsmPrimitive> predicate) {
804 return getNearestWaySegments(p, null, predicate);
805 }
806
807 /**
808 * The *result* depends on the current map selection state IF use_selected is true.
809 *
810 * @return The nearest way segment to point p,
811 * and, depending on use_selected, prefers a selected way segment, if found.
812 * @see #getNearestWaySegments(Point, Collection, Predicate)
813 *
814 * @param p the point for which to search the nearest segment.
815 * @param predicate the returned object has to fulfill certain properties.
816 * @param use_selected whether selected way segments should be preferred.
817 */
818 public final WaySegment getNearestWaySegment(Point p, Predicate<OsmPrimitive> predicate, boolean use_selected) {
819 WaySegment wayseg = null, ntsel = null;
820
821 for (List<WaySegment> wslist : getNearestWaySegmentsImpl(p, predicate).values()) {
822 if (wayseg != null && ntsel != null) {
823 break;
824 }
825 for (WaySegment ws : wslist) {
826 if (wayseg == null) {
827 wayseg = ws;
828 }
829 if (ntsel == null && ws.way.isSelected()) {
830 ntsel = ws;
831 }
832 }
833 }
834
835 return (ntsel != null && use_selected) ? ntsel : wayseg;
836 }
837
838 /**
839 * Convenience method to {@link #getNearestWaySegment(Point, Predicate, boolean)}.
840 *
841 * @return The nearest way segment to point p.
842 */
843 public final WaySegment getNearestWaySegment(Point p, Predicate<OsmPrimitive> predicate) {
844 return getNearestWaySegment(p, predicate, true);
845 }
846
847 /**
848 * The *result* does not depend on the current map selection state,
849 * neither does the result *order*.
850 * It solely depends on the perpendicular distance to point p.
851 *
852 * @return all nearest ways to the screen point given that are not in ignore.
853 * @see #getNearestWaySegments(Point, Collection, Predicate)
854 *
855 * @param p the point for which to search the nearest ways.
856 * @param ignore a collection of ways which are not to be returned.
857 * @param predicate the returned object has to fulfill certain properties.
858 */
859 public final List<Way> getNearestWays(Point p,
860 Collection<Way> ignore, Predicate<OsmPrimitive> predicate) {
861 List<Way> nearestList = new ArrayList<Way>();
862 Set<Way> wset = new HashSet<Way>();
863
864 for (List<WaySegment> wss : getNearestWaySegmentsImpl(p, predicate).values()) {
865 for (WaySegment ws : wss) {
866 if (wset.add(ws.way)) {
867 nearestList.add(ws.way);
868 }
869 }
870 }
871 if (ignore != null) {
872 nearestList.removeAll(ignore);
873 }
874
875 return nearestList;
876 }
877
878 /**
879 * The *result* does not depend on the current map selection state,
880 * neither does the result *order*.
881 * It solely depends on the perpendicular distance to point p.
882 *
883 * @return all nearest ways to the screen point given.
884 * @see #getNearestWays(Point, Collection, Predicate)
885 *
886 * @param p the point for which to search the nearest ways.
887 * @param predicate the returned object has to fulfill certain properties.
888 */
889 public final List<Way> getNearestWays(Point p, Predicate<OsmPrimitive> predicate) {
890 return getNearestWays(p, null, predicate);
891 }
892
893 /**
894 * The *result* depends on the current map selection state.
895 *
896 * @return The nearest way to point p,
897 * prefer a selected way if there are multiple nearest.
898 * @see #getNearestWaySegment(Point, Collection, Predicate)
899 *
900 * @param p the point for which to search the nearest segment.
901 * @param predicate the returned object has to fulfill certain properties.
902 */
903 public final Way getNearestWay(Point p, Predicate<OsmPrimitive> predicate) {
904 WaySegment nearestWaySeg = getNearestWaySegment(p, predicate);
905 return (nearestWaySeg == null) ? null : nearestWaySeg.way;
906 }
907
908 /**
909 * The *result* does not depend on the current map selection state,
910 * neither does the result *order*.
911 * It solely depends on the distance to point p.
912 *
913 * First, nodes will be searched. If there are nodes within BBox found,
914 * return a collection of those nodes only.
915 *
916 * If no nodes are found, search for nearest ways. If there are ways
917 * within BBox found, return a collection of those ways only.
918 *
919 * If nothing is found, return an empty collection.
920 *
921 * @return Primitives nearest to the given screen point that are not in ignore.
922 * @see #getNearestNodes(Point, Collection, Predicate)
923 * @see #getNearestWays(Point, Collection, Predicate)
924 *
925 * @param p The point on screen.
926 * @param ignore a collection of ways which are not to be returned.
927 * @param predicate the returned object has to fulfill certain properties.
928 */
929 public final List<OsmPrimitive> getNearestNodesOrWays(Point p,
930 Collection<OsmPrimitive> ignore, Predicate<OsmPrimitive> predicate) {
931 List<OsmPrimitive> nearestList = Collections.emptyList();
932 OsmPrimitive osm = getNearestNodeOrWay(p, predicate, false);
933
934 if (osm != null) {
935 if (osm instanceof Node) {
936 nearestList = new ArrayList<OsmPrimitive>(getNearestNodes(p, predicate));
937 } else if (osm instanceof Way) {
938 nearestList = new ArrayList<OsmPrimitive>(getNearestWays(p, predicate));
939 }
940 if (ignore != null) {
941 nearestList.removeAll(ignore);
942 }
943 }
944
945 return nearestList;
946 }
947
948 /**
949 * The *result* does not depend on the current map selection state,
950 * neither does the result *order*.
951 * It solely depends on the distance to point p.
952 *
953 * @return Primitives nearest to the given screen point.
954 * @see #getNearests(Point, Collection, Predicate)
955 *
956 * @param p The point on screen.
957 * @param predicate the returned object has to fulfill certain properties.
958 */
959 public final List<OsmPrimitive> getNearestNodesOrWays(Point p, Predicate<OsmPrimitive> predicate) {
960 return getNearestNodesOrWays(p, null, predicate);
961 }
962
963 /**
964 * This is used as a helper routine to {@link #getNearestNodeOrWay(Point, Predicate, boolean)}
965 * It decides, whether to yield the node to be tested or look for further (way) candidates.
966 *
967 * @return true, if the node fulfills the properties of the function body
968 *
969 * @param osm node to check
970 * @param p point clicked
971 * @param use_selected whether to prefer selected nodes
972 */
973 private boolean isPrecedenceNode(Node osm, Point p, boolean use_selected) {
974 boolean ret = false;
975
976 if (osm != null) {
977 ret |= !(p.distanceSq(getPoint2D(osm)) > (4)*(4));
978 ret |= osm.isTagged();
979 if (use_selected) {
980 ret |= osm.isSelected();
981 }
982 }
983
984 return ret;
985 }
986
987 /**
988 * The *result* depends on the current map selection state IF use_selected is true.
989 *
990 * IF use_selected is true, use {@link #getNearestNode(Point, Predicate)} to find
991 * the nearest, selected node. If not found, try {@link #getNearestWaySegment(Point, Predicate)}
992 * to find the nearest selected way.
993 *
994 * IF use_selected is false, or if no selected primitive was found, do the following.
995 *
996 * If the nearest node found is within 4px of p, simply take it.
997 * Else, find the nearest way segment. Then, if p is closer to its
998 * middle than to the node, take the way segment, else take the node.
999 *
1000 * Finally, if no nearest primitive is found at all, return null.
1001 *
1002 * @return A primitive within snap-distance to point p,
1003 * that is chosen by the algorithm described.
1004 * @see getNearestNode(Point, Predicate)
1005 * @see getNearestNodesImpl(Point, Predicate)
1006 * @see getNearestWay(Point, Predicate)
1007 *
1008 * @param p The point on screen.
1009 * @param predicate the returned object has to fulfill certain properties.
1010 * @param use_selected whether to prefer primitives that are currently selected.
1011 */
1012 public final OsmPrimitive getNearestNodeOrWay(Point p, Predicate<OsmPrimitive> predicate, boolean use_selected) {
1013 OsmPrimitive osm = getNearestNode(p, predicate, use_selected);
1014 WaySegment ws = null;
1015
1016 if (!isPrecedenceNode((Node)osm, p, use_selected)) {
1017 ws = getNearestWaySegment(p, predicate, use_selected);
1018
1019 if (ws != null) {
1020 if ((ws.way.isSelected() && use_selected) || osm == null) {
1021 // either (no _selected_ nearest node found, if desired) or no nearest node was found
1022 osm = ws.way;
1023 } else {
1024 int maxWaySegLenSq = 3*PROP_SNAP_DISTANCE.get();
1025 maxWaySegLenSq *= maxWaySegLenSq;
1026
1027 Point2D wp1 = getPoint2D(ws.way.getNode(ws.lowerIndex));
1028 Point2D wp2 = getPoint2D(ws.way.getNode(ws.lowerIndex+1));
1029
1030 // is wayseg shorter than maxWaySegLenSq and
1031 // is p closer to the middle of wayseg than to the nearest node?
1032 if (wp1.distanceSq(wp2) < maxWaySegLenSq &&
1033 p.distanceSq(project(0.5, wp1, wp2)) < p.distanceSq(getPoint2D((Node)osm))) {
1034 osm = ws.way;
1035 }
1036 }
1037 }
1038 }
1039
1040 return osm;
1041 }
1042
1043 /**
1044 * @return o as collection of o's type.
1045 */
1046 public static <T> Collection<T> asColl(T o) {
1047 if (o == null)
1048 return Collections.emptySet();
1049 return Collections.singleton(o);
1050 }
1051
1052 public static double perDist(Point2D pt, Point2D a, Point2D b) {
1053 if (pt != null && a != null && b != null) {
1054 double pd = (
1055 (a.getX()-pt.getX())*(b.getX()-a.getX()) -
1056 (a.getY()-pt.getY())*(b.getY()-a.getY()) );
1057 return Math.abs(pd) / a.distance(b);
1058 }
1059 return 0d;
1060 }
1061
1062 /**
1063 *
1064 * @param pt point to project onto (ab)
1065 * @param a root of vector
1066 * @param b vector
1067 * @return point of intersection of line given by (ab)
1068 * with its orthogonal line running through pt
1069 */
1070 public static Point2D project(Point2D pt, Point2D a, Point2D b) {
1071 if (pt != null && a != null && b != null) {
1072 double r = ((
1073 (pt.getX()-a.getX())*(b.getX()-a.getX()) +
1074 (pt.getY()-a.getY())*(b.getY()-a.getY()) )
1075 / a.distanceSq(b));
1076 return project(r, a, b);
1077 }
1078 return null;
1079 }
1080
1081 /**
1082 * if r = 0 returns a, if r=1 returns b,
1083 * if r = 0.5 returns center between a and b, etc..
1084 *
1085 * @param r scale value
1086 * @param a root of vector
1087 * @param b vector
1088 * @return new point at a + r*(ab)
1089 */
1090 public static Point2D project(double r, Point2D a, Point2D b) {
1091 Point2D ret = null;
1092
1093 if (a != null && b != null) {
1094 ret = new Point2D.Double(a.getX() + r*(b.getX()-a.getX()),
1095 a.getY() + r*(b.getY()-a.getY()));
1096 }
1097 return ret;
1098 }
1099
1100 /**
1101 * The *result* does not depend on the current map selection state,
1102 * neither does the result *order*.
1103 * It solely depends on the distance to point p.
1104 *
1105 * @return a list of all objects that are nearest to point p and
1106 * not in ignore or an empty list if nothing was found.
1107 *
1108 * @param p The point on screen.
1109 * @param ignore a collection of ways which are not to be returned.
1110 * @param predicate the returned object has to fulfill certain properties.
1111 */
1112 public final List<OsmPrimitive> getAllNearest(Point p,
1113 Collection<OsmPrimitive> ignore, Predicate<OsmPrimitive> predicate) {
1114 List<OsmPrimitive> nearestList = new ArrayList<OsmPrimitive>();
1115 Set<Way> wset = new HashSet<Way>();
1116
1117 // add nearby ways
1118 for (List<WaySegment> wss : getNearestWaySegmentsImpl(p, predicate).values()) {
1119 for (WaySegment ws : wss) {
1120 if (wset.add(ws.way)) {
1121 nearestList.add(ws.way);
1122 }
1123 }
1124 }
1125
1126 // add nearby nodes
1127 for (List<Node> nlist : getNearestNodesImpl(p, predicate).values()) {
1128 nearestList.addAll(nlist);
1129 }
1130
1131 // add parent relations of nearby nodes and ways
1132 Set<OsmPrimitive> parentRelations = new HashSet<OsmPrimitive>();
1133 for (OsmPrimitive o : nearestList) {
1134 for (OsmPrimitive r : o.getReferrers()) {
1135 if (r instanceof Relation && predicate.evaluate(r)) {
1136 parentRelations.add(r);
1137 }
1138 }
1139 }
1140 nearestList.addAll(parentRelations);
1141
1142 if (ignore != null) {
1143 nearestList.removeAll(ignore);
1144 }
1145
1146 return nearestList;
1147 }
1148
1149 /**
1150 * The *result* does not depend on the current map selection state,
1151 * neither does the result *order*.
1152 * It solely depends on the distance to point p.
1153 *
1154 * @return a list of all objects that are nearest to point p
1155 * or an empty list if nothing was found.
1156 * @see #getAllNearest(Point, Collection, Predicate)
1157 *
1158 * @param p The point on screen.
1159 * @param predicate the returned object has to fulfill certain properties.
1160 */
1161 public final List<OsmPrimitive> getAllNearest(Point p, Predicate<OsmPrimitive> predicate) {
1162 return getAllNearest(p, null, predicate);
1163 }
1164
1165 /**
1166 * @return The projection to be used in calculating stuff.
1167 */
1168 public Projection getProjection() {
1169 return Main.getProjection();
1170 }
1171
1172 public String helpTopic() {
1173 String n = getClass().getName();
1174 return n.substring(n.lastIndexOf('.')+1);
1175 }
1176
1177 /**
1178 * Return a ID which is unique as long as viewport dimensions are the same
1179 */
1180 public int getViewID() {
1181 String x = center.east() + "_" + center.north() + "_" + scale + "_" +
1182 getWidth() + "_" + getHeight() + "_" + getProjection().toString();
1183 java.util.zip.CRC32 id = new java.util.zip.CRC32();
1184 id.update(x.getBytes());
1185 return (int)id.getValue();
1186 }
1187
1188 /**
1189 * Returns the current system of measurement.
1190 * @return The current system of measurement (metric system by default).
1191 * @since 3490
1192 */
1193 public static SystemOfMeasurement getSystemOfMeasurement() {
1194 SystemOfMeasurement som = SYSTEMS_OF_MEASUREMENT.get(ProjectionPreference.PROP_SYSTEM_OF_MEASUREMENT.get());
1195 if (som == null)
1196 return METRIC_SOM;
1197 return som;
1198 }
1199
1200 /**
1201 * A system of units used to express length and area measurements.
1202 * @since 3406
1203 */
1204 public static class SystemOfMeasurement {
1205 public final double aValue;
1206 public final double bValue;
1207 public final String aName;
1208 public final String bName;
1209
1210 /**
1211 * System of measurement. Currently covers only length (and area) units.
1212 *
1213 * If a quantity x is given in m (x_m) and in unit a (x_a) then it translates as
1214 * x_a == x_m / aValue
1215 */
1216 public SystemOfMeasurement(double aValue, String aName, double bValue, String bName) {
1217 this.aValue = aValue;
1218 this.aName = aName;
1219 this.bValue = bValue;
1220 this.bName = bName;
1221 }
1222
1223 /**
1224 * Returns the text describing the given distance in this system of measurement.
1225 * @param dist The distance in metres
1226 * @return The text describing the given distance in this system of measurement.
1227 */
1228 public String getDistText(double dist) {
1229 double a = dist / aValue;
1230 if (!Main.pref.getBoolean("system_of_measurement.use_only_lower_unit", false) && a > bValue / aValue) {
1231 double b = dist / bValue;
1232 return String.format(Locale.US, "%." + (b<10 ? 2 : 1) + "f %s", b, bName);
1233 } else if (a < 0.01)
1234 return "< 0.01 " + aName;
1235 else
1236 return String.format(Locale.US, "%." + (a<10 ? 2 : 1) + "f %s", a, aName);
1237 }
1238
1239 /**
1240 * Returns the text describing the given area in this system of measurement.
1241 * @param area The area in square metres
1242 * @return The text describing the given area in this system of measurement.
1243 * @since 5560
1244 */
1245 public String getAreaText(double area) {
1246 double a = area / (aValue*aValue);
1247 if (!Main.pref.getBoolean("system_of_measurement.use_only_lower_unit", false) && a > bValue / aValue) {
1248 double b = area / (bValue*bValue);
1249 return String.format(Locale.US, "%." + (b<10 ? 2 : 1) + "f %s", b, bName+"\u00b2");
1250 } else if (a < 0.01)
1251 return "< 0.01 " + aName;
1252 else
1253 return String.format(Locale.US, "%." + (a<10 ? 2 : 1) + "f %s", a, aName+"\u00b2");
1254 }
1255 }
1256
1257 /**
1258 * Metric system (international standard).
1259 * @since 3406
1260 */
1261 public static final SystemOfMeasurement METRIC_SOM = new SystemOfMeasurement(1, "m", 1000, "km");
1262
1263 /**
1264 * Chinese system.
1265 * @since 3406
1266 */
1267 public static final SystemOfMeasurement CHINESE_SOM = new SystemOfMeasurement(1.0/3.0, "\u5e02\u5c3a" /* chi */, 500, "\u5e02\u91cc" /* li */);
1268
1269 /**
1270 * Imperial system (British Commonwealth and former British Empire).
1271 * @since 3406
1272 */
1273 public static final SystemOfMeasurement IMPERIAL_SOM = new SystemOfMeasurement(0.3048, "ft", 1609.344, "mi");
1274
1275 /**
1276 * Nautical mile system (navigation, polar exploration).
1277 * @since 5549
1278 */
1279 public static final SystemOfMeasurement NAUTICAL_MILE_SOM = new SystemOfMeasurement(185.2, "kbl", 1852, "NM");
1280
1281 /**
1282 * Known systems of measurement.
1283 * @since 3406
1284 */
1285 public static final Map<String, SystemOfMeasurement> SYSTEMS_OF_MEASUREMENT;
1286 static {
1287 SYSTEMS_OF_MEASUREMENT = new LinkedHashMap<String, SystemOfMeasurement>();
1288 SYSTEMS_OF_MEASUREMENT.put(marktr("Metric"), METRIC_SOM);
1289 SYSTEMS_OF_MEASUREMENT.put(marktr("Chinese"), CHINESE_SOM);
1290 SYSTEMS_OF_MEASUREMENT.put(marktr("Imperial"), IMPERIAL_SOM);
1291 SYSTEMS_OF_MEASUREMENT.put(marktr("Nautical Mile"), NAUTICAL_MILE_SOM);
1292 }
1293
1294 private static class CursorInfo {
1295 public Cursor cursor;
1296 public Object object;
1297 public CursorInfo(Cursor c, Object o) {
1298 cursor = c;
1299 object = o;
1300 }
1301 }
1302
1303 private LinkedList<CursorInfo> Cursors = new LinkedList<CursorInfo>();
1304 /**
1305 * Set new cursor.
1306 */
1307 public void setNewCursor(Cursor cursor, Object reference) {
1308 if(Cursors.size() > 0) {
1309 CursorInfo l = Cursors.getLast();
1310 if(l != null && l.cursor == cursor && l.object == reference)
1311 return;
1312 stripCursors(reference);
1313 }
1314 Cursors.add(new CursorInfo(cursor, reference));
1315 setCursor(cursor);
1316 }
1317 public void setNewCursor(int cursor, Object reference) {
1318 setNewCursor(Cursor.getPredefinedCursor(cursor), reference);
1319 }
1320 /**
1321 * Remove the new cursor and reset to previous
1322 */
1323 public void resetCursor(Object reference) {
1324 if(Cursors.size() == 0) {
1325 setCursor(null);
1326 return;
1327 }
1328 CursorInfo l = Cursors.getLast();
1329 stripCursors(reference);
1330 if(l != null && l.object == reference) {
1331 if(Cursors.size() == 0) {
1332 setCursor(null);
1333 } else {
1334 setCursor(Cursors.getLast().cursor);
1335 }
1336 }
1337 }
1338
1339 private void stripCursors(Object reference) {
1340 LinkedList<CursorInfo> c = new LinkedList<CursorInfo>();
1341 for(CursorInfo i : Cursors) {
1342 if(i.object != reference) {
1343 c.add(i);
1344 }
1345 }
1346 Cursors = c;
1347 }
1348
1349 @Override
1350 public void paint(Graphics g) {
1351 synchronized (paintRequestLock) {
1352 if (paintRect != null) {
1353 Graphics g2 = g.create();
1354 g2.setColor(Utils.complement(PaintColors.getBackgroundColor()));
1355 g2.drawRect(paintRect.x, paintRect.y, paintRect.width, paintRect.height);
1356 g2.dispose();
1357 }
1358 if (paintPoly != null) {
1359 Graphics g2 = g.create();
1360 g2.setColor(Utils.complement(PaintColors.getBackgroundColor()));
1361 g2.drawPolyline(paintPoly.xpoints, paintPoly.ypoints, paintPoly.npoints);
1362 g2.dispose();
1363 }
1364 }
1365 super.paint(g);
1366 }
1367
1368 /**
1369 * Requests to paint the given {@code Rectangle}.
1370 * @param r The Rectangle to draw
1371 * @see #requestClearRect
1372 * @since 5500
1373 */
1374 public void requestPaintRect(Rectangle r) {
1375 if (r != null) {
1376 synchronized (paintRequestLock) {
1377 paintRect = r;
1378 }
1379 repaint();
1380 }
1381 }
1382
1383 /**
1384 * Requests to paint the given {@code Polygon} as a polyline (unclosed polygon).
1385 * @param p The Polygon to draw
1386 * @see #requestClearPoly
1387 * @since 5500
1388 */
1389 public void requestPaintPoly(Polygon p) {
1390 if (p != null) {
1391 synchronized (paintRequestLock) {
1392 paintPoly = p;
1393 }
1394 repaint();
1395 }
1396 }
1397
1398 /**
1399 * Requests to clear the rectangled previously drawn.
1400 * @see #requestPaintRect
1401 * @since 5500
1402 */
1403 public void requestClearRect() {
1404 synchronized (paintRequestLock) {
1405 paintRect = null;
1406 }
1407 repaint();
1408 }
1409
1410 /**
1411 * Requests to clear the polyline previously drawn.
1412 * @see #requestPaintPoly
1413 * @since 5500
1414 */
1415 public void requestClearPoly() {
1416 synchronized (paintRequestLock) {
1417 paintPoly = null;
1418 }
1419 repaint();
1420 }
1421}
Note: See TracBrowser for help on using the repository browser.