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

Last change on this file since 4436 was 4126, checked in by bastiK, 13 years ago

memory optimizations for Node & WayPoint (Patch by Gubaer, modified)

The field 'proj' in CachedLatLon is a waste of memory. For the 2 classes where this has the greatest impact, the cache for the projected coordinates is replaced by 2 simple double fields (east & north). On projection change, they have to be invalidated explicitly. This is handled by the DataSet & the GpxLayer.

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