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 }