| 1 | // License: GPL. Copyright 2007 by Immanuel Scholz and others |
| 2 | |
| 3 | package org.openstreetmap.josm.actions.mapmode; |
| 4 | |
| 5 | import static org.openstreetmap.josm.gui.help.HelpUtil.ht; |
| 6 | import static org.openstreetmap.josm.tools.I18n.tr; |
| 7 | |
| 8 | import java.awt.AWTEvent; |
| 9 | import java.awt.Cursor; |
| 10 | import java.awt.Point; |
| 11 | import java.awt.Toolkit; |
| 12 | import java.awt.event.AWTEventListener; |
| 13 | import java.awt.event.ActionEvent; |
| 14 | import java.awt.event.InputEvent; |
| 15 | import java.awt.event.KeyEvent; |
| 16 | import java.awt.event.MouseEvent; |
| 17 | import java.util.ArrayList; |
| 18 | import java.util.Collections; |
| 19 | import java.util.HashMap; |
| 20 | import java.util.List; |
| 21 | |
| 22 | import org.openstreetmap.josm.Main; |
| 23 | import org.openstreetmap.josm.actions.CombineWayAction; |
| 24 | import org.openstreetmap.josm.command.AddCommand; |
| 25 | import org.openstreetmap.josm.command.Command; |
| 26 | import org.openstreetmap.josm.command.SequenceCommand; |
| 27 | import org.openstreetmap.josm.data.coor.EastNorth; |
| 28 | import org.openstreetmap.josm.data.osm.DataSet; |
| 29 | import org.openstreetmap.josm.data.osm.Node; |
| 30 | import org.openstreetmap.josm.data.osm.OsmPrimitive; |
| 31 | import org.openstreetmap.josm.data.osm.Way; |
| 32 | import org.openstreetmap.josm.data.osm.WaySegment; |
| 33 | import org.openstreetmap.josm.gui.MapFrame; |
| 34 | import org.openstreetmap.josm.gui.MapView; |
| 35 | import org.openstreetmap.josm.gui.layer.Layer; |
| 36 | import org.openstreetmap.josm.gui.layer.OsmDataLayer; |
| 37 | import org.openstreetmap.josm.tools.Geometry; |
| 38 | import org.openstreetmap.josm.tools.Shortcut; |
| 39 | |
| 40 | //// TODO: (list below) |
| 41 | /* |
| 42 | * 1. Use selected nodes as split points for the selected ways. |
| 43 | * |
| 44 | * The ways containing the selected nodes will be split and only the "inner" |
| 45 | * parts will be copied |
| 46 | * |
| 47 | * 2. Enter exact offset |
| 48 | * |
| 49 | * 3. Improve snapping |
| 50 | * |
| 51 | * Need at least a setting for step length |
| 52 | * |
| 53 | * 4. Visual cues? Highlight source path, draw offset line, etc? |
| 54 | * |
| 55 | * 5. Cursors |
| 56 | * |
| 57 | * 6. (long term) Parallelize and adjust offsets of existing ways |
| 58 | */ |
| 59 | |
| 60 | /** |
| 61 | * MapMode for making parallel ways. |
| 62 | * |
| 63 | * All calculations are done in projected coordinates. |
| 64 | * |
| 65 | * @author Ole Jørgen Brønner (olejorgenb) |
| 66 | */ |
| 67 | public class ParallelWayAction extends MapMode implements AWTEventListener { |
| 68 | // omg.. |
| 69 | public void dumpMouseEvent(MouseEvent e) { |
| 70 | // System.out.println(e.paramString()); |
| 71 | // System.out.print("e.getButton() = "); |
| 72 | // switch (e.getButton()) { |
| 73 | // case MouseEvent.BUTTON1: |
| 74 | // System.out.println("BUTTON1"); |
| 75 | // break; |
| 76 | // case MouseEvent.BUTTON2: |
| 77 | // System.out.println("BUTTON2"); |
| 78 | // break; |
| 79 | // case MouseEvent.BUTTON3: |
| 80 | // System.out.println("BUTTON3"); |
| 81 | // break; |
| 82 | // case MouseEvent.NOBUTTON: |
| 83 | // System.out.println("NOBUTTON"); |
| 84 | // break; |
| 85 | // default: |
| 86 | // System.out.println(e.getButton()); |
| 87 | // break; |
| 88 | // } |
| 89 | } |
| 90 | |
| 91 | private enum Mode { |
| 92 | dragging, normal |
| 93 | } |
| 94 | |
| 95 | private Mode mode; |
| 96 | |
| 97 | private boolean snap; |
| 98 | private double snapThreshold; |
| 99 | |
| 100 | private int initialMoveDelay; |
| 101 | // private int initialMoveThreshold; |
| 102 | |
| 103 | private final MapView mv; |
| 104 | |
| 105 | ParallelWayAction.MouseTracker mouseTracker = new MouseTracker(); |
| 106 | |
| 107 | private WaySegment referenceSegment; |
| 108 | private ParallelWays pWays; |
| 109 | |
| 110 | private boolean ctrl; |
| 111 | private boolean alt; |
| 112 | private boolean shift; |
| 113 | |
| 114 | public ParallelWayAction(MapFrame mapFrame) { |
| 115 | super(tr("Parallel"), "parallel", tr("Makes a paralell copy of the selected way(s)"), Shortcut |
| 116 | .registerShortcut("mapmode:parallel", tr("Mode: {0}", tr("Parallel")), KeyEvent.VK_P, |
| 117 | Shortcut.GROUP_EDIT, Shortcut.SHIFT_DEFAULT), mapFrame, Cursor |
| 118 | .getPredefinedCursor(Cursor.MOVE_CURSOR)); |
| 119 | putValue("help", ht("/Action/Parallel")); |
| 120 | mv = mapFrame.mapView; |
| 121 | |
| 122 | mouseTracker = new MouseTracker(); |
| 123 | } |
| 124 | |
| 125 | @Override |
| 126 | public String getModeHelpText() { |
| 127 | // TODO: add more detailed feedback based on modifier state. |
| 128 | switch (mode) { |
| 129 | case normal: |
| 130 | return tr("Select ways as in Select mode. Drag selected ways or a single way to create a parallel copy (Alt for tagless)"); |
| 131 | case dragging: |
| 132 | return tr("Hold Ctrl to snap to whole meters"); |
| 133 | } |
| 134 | return ""; // impossible .. |
| 135 | } |
| 136 | |
| 137 | @Override |
| 138 | public void enterMode() { |
| 139 | setMode(Mode.normal); |
| 140 | pWays = null; |
| 141 | super.enterMode(); |
| 142 | mv.addMouseListener(this); |
| 143 | mv.addMouseMotionListener(this); |
| 144 | // FIXME: What do do with default values? Not that they probably change |
| 145 | // that often, but having them multiple places is still a bit ickky. |
| 146 | initialMoveDelay = Main.pref.getInteger("edit.initial-move-delay", 200); |
| 147 | // initialMoveThreshold = Main.pref.getInteger("edit.initial-move-threshold", 5); |
| 148 | snapThreshold = Main.pref.getDouble("edit.make-parallel-way-action.snap-threshold", 0.35); |
| 149 | |
| 150 | //// Needed to update the mouse cursor if modifiers are changed when the mouse is motionless |
| 151 | try { |
| 152 | Toolkit.getDefaultToolkit().addAWTEventListener(this, AWTEvent.KEY_EVENT_MASK); |
| 153 | } catch (SecurityException ex) { |
| 154 | } |
| 155 | |
| 156 | } |
| 157 | |
| 158 | @Override |
| 159 | public void exitMode() { |
| 160 | super.exitMode(); |
| 161 | mv.removeMouseListener(this); |
| 162 | mv.removeMouseMotionListener(this); |
| 163 | Main.map.statusLine.setDist(-1); |
| 164 | Main.map.statusLine.repaint(); |
| 165 | try { |
| 166 | Toolkit.getDefaultToolkit().removeAWTEventListener(this); |
| 167 | } catch (SecurityException ex) { |
| 168 | } |
| 169 | } |
| 170 | |
| 171 | @Override |
| 172 | public boolean layerIsSupported(Layer layer) { |
| 173 | return layer instanceof OsmDataLayer; |
| 174 | } |
| 175 | |
| 176 | @Override |
| 177 | public void eventDispatched(AWTEvent e) { |
| 178 | if (Main.map == null || mv == null || !mv.isActiveLayerDrawable()) |
| 179 | return; |
| 180 | |
| 181 | // Should only get InputEvents due to the mask in enterMode |
| 182 | updateKeyModifiers((InputEvent) e); |
| 183 | } |
| 184 | |
| 185 | private void updateKeyModifiers(InputEvent e) { |
| 186 | ctrl = (e.getModifiers() & ActionEvent.CTRL_MASK) != 0; |
| 187 | alt = (e.getModifiers() & (ActionEvent.ALT_MASK | InputEvent.ALT_GRAPH_MASK)) != 0; |
| 188 | shift = (e.getModifiers() & ActionEvent.SHIFT_MASK) != 0; |
| 189 | } |
| 190 | |
| 191 | private void updateCursor(Mode mode) { |
| 192 | |
| 193 | } |
| 194 | |
| 195 | private void setMode(Mode mode) { |
| 196 | this.mode = mode; |
| 197 | updateCursor(this.mode); |
| 198 | updateStatusLine(); |
| 199 | } |
| 200 | |
| 201 | private void onModifiersChanged() { |
| 202 | updateCursor(mode); |
| 203 | } |
| 204 | |
| 205 | @Override |
| 206 | public void mousePressed(MouseEvent e) { |
| 207 | mouseTracker.registrerPressed(e); |
| 208 | dumpMouseEvent(e); |
| 209 | // System.out.println("MousePressed"); |
| 210 | if (e.getButton() != MouseEvent.BUTTON1) |
| 211 | return; |
| 212 | |
| 213 | if (!mv.isActiveLayerVisible()) |
| 214 | return; |
| 215 | if (!mv.isActiveLayerDrawable()) |
| 216 | return; |
| 217 | if (!(Boolean) this.getValue("active")) |
| 218 | return; |
| 219 | } |
| 220 | |
| 221 | @Override |
| 222 | public void mouseClicked(MouseEvent e) { |
| 223 | // System.out.println("MouseClicked"); |
| 224 | } |
| 225 | |
| 226 | @Override |
| 227 | public void mouseReleased(MouseEvent e) { |
| 228 | mouseTracker.registrerReleased(e); |
| 229 | dumpMouseEvent(e); |
| 230 | // System.out.println("MouseReleased"); |
| 231 | if (e.getButton() != MouseEvent.BUTTON1) |
| 232 | return; |
| 233 | |
| 234 | updateKeyModifiers(e); |
| 235 | |
| 236 | pWays = null; |
| 237 | setMode(Mode.normal); |
| 238 | |
| 239 | if (!(mouseTracker.buttonState[MouseEvent.BUTTON1].hasBeenDragged)) { |
| 240 | assert (pWays == null); |
| 241 | // use point from press or click event? (or are these always the same) |
| 242 | Way nearestWay = mv.getNearestWay(e.getPoint(), OsmPrimitive.isSelectablePredicate); |
| 243 | if (nearestWay == null) { |
| 244 | if (!shift && !ctrl) { |
| 245 | getCurrentDataSet().clearSelection(); |
| 246 | } |
| 247 | return; |
| 248 | } |
| 249 | boolean isSelected = nearestWay.isSelected(); |
| 250 | if (shift && !ctrl && !alt) { |
| 251 | if (!isSelected) { |
| 252 | getCurrentDataSet().addSelected(nearestWay); |
| 253 | } |
| 254 | } else if (ctrl && !shift && !alt) { |
| 255 | if (isSelected) { |
| 256 | getCurrentDataSet().clearSelection(nearestWay); |
| 257 | } else { |
| 258 | getCurrentDataSet().addSelected(nearestWay); |
| 259 | } |
| 260 | } else if (!ctrl && !shift && !alt) { |
| 261 | getCurrentDataSet().setSelected(nearestWay); |
| 262 | } // else -> invalid modifier combination |
| 263 | } |
| 264 | } |
| 265 | |
| 266 | @Override |
| 267 | public void mouseDragged(MouseEvent e) { |
| 268 | mouseTracker.registrerDragged(e); |
| 269 | dumpMouseEvent(e); |
| 270 | // System.out.println("MouseDragged"); |
| 271 | if (!mouseTracker.buttonState[MouseEvent.BUTTON1].isPressed) |
| 272 | return; // WTF.. the events passed here does not have button info? |
| 273 | |
| 274 | updateKeyModifiers(e); |
| 275 | |
| 276 | if ((System.currentTimeMillis() - mouseTracker.buttonState[MouseEvent.BUTTON1].pressedTime) < initialMoveDelay) |
| 277 | return; |
| 278 | |
| 279 | if (!shift && !ctrl) { |
| 280 | snap = false; |
| 281 | } else if (!shift && ctrl) { |
| 282 | snap = true; |
| 283 | } |
| 284 | |
| 285 | Point p = e.getPoint(); |
| 286 | if (pWays == null) { |
| 287 | boolean copyTags = true; |
| 288 | // This is the first drag event |
| 289 | if (!shift && alt) { |
| 290 | copyTags = false; |
| 291 | } else if (!shift && !alt) { |
| 292 | copyTags = true; |
| 293 | } else |
| 294 | return; // invalid modifier combinations |
| 295 | // Important to use mouse position from the press, since the drag |
| 296 | // event can come quite late |
| 297 | if (!initParallelWays(mouseTracker.buttonState[MouseEvent.BUTTON1].pressedPos, copyTags)) |
| 298 | return; |
| 299 | setMode(Mode.dragging); |
| 300 | } |
| 301 | |
| 302 | //// Calculate distance to the reference line |
| 303 | EastNorth enp = mv.getEastNorth((int) p.getX(), (int) p.getY()); |
| 304 | EastNorth enOnRefLine = Geometry.closestPointToLine(referenceSegment.getFirstNode().getEastNorth(), |
| 305 | referenceSegment.getSecondNode().getEastNorth(), enp); |
| 306 | double d = enp.distance(enOnRefLine); |
| 307 | boolean toTheRight = Geometry.isToTheRightSideOfLine(referenceSegment.getFirstNode(), |
| 308 | referenceSegment.getFirstNode(), referenceSegment.getSecondNode(), new Node(enp)); |
| 309 | |
| 310 | if (snap) { |
| 311 | // TODO: Very simple snapping |
| 312 | // - Snap steps and/or threshold relative to the distance? |
| 313 | long closestWholeUnit = Math.round(d); |
| 314 | if (Math.abs(closestWholeUnit - d) < snapThreshold) { |
| 315 | d = closestWholeUnit; |
| 316 | } else { |
| 317 | d = closestWholeUnit + Math.signum(closestWholeUnit - d) * -0.5; |
| 318 | } |
| 319 | } |
| 320 | if (toTheRight) { |
| 321 | d = -d; |
| 322 | } |
| 323 | pWays.changeOffset(d); |
| 324 | |
| 325 | Main.map.statusLine.setDist(Math.abs(d)); |
| 326 | Main.map.statusLine.repaint(); |
| 327 | mv.repaint(); |
| 328 | } |
| 329 | |
| 330 | // TODO: rename |
| 331 | private boolean initParallelWays(Point p, boolean copyTags) { |
| 332 | referenceSegment = mv.getNearestWaySegment(p, Way.isUsablePredicate, true); |
| 333 | if (referenceSegment == null) |
| 334 | return false; |
| 335 | |
| 336 | // The collection returned is very inefficient so we collect it in an ArrayList |
| 337 | // Not sure if the list is iterated multiple times any more... |
| 338 | List<Way> selectedWays = new ArrayList<Way>(getCurrentDataSet().getSelectedWays()); |
| 339 | if (!selectedWays.contains(referenceSegment.way)) { |
| 340 | getCurrentDataSet().setSelected(referenceSegment.way); |
| 341 | selectedWays.clear(); |
| 342 | selectedWays.add(referenceSegment.way); |
| 343 | } |
| 344 | |
| 345 | try { |
| 346 | pWays = new ParallelWays(selectedWays, copyTags, selectedWays.indexOf(referenceSegment.way)); |
| 347 | pWays.commit(null); |
| 348 | getCurrentDataSet().setSelected(pWays.ways); |
| 349 | return true; |
| 350 | } catch (IllegalArgumentException e) { |
| 351 | System.err.println(e); |
| 352 | pWays = null; |
| 353 | return false; |
| 354 | } |
| 355 | } |
| 356 | |
| 357 | // TODO: 'ParallelPath' better name? |
| 358 | static final class ParallelWays { |
| 359 | private List<Way> ways; |
| 360 | private List<Node> sortedNodes; |
| 361 | |
| 362 | private int nodeCount; |
| 363 | |
| 364 | private EastNorth[] pts; |
| 365 | private EastNorth[] normals; |
| 366 | |
| 367 | public ParallelWays(List<Way> sourceWays, boolean copyTags, int refWayIndex) { |
| 368 | // Possible/sensible to use PrimetiveDeepCopy here? |
| 369 | |
| 370 | //// Make a deep copy of the ways, keeping the copied ways connected |
| 371 | HashMap<Node, Node> splitNodeMap = new HashMap<Node, Node>(sourceWays.size()); |
| 372 | for (Way w : sourceWays) { |
| 373 | if (!splitNodeMap.containsKey(w.firstNode())) { |
| 374 | splitNodeMap.put(w.firstNode(), copyNode(w.firstNode(), copyTags)); |
| 375 | } |
| 376 | if (!splitNodeMap.containsKey(w.lastNode())) { |
| 377 | splitNodeMap.put(w.lastNode(), copyNode(w.lastNode(), copyTags)); |
| 378 | } |
| 379 | } |
| 380 | ways = new ArrayList<Way>(sourceWays.size()); |
| 381 | for (Way w : sourceWays) { |
| 382 | Way wCopy = new Way(); |
| 383 | wCopy.addNode(splitNodeMap.get(w.firstNode())); |
| 384 | for (int i = 1; i < w.getNodesCount() - 1; i++) { |
| 385 | wCopy.addNode(copyNode(w.getNode(i), copyTags)); |
| 386 | } |
| 387 | wCopy.addNode(splitNodeMap.get(w.lastNode())); |
| 388 | if (copyTags) { |
| 389 | wCopy.setKeys(w.getKeys()); |
| 390 | } |
| 391 | ways.add(wCopy); |
| 392 | } |
| 393 | sourceWays = null; // Ensure that we only use the copies from now |
| 394 | |
| 395 | //// Find a linear ordering of the nodes. Fail if there isn't one. |
| 396 | CombineWayAction.NodeGraph nodeGraph = CombineWayAction.NodeGraph.createUndirectedGraphFromNodeWays(ways); |
| 397 | sortedNodes = nodeGraph.buildSpanningPath(); |
| 398 | if (sortedNodes == null) |
| 399 | throw new IllegalArgumentException("Ways must have spanning path"); // Create a dedicated exception? |
| 400 | |
| 401 | //// Ugly method of ensuring that the offset isn't inverted. I'm sure there is a better and more elegant way, but I'm starting to get sleepy, so I do this for now. |
| 402 | { |
| 403 | Way refWay = ways.get(refWayIndex); |
| 404 | boolean refWayReversed = false; |
| 405 | if (isClosedPath()) { // Nodes occur more than once in the list |
| 406 | if (refWay.firstNode() == sortedNodes.get(0) && refWay.lastNode() == sortedNodes.get(0)) { |
| 407 | refWayReversed = sortedNodes.get(1) != refWay.getNode(1); |
| 408 | } else if (refWay.lastNode() == sortedNodes.get(0)) { |
| 409 | refWayReversed = |
| 410 | sortedNodes.get(sortedNodes.size() - 1) != refWay.getNode(refWay.getNodesCount() - 1); |
| 411 | } else if (refWay.firstNode() == sortedNodes.get(0)) { |
| 412 | refWayReversed = sortedNodes.get(1) != refWay.getNode(1); |
| 413 | } else { |
| 414 | refWayReversed = |
| 415 | sortedNodes.indexOf(refWay.firstNode()) > sortedNodes.indexOf(refWay.lastNode()); |
| 416 | } |
| 417 | |
| 418 | } else { |
| 419 | refWayReversed = sortedNodes.indexOf(refWay.firstNode()) > sortedNodes.indexOf(refWay.lastNode()); |
| 420 | } |
| 421 | if (refWayReversed) { |
| 422 | Collections.reverse(sortedNodes); // need to keep the orientation of the reference way. |
| 423 | System.err.println("reversed!"); |
| 424 | } |
| 425 | } |
| 426 | |
| 427 | //// Initialize the required parameters. (segment normals, etc.) |
| 428 | nodeCount = sortedNodes.size(); |
| 429 | pts = new EastNorth[nodeCount]; |
| 430 | normals = new EastNorth[nodeCount - 1]; |
| 431 | int i = 0; |
| 432 | for (Node n : sortedNodes) { |
| 433 | EastNorth t = n.getEastNorth(); |
| 434 | pts[i] = t; |
| 435 | i++; |
| 436 | } |
| 437 | for (i = 0; i < nodeCount - 1; i++) { |
| 438 | double dx = pts[i + 1].getX() - pts[i].getX(); |
| 439 | double dy = pts[i + 1].getY() - pts[i].getY(); |
| 440 | double len = Math.sqrt(dx * dx + dy * dy); |
| 441 | normals[i] = new EastNorth(-dy / len, dx / len); |
| 442 | } |
| 443 | } |
| 444 | |
| 445 | public boolean isClosedPath() { |
| 446 | return sortedNodes.get(0) == sortedNodes.get(sortedNodes.size() - 1); |
| 447 | } |
| 448 | |
| 449 | public void changeOffset(double d) { |
| 450 | //// This is the core algorithm: |
| 451 | /* 1. Calculate a parallel line, offset by 'd', to each segment in |
| 452 | * the path |
| 453 | * 2. Find the intersection of lines belonging to neighboring |
| 454 | * segments. These become the new node positions |
| 455 | * 3. Do some special casing for closed paths |
| 456 | * |
| 457 | * Simple and probably not even close to optimal performance-vise |
| 458 | */ |
| 459 | |
| 460 | EastNorth[] ppts = new EastNorth[nodeCount]; |
| 461 | |
| 462 | EastNorth prevA = add(pts[0], mul(normals[0], d)); |
| 463 | EastNorth prevB = add(pts[1], mul(normals[0], d)); |
| 464 | for (int i = 1; i < nodeCount - 1; i++) { |
| 465 | EastNorth A = add(pts[i], mul(normals[i], d)); |
| 466 | EastNorth B = add(pts[i + 1], mul(normals[i], d)); |
| 467 | if (Geometry.segmentsParallel(A, B, prevA, prevB)) { |
| 468 | ppts[i] = A; |
| 469 | } else { |
| 470 | ppts[i] = Geometry.getLineLineIntersection(A, B, prevA, prevB); |
| 471 | } |
| 472 | prevA = A; |
| 473 | prevB = B; |
| 474 | } |
| 475 | if (isClosedPath()) { |
| 476 | EastNorth A = add(pts[0], mul(normals[0], d)); |
| 477 | EastNorth B = add(pts[1], mul(normals[0], d)); |
| 478 | if (Geometry.segmentsParallel(A, B, prevA, prevB)) { |
| 479 | ppts[0] = A; |
| 480 | } else { |
| 481 | ppts[0] = Geometry.getLineLineIntersection(A, B, prevA, prevB); |
| 482 | } |
| 483 | ppts[nodeCount - 1] = ppts[0]; |
| 484 | } else { |
| 485 | ppts[0] = add(pts[0], mul(normals[0], d)); |
| 486 | ppts[nodeCount - 1] = add(pts[nodeCount - 1], mul(normals[nodeCount - 2], d)); |
| 487 | } |
| 488 | |
| 489 | for (int i = 0; i < nodeCount; i++) { |
| 490 | sortedNodes.get(i).setEastNorth(ppts[i]); |
| 491 | } |
| 492 | } |
| 493 | |
| 494 | // Draw helper lines instead like DrawAction ExtrudeAction? |
| 495 | public void commit(DataSet ds) { |
| 496 | SequenceCommand undoCommand = new SequenceCommand("Make parallel way(s)", makeAddWayAndNodesCommandList()); |
| 497 | Main.main.undoRedo.add(undoCommand); |
| 498 | } |
| 499 | |
| 500 | private List<Command> makeAddWayAndNodesCommandList() { |
| 501 | ArrayList<Command> commands = new ArrayList<Command>(sortedNodes.size() + ways.size()); |
| 502 | for (int i = 0; i < sortedNodes.size() - 1; i++) { |
| 503 | commands.add(new AddCommand(sortedNodes.get(i))); |
| 504 | } |
| 505 | if (!isClosedPath()) { |
| 506 | commands.add(new AddCommand(sortedNodes.get(sortedNodes.size() - 1))); |
| 507 | } |
| 508 | for (Way w : ways) { |
| 509 | commands.add(new AddCommand(w)); |
| 510 | } |
| 511 | return commands; |
| 512 | } |
| 513 | |
| 514 | static private Node copyNode(Node source, boolean copyTags) { |
| 515 | if (copyTags) |
| 516 | return new Node(source, true); |
| 517 | else { |
| 518 | Node n = new Node(); |
| 519 | n.setCoor(source.getCoor()); |
| 520 | return n; |
| 521 | } |
| 522 | } |
| 523 | |
| 524 | // We need either a dedicated vector type, or operations such as these |
| 525 | // added to EastNorth... |
| 526 | static private EastNorth mul(EastNorth en, double f) { |
| 527 | return new EastNorth(en.getX() * f, en.getY() * f); |
| 528 | } |
| 529 | |
| 530 | static private EastNorth add(EastNorth a, EastNorth b) { |
| 531 | return new EastNorth(a.east() + b.east(), a.north() + b.north()); |
| 532 | } |
| 533 | } |
| 534 | |
| 535 | // TODO: Finish me. (or maybe having a state-tracker object for this is over-complicating things?) |
| 536 | // Dunno.. this swing stuff is a mess.. |
| 537 | static final class MouseTracker { |
| 538 | public static final class ButtonState { |
| 539 | public long pressedTime = -1; |
| 540 | public Point pressedPos = null; |
| 541 | public long releasedTime = -1; |
| 542 | public Point releasedPos = null; |
| 543 | public boolean hasBeenDragged = false; |
| 544 | public boolean isPressed = false; |
| 545 | public Point lastPos = null; |
| 546 | } |
| 547 | |
| 548 | public MouseTracker() { |
| 549 | for (int i = 0; i < buttonCount; i++) { |
| 550 | buttonState[i] = new ButtonState(); |
| 551 | } |
| 552 | } |
| 553 | |
| 554 | public ButtonState[] buttonState = new ButtonState[4]; |
| 555 | public final int buttonCount = 4; |
| 556 | |
| 557 | public MouseEvent lastPressedEvent; |
| 558 | public MouseEvent lastMoveEvent; |
| 559 | public MouseEvent lastDragEvent; |
| 560 | public MouseEvent lastReleaseEvent; |
| 561 | |
| 562 | public ButtonState getButtonState(MouseEvent e) { |
| 563 | return buttonState[e.getButton()]; |
| 564 | } |
| 565 | |
| 566 | public void registrerPressed(MouseEvent e) { |
| 567 | ButtonState b = getButtonState(e); |
| 568 | b.pressedTime = System.currentTimeMillis(); |
| 569 | b.pressedPos = e.getPoint(); |
| 570 | b.hasBeenDragged = false; |
| 571 | b.lastPos = e.getPoint(); |
| 572 | b.isPressed = true; |
| 573 | |
| 574 | lastPressedEvent = e; |
| 575 | } |
| 576 | |
| 577 | public void registrerReleased(MouseEvent e) { |
| 578 | ButtonState b = getButtonState(e); |
| 579 | b.releasedTime = System.currentTimeMillis(); |
| 580 | b.releasedPos = e.getPoint(); |
| 581 | b.isPressed = false; |
| 582 | b.lastPos = e.getPoint(); |
| 583 | |
| 584 | lastReleaseEvent = e; |
| 585 | } |
| 586 | |
| 587 | public void registrerMoved(MouseEvent e) { |
| 588 | // TODO: |
| 589 | } |
| 590 | |
| 591 | public void registrerDragged(MouseEvent e) { |
| 592 | // no button info |
| 593 | for (ButtonState b : buttonState) { |
| 594 | if (b.isPressed) { |
| 595 | b.hasBeenDragged = true; |
| 596 | b.lastPos = e.getPoint(); |
| 597 | } |
| 598 | } |
| 599 | |
| 600 | lastDragEvent = e; |
| 601 | } |
| 602 | |
| 603 | public void clear() { |
| 604 | } |
| 605 | } |
| 606 | } |