001    // License: GPL. See LICENSE file for details.
002    package org.openstreetmap.josm.gui;
003    
004    import static org.openstreetmap.josm.tools.I18n.marktr;
005    
006    import java.awt.Cursor;
007    import java.awt.Graphics;
008    import java.awt.Point;
009    import java.awt.Polygon;
010    import java.awt.Rectangle;
011    import java.awt.geom.AffineTransform;
012    import java.awt.geom.Point2D;
013    import java.util.ArrayList;
014    import java.util.Collection;
015    import java.util.Collections;
016    import java.util.Date;
017    import java.util.HashSet;
018    import java.util.LinkedHashMap;
019    import java.util.LinkedList;
020    import java.util.List;
021    import java.util.Locale;
022    import java.util.Map;
023    import java.util.Set;
024    import java.util.Stack;
025    import java.util.TreeMap;
026    import java.util.concurrent.CopyOnWriteArrayList;
027    
028    import javax.swing.JComponent;
029    
030    import org.openstreetmap.josm.Main;
031    import org.openstreetmap.josm.data.Bounds;
032    import org.openstreetmap.josm.data.ProjectionBounds;
033    import org.openstreetmap.josm.data.coor.CachedLatLon;
034    import org.openstreetmap.josm.data.coor.EastNorth;
035    import org.openstreetmap.josm.data.coor.LatLon;
036    import org.openstreetmap.josm.data.osm.BBox;
037    import org.openstreetmap.josm.data.osm.DataSet;
038    import org.openstreetmap.josm.data.osm.Node;
039    import org.openstreetmap.josm.data.osm.OsmPrimitive;
040    import org.openstreetmap.josm.data.osm.Relation;
041    import org.openstreetmap.josm.data.osm.Way;
042    import org.openstreetmap.josm.data.osm.WaySegment;
043    import org.openstreetmap.josm.data.osm.visitor.paint.PaintColors;
044    import org.openstreetmap.josm.data.preferences.IntegerProperty;
045    import org.openstreetmap.josm.data.projection.Projection;
046    import org.openstreetmap.josm.data.projection.Projections;
047    import org.openstreetmap.josm.gui.help.Helpful;
048    import org.openstreetmap.josm.gui.preferences.projection.ProjectionPreference;
049    import org.openstreetmap.josm.tools.Predicate;
050    import org.openstreetmap.josm.tools.Utils;
051    
052    /**
053     * An component that can be navigated by a mapmover. Used as map view and for the
054     * zoomer in the download dialog.
055     *
056     * @author imi
057     */
058    public class NavigatableComponent extends JComponent implements Helpful {
059    
060        /**
061         * Interface to notify listeners of the change of the zoom area.
062         */
063        public interface ZoomChangeListener {
064            void zoomChanged();
065        }
066    
067        public static final IntegerProperty PROP_SNAP_DISTANCE = new IntegerProperty("mappaint.node.snap-distance", 10);
068    
069        public static final String PROPNAME_CENTER = "center";
070        public static final String PROPNAME_SCALE  = "scale";
071    
072        /**
073         * the zoom listeners
074         */
075        private static final CopyOnWriteArrayList<ZoomChangeListener> zoomChangeListeners = new CopyOnWriteArrayList<ZoomChangeListener>();
076    
077        /**
078         * Removes a zoom change listener
079         *
080         * @param listener the listener. Ignored if null or already absent
081         */
082        public static void removeZoomChangeListener(NavigatableComponent.ZoomChangeListener listener) {
083            zoomChangeListeners.remove(listener);
084        }
085    
086        /**
087         * Adds a zoom change listener
088         *
089         * @param listener the listener. Ignored if null or already registered.
090         */
091        public static void addZoomChangeListener(NavigatableComponent.ZoomChangeListener listener) {
092            if (listener != null) {
093                zoomChangeListeners.addIfAbsent(listener);
094            }
095        }
096    
097        protected static void fireZoomChanged() {
098            for (ZoomChangeListener l : zoomChangeListeners) {
099                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    }