refactor EditController to make it stateful, and add addWayNodes functionality with...
authorDave Stubbs <osm@randomjunk.co.uk>
Sat, 5 Sep 2009 21:09:55 +0000 (21:09 +0000)
committerDave Stubbs <osm@randomjunk.co.uk>
Sat, 5 Sep 2009 21:09:55 +0000 (21:09 +0000)
13 files changed:
net/systemeD/halcyon/WayUI.as
net/systemeD/halcyon/connection/Connection.as
net/systemeD/halcyon/connection/Entity.as
net/systemeD/halcyon/connection/Node.as
net/systemeD/halcyon/connection/Way.as
net/systemeD/halcyon/connection/WayNodeEvent.as [new file with mode: 0644]
net/systemeD/halcyon/connection/XMLConnection.as
net/systemeD/potlatch2/EditController.as
net/systemeD/potlatch2/TagViewer.mxml
net/systemeD/potlatch2/controller/ControllerState.as [new file with mode: 0644]
net/systemeD/potlatch2/controller/DragWayNode.as [new file with mode: 0644]
net/systemeD/potlatch2/controller/NoSelection.as [new file with mode: 0644]
net/systemeD/potlatch2/controller/SelectedWay.as [new file with mode: 0644]

index 4019008db211811aebd09f69e5ba44174fdf3015..9141ecbb6b06ae266b26a941d9b15cb3d1a23703 100755 (executable)
@@ -39,11 +39,27 @@ package net.systemeD.halcyon {
                        this.map = map;
             init();
             way.addEventListener(Connection.TAG_CHANGE, wayTagChanged);
+            way.addEventListener(Connection.WAY_NODE_ADDED, wayNodeAdded);
+            way.addEventListener(Connection.WAY_NODE_REMOVED, wayNodeRemoved);
+            attachNodeListeners();
+               }
+               
+               private function attachNodeListeners():void {
             for (var i:uint = 0; i < way.length; i++ ) {
                 way.getNode(i).addEventListener(Connection.NODE_MOVED, nodeMoved);
             }
                }
                
+               private function wayNodeAdded(event:WayNodeEvent):void {
+                   event.node.addEventListener(Connection.NODE_MOVED, nodeMoved);
+                   redraw();
+               }
+                   
+               private function wayNodeRemoved(event:WayNodeEvent):void {
+                   event.node.removeEventListener(Connection.NODE_MOVED, nodeMoved);
+                   redraw();
+               }
+                   
         private function wayTagChanged(event:TagEvent):void {
             redraw();
         }
@@ -402,8 +418,8 @@ package net.systemeD.halcyon {
                 var node:Node = way.getNode(i);
                 var nodeX:Number = map.lon2coord(node.lon);
                 var nodeY:Number = map.latp2coord(node.latp);
-                if ( nodeX >= x-2 && nodeX <= x+2 &&
-                     nodeY >= y-2 && nodeY <= y+2 )
+                if ( nodeX >= x-3 && nodeX <= x+3 &&
+                     nodeY >= y-3 && nodeY <= y+3 )
                     return node;
             }
             return null;
index 965d12f8da73196e6950a58a390e386bccbf604b..f7d48d5d1dbf25a42e94eb9f839b495ab5d22a4b 100755 (executable)
@@ -61,6 +61,8 @@ package net.systemeD.halcyon.connection {
         public static var NEW_POI:String = "new_poi";
         public static var TAG_CHANGE:String = "tag_change";
         public static var NODE_MOVED:String = "node_moved";
+        public static var WAY_NODE_ADDED:String = "way_node_added";
+        public static var WAY_NODE_REMOVED:String = "way_node_removed";
 
         // store the data we download
         private var negativeID:Number = -1;
@@ -89,6 +91,21 @@ package net.systemeD.halcyon.connection {
             if (relation.loaded) { sendEvent(new EntityEvent(NEW_RELATION, relation)); }
         }
 
+        protected function renumberNode(oldID:Number, node:Node):void {
+            nodes[node.id] = node;
+            delete nodes[oldID];
+        }
+
+        protected function renumberWay(oldID:Number, way:Way):void {
+            ways[way.id] = way;
+            delete ways[oldID];
+        }
+
+        protected function renumberRelation(oldID:Number, relation:Relation):void {
+            relations[relation.id] = relation;
+            delete relations[oldID];
+        }
+
                public function sendEvent(e:*):void {
                        dispatchEvent(e);
                }
index 1e77bd0582798cd1235d245ff2e4f60271c70072..de2983a087f24a3870d057fa15cfcc019a290c6c 100644 (file)
@@ -31,8 +31,8 @@ package net.systemeD.halcyon.connection {
             return _loaded;
         }
 
-               public function updateEntityProperties(v:uint,t:Object,l:Boolean):void {
-                       _version=v; tags=t; _loaded=l;
+               public function updateEntityProperties(version:uint, tags:Object, loaded:Boolean):void {
+                       _version=version; this.tags=tags; _loaded=loaded;
                }
 
                // Tag-handling methods
@@ -140,7 +140,11 @@ package net.systemeD.halcyon.connection {
                        for (var o:Object in parents) { a.push(o); }
                        return a;
                }
-
+               
+               public function hasParent(entity:Entity):Boolean {
+            return parents[entity] == true;
+        }
+        
                // To be overridden
 
         public function getType():String {
index de7806522e482aa96a7c567ccc1f83f91c9c56e9..7510462ea47ef6eabfa48bb69b858950ea8d74aa 100644 (file)
@@ -7,8 +7,9 @@ package net.systemeD.halcyon.connection {
 
         public function Node(id:Number, version:uint, tags:Object, loaded:Boolean, lat:Number, lon:Number) {
             super(id, version, tags, loaded);
-            this.lat = lat;
-            this.lon = lon;
+            this._lat = lat;
+            this._latproj = lat2latp(lat);
+            this._lon = lon;
         }
 
                public function update(version:uint, tags:Object, loaded:Boolean, lat:Number, lon:Number):void {
@@ -36,13 +37,19 @@ package net.systemeD.halcyon.connection {
         }
 
         public function set latp(latproj:Number):void {
+            var oldLat:Number = this._lat;
             this._latproj = latproj;
             this._lat = latp2lat(lat);
-        }
+            markDirty();
+            dispatchEvent(new NodeMovedEvent(Connection.NODE_MOVED, this, oldLat, _lon));
+         }
 
         public function set lon(lon:Number):void {
+            var oldLon:Number = this._lon;
             this._lon = lon;
-        }
+            markDirty();
+            dispatchEvent(new NodeMovedEvent(Connection.NODE_MOVED, this, _lat, oldLon));
+         }
 
         public override function toString():String {
             return "Node("+id+"@"+version+"): "+lat+","+lon+" "+getTagList();
index 69c62ee92e2716547a439764c2450f186f2633a7..0dec6af9429a7d67e83f70d95bb4473b93ac98e7 100644 (file)
@@ -1,4 +1,5 @@
 package net.systemeD.halcyon.connection {
+    import flash.geom.Point;
 
     public class Way extends Entity {
         private var nodes:Array;
@@ -28,19 +29,70 @@ package net.systemeD.halcyon.connection {
         public function insertNode(index:uint, node:Node):void {
                        node.addParent(this);
             nodes.splice(index, 0, node);
+            markDirty();
+            dispatchEvent(new WayNodeEvent(Connection.WAY_NODE_ADDED, node, this, index));
         }
 
         public function appendNode(node:Node):uint {
                        node.addParent(this);
             nodes.push(node);
+            markDirty();
+            dispatchEvent(new WayNodeEvent(Connection.WAY_NODE_ADDED, node, this, nodes.length - 1));
             return nodes.length;
         }
 
         public function removeNode(index:uint):void {
             var removed:Array=nodes.splice(index, 1);
                        removed[0].removeParent(this);
+                       markDirty();
+            dispatchEvent(new WayNodeEvent(Connection.WAY_NODE_REMOVED, removed[0], this, index));
         }
 
+        /**
+         * Finds the 1st way segment which intersects the projected
+         * coordinate and adds the node to that segment. If snap is
+         * specified then the node is moved to exactly bisect the
+         * segment.
+         */
+        public function insertNodeAtClosestPosition(newNode:Node, isSnap:Boolean):int {
+            var closestProportion:Number = 1;
+            var newIndex:uint = 0;
+            var nP:Point = new Point(newNode.lon, newNode.latp);
+            var snapped:Point = null;
+            
+            for ( var i:uint; i < length - 1; i++ ) {
+                var node1:Node = getNode(i);
+                var node2:Node = getNode(i+1);
+                var p1:Point = new Point(node1.lon, node1.latp);
+                var p2:Point = new Point(node2.lon, node2.latp);
+                
+                var directDist:Number = Point.distance(p1, p2);
+                var viaNewDist:Number = Point.distance(p1, nP) + Point.distance(nP, p2);
+                        
+                var proportion:Number = Math.abs(viaNewDist/directDist - 1);
+                if ( proportion < closestProportion ) {
+                    newIndex = i+1;
+                    closestProportion = proportion;
+                    snapped = calculateSnappedPoint(p1, p2, nP);
+                }
+            }
+            
+            // splice in new node
+            if ( isSnap ) {
+                newNode.latp = snapped.y;
+                newNode.lon = snapped.x;
+            }
+            insertNode(newIndex, newNode);
+            return newIndex;
+        }
+        
+        private function calculateSnappedPoint(p1:Point, p2:Point, nP:Point):Point {
+            var w:Number = p2.x - p1.x;
+            var h:Number = p2.y - p1.y;
+            var u:Number = ((nP.x-p1.x) * w + (nP.y-p1.y) * h) / (w*w + h*h);
+            return new Point(p1.x + u*w, p1.y+u*h);
+        }
+        
         public override function toString():String {
             return "Way("+id+"@"+version+"): "+getTagList()+
                      " "+nodes.map(function(item:Node,index:int, arr:Array):String {return item.id.toString();}).join(",");
diff --git a/net/systemeD/halcyon/connection/WayNodeEvent.as b/net/systemeD/halcyon/connection/WayNodeEvent.as
new file mode 100644 (file)
index 0000000..1a6bd4c
--- /dev/null
@@ -0,0 +1,25 @@
+package net.systemeD.halcyon.connection {
+
+    import flash.events.Event;
+
+    public class WayNodeEvent extends EntityEvent {
+        private var _way:Way;
+        private var _index:uint;
+
+        public function WayNodeEvent(type:String, node:Node, way:Way, index:uint) {
+            super(type, node);
+            this._way = way;
+            this._index = index;
+        }
+
+        public function get way():Way { return _way; }
+        public function get node():Node { return entity as Node; }
+        public function get index():uint { return _index; }
+
+        public override function toString():String {
+            return super.toString() + " in "+_way+" at "+_index;
+        }
+    }
+
+}
+
index 08c3d8e74bbe19f424fb522d821852fb820ae3a0..4c1155b621f22dd27c57fdec410b07c6a3f08a76 100644 (file)
@@ -151,7 +151,7 @@ package net.systemeD.halcyon.connection {
             upload.appendChild(addModified(changeset, getAllNodeIDs, getNode, serialiseNode));
             upload.appendChild(addModified(changeset, getAllWayIDs, getWay, serialiseWay));
             upload.appendChild(addModified(changeset, getAllRelationIDs, getRelation, serialiseRelation));
-            
+
             // *** TODO *** deleting items
             
             // now actually upload them
@@ -191,7 +191,12 @@ package net.systemeD.halcyon.connection {
                 else if ( type == "relation" ) entity = getRelation(oldID);
                 entity.markClean(newID, version);
                 
-                // *** TODO *** handle renumbering of creates, and deleting
+                if ( oldID != newID ) {
+                    if ( type == "node" ) renumberNode(oldID, entity as Node);
+                    else if ( type == "way" ) renumberWay(oldID, entity as Way);
+                    else if ( type == "relation" ) renumberRelation(oldID, entity as Relation);
+                }
+                // *** TODO *** handle deleting
             }
 
                dispatchEvent(new SaveCompleteEvent(SAVE_COMPLETED, true));
index 3b7881aa6c14a1f14d0c96a065fbb31b82612c9d..fcab3413528951b9d91e3c21fd77260b40decbc3 100644 (file)
@@ -2,64 +2,102 @@ package net.systemeD.potlatch2 {
     import net.systemeD.halcyon.Map;
     import net.systemeD.halcyon.MapController;
     import net.systemeD.halcyon.connection.*;
+    import net.systemeD.potlatch2.controller.*;
        import flash.events.*;
        import flash.geom.*;
 
     public class EditController implements MapController {
 
-        private var map:Map;
+        private var _map:Map;
         private var tagViewer:TagViewer;
-        private var selectedEntity:Entity;
+        private var selectedWay:Way;
+        private var selectedNode:Node;
         
         private var draggingNode:Node = null;
         
+        private var state:ControllerState;
+        private var _connection:Connection;
+        
 
         public function EditController(map:Map, tagViewer:TagViewer) {
-            this.map = map;
+            this._map = map;
             this.tagViewer = tagViewer;
+            setState(new NoSelection());
             
             map.parent.addEventListener(MouseEvent.MOUSE_MOVE, mapMouseEvent);
             map.parent.addEventListener(MouseEvent.MOUSE_UP, mapMouseEvent);
             map.parent.addEventListener(MouseEvent.MOUSE_DOWN, mapMouseEvent);
+            map.parent.addEventListener(MouseEvent.CLICK, mapMouseEvent);
         }
 
         public function setActive():void {
             map.setController(this);
+            _connection = map.connection;
         }
 
+        public function get map():Map {
+            return _map;
+        }
+        
+        public function get connection():Connection {
+            return _connection;
+        }
+        
+        public function setTagViewer(entity:Entity):void {
+            tagViewer.setEntity(entity);
+        }
+        
         private function mapMouseEvent(event:MouseEvent):void {
-            if ( draggingNode != null ) {
-                var mapLoc:Point = map.globalToLocal(new Point(event.stageX, event.stageY));
-                event.localX = mapLoc.x;
-                event.localY = mapLoc.y;
+            var mapLoc:Point = map.globalToLocal(new Point(event.stageX, event.stageY));
+            event.localX = mapLoc.x;
+            event.localY = mapLoc.y;
 
-                processNodeEvent(event, null);
+            var newState:ControllerState = state.processMouseEvent(event, null);
+            setState(newState);
+            if ( draggingNode != null ) {
             }
         }
         
         public function entityMouseEvent(event:MouseEvent, entity:Entity):void {
-            if ( event.type == MouseEvent.MOUSE_DOWN )
+            //if ( event.type == MouseEvent.MOUSE_DOWN )
                 event.stopPropagation();
+                
+            var newState:ControllerState = state.processMouseEvent(event, entity);
+            setState(newState);
 
+            /*
             if ( entity is Node || draggingNode != null ) {
                 processNodeEvent(event, entity);
-                return;
+            } else if ( enity is Way ) {
+                processWayEvent(event, entity);
             }
             
             if ( event.type == MouseEvent.CLICK ) {
-                if ( selectedEntity != null ) {
-                    map.setHighlight(selectedEntity, "selected", false);
-                    map.setHighlight(selectedEntity, "showNodes", false);
+                if ( selectedWay != null ) {
+                    map.setHighlight(selectedWay, "selected", false);
+                    map.setHighlight(selectedWay, "showNodes", false);
                 }
                 tagViewer.setEntity(entity);
                 map.setHighlight(entity, "selected", true);
                 map.setHighlight(entity, "showNodes", true);
-                selectedEntity = entity;
+                selectedWay = entity;
             } else if ( event.type == MouseEvent.MOUSE_OVER )
                 map.setHighlight(entity, "hover", true);
             else if ( event.type == MouseEvent.MOUSE_OUT )
                 map.setHighlight(entity, "hover", false);
-
+            */
+        }
+        
+        private function setState(newState:ControllerState):void {
+            if ( newState == state )
+                return;
+                
+            if ( state != null )
+                state.exitState();
+            newState.setController(this);
+            newState.setPreviousState(state);
+            state = newState;
+            state.enterState();
         }
 
         private function processNodeEvent(event:MouseEvent, entity:Entity):void {
@@ -77,5 +115,6 @@ package net.systemeD.potlatch2 {
         
     }
 
+    
 }
 
index 5d4dff4ae73ba50b43be8d3124342623676e0de0..70a006167a99e50bd9435acf7a5c8e768c9a0ce0 100644 (file)
@@ -61,7 +61,8 @@
               if ( selectedEntity != null )
                   selectedEntity.removeEventListener(Connection.TAG_CHANGE, tagChanged);
               selectedEntity = entity;
-              selectedEntity.addEventListener(Connection.TAG_CHANGE, tagChanged);
+              if ( selectedEntity != null )
+                  selectedEntity.addEventListener(Connection.TAG_CHANGE, tagChanged);
           }
 
           if ( advancedID != null )
@@ -71,7 +72,7 @@
       }
 
       private function refreshFeatureIcon():void {
-          feature = mapFeatures.findMatchingFeature(selectedEntity);
+          feature = selectedEntity == null ? null : mapFeatures.findMatchingFeature(selectedEntity);
           if ( feature != null )
               setFeatureIcon(selectedEntity, feature);
           else
       }
 
       private function setupAdvanced(entity:Entity):void {
-          var entityText:String = "xx";
-          if ( entity is Node ) entityText = "Node";
-          else if ( entity is Way ) entityText = "Way";
-          else if ( entity is Relation ) entityText = "Relation";
-
-          advancedID.htmlText = entityText+": <b>"+entity.id+"</b>";
-
           if ( collection == null ) {
               collection = new ArrayCollection();
-              //var sort:Sort = new Sort();
-              //sort.fields = [new SortField("key", true)];
-              //collection.sort = sort;
-              //collection.refresh();
               advancedTagGrid.dataProvider = collection;
           }
+
           collection.removeAll();
-          var tags:Array = entity.getTagArray();
-          tags.sortOn("key");
-          for each(var tag:Tag in tags)
-              collection.addItem(tag);
+          
+          if ( entity == null ) {
+              advancedID.htmlText = "";
+          } else {
+              var entityText:String = "xx";
+              if ( entity is Node ) entityText = "Node";
+              else if ( entity is Way ) entityText = "Way";
+              else if ( entity is Relation ) entityText = "Relation";
+              advancedID.htmlText = entityText+": <b>"+entity.id+"</b>";
+
+              var tags:Array = entity.getTagArray();
+              tags.sortOn("key");
+              for each(var tag:Tag in tags)
+                  collection.addItem(tag);
+          }
       }
 
       private function tagChanged(event:TagEvent):void {
diff --git a/net/systemeD/potlatch2/controller/ControllerState.as b/net/systemeD/potlatch2/controller/ControllerState.as
new file mode 100644 (file)
index 0000000..286cd54
--- /dev/null
@@ -0,0 +1,30 @@
+package net.systemeD.potlatch2.controller {
+       import flash.events.*;
+    import net.systemeD.halcyon.connection.*;
+    import net.systemeD.potlatch2.EditController;
+
+    public class ControllerState {
+
+        protected var controller:EditController;
+        protected var previousState:ControllerState;
+
+        public function ControllerState() {}
+        public function setController(controller:EditController):void {
+            this.controller = controller;
+        }
+
+        public function setPreviousState(previousState:ControllerState):void {
+            if ( this.previousState == null )
+                this.previousState = previousState;
+        }
+   
+        public function processMouseEvent(event:MouseEvent, entity:Entity):ControllerState {
+            return this;
+        }
+        
+        public function enterState():void {}
+        public function exitState():void {}
+
+    }
+}
diff --git a/net/systemeD/potlatch2/controller/DragWayNode.as b/net/systemeD/potlatch2/controller/DragWayNode.as
new file mode 100644 (file)
index 0000000..dfb89ef
--- /dev/null
@@ -0,0 +1,59 @@
+package net.systemeD.potlatch2.controller {
+       import flash.events.*;
+    import net.systemeD.potlatch2.EditController;
+    import net.systemeD.halcyon.connection.*;
+
+    public class DragWayNode extends ControllerState {
+        private var selectedWay:Way;
+        private var draggingNode:Node;
+        private var isDraggingStarted:Boolean = false;
+        private var downX:Number;
+        private var downY:Number;
+        
+        public function DragWayNode(way:Way, node:Node, mouseDown:MouseEvent) {
+            selectedWay = way;
+            draggingNode = node;
+            downX = mouseDown.localX;
+            downY = mouseDown.localY;
+        }
+        override public function processMouseEvent(event:MouseEvent, entity:Entity):ControllerState {
+            if ( event.type == MouseEvent.MOUSE_UP )
+                return endDrag();
+
+            if ( !isDragging(event) )
+                return this;
+            
+            if ( event.type == MouseEvent.MOUSE_MOVE )
+                return dragTo(event);
+            else   
+                return this;
+        }
+
+        private function isDragging(event:MouseEvent):Boolean {
+            if ( isDraggingStarted )
+                return true;
+            
+            isDraggingStarted = Math.abs(downX - event.localX) > 3 ||
+                                Math.abs(downY - event.localY) > 3;
+            return isDraggingStarted;
+        }
+        
+        private function endDrag():ControllerState {
+            return previousState;
+        }
+        
+        private function dragTo(event:MouseEvent):ControllerState {
+            draggingNode.lat = controller.map.coord2lat(event.localY);
+            draggingNode.lon = controller.map.coord2lon(event.localX);
+            return this;
+        }
+        
+        override public function enterState():void {
+            controller.map.setHighlight(selectedWay, "showNodes", true);
+        }
+        override public function exitState():void {
+            controller.map.setHighlight(selectedWay, "showNodes", false);
+        }
+    }
+}
diff --git a/net/systemeD/potlatch2/controller/NoSelection.as b/net/systemeD/potlatch2/controller/NoSelection.as
new file mode 100644 (file)
index 0000000..2de2e21
--- /dev/null
@@ -0,0 +1,38 @@
+package net.systemeD.potlatch2.controller {
+       import flash.events.*;
+    import net.systemeD.potlatch2.EditController;
+    import net.systemeD.halcyon.connection.*;
+
+    public class NoSelection extends ControllerState {
+        public function NoSelection() {
+        }
+        override public function processMouseEvent(event:MouseEvent, entity:Entity):ControllerState {
+            var focus:Entity = getTopLevelFocusEntity(entity);
+            if ( event.type == MouseEvent.CLICK )
+                if ( focus is Way )
+                    return new SelectedWay(focus as Way);
+                else if ( focus is Node )
+                    trace("select poi");
+            else if ( event.type == MouseEvent.MOUSE_OVER )
+                controller.map.setHighlight(focus, "hover", true);
+            else if ( event.type == MouseEvent.MOUSE_OUT )
+                controller.map.setHighlight(focus, "hover", false);
+                
+            return this;
+        }
+        
+        public static function getTopLevelFocusEntity(entity:Entity):Entity {
+            if ( entity is Node ) {
+                for each (var parent:Entity in entity.parentWays) {
+                    return parent;
+                }
+                return entity;
+            } else if ( entity is Way ) {
+                return entity;
+            } else {
+                return null;
+            }
+        }
+    }
+}
diff --git a/net/systemeD/potlatch2/controller/SelectedWay.as b/net/systemeD/potlatch2/controller/SelectedWay.as
new file mode 100644 (file)
index 0000000..1af1a55
--- /dev/null
@@ -0,0 +1,83 @@
+package net.systemeD.potlatch2.controller {
+       import flash.events.*;
+    import net.systemeD.potlatch2.EditController;
+    import net.systemeD.halcyon.connection.*;
+
+    public class SelectedWay extends ControllerState {
+        private var selectedWay:Way;
+        private var initWay:Way;
+        
+        public function SelectedWay(way:Way) {
+            initWay = way;
+        }
+        protected function selectWay(way:Way):void {
+            if ( way == selectedWay )
+                return;
+
+            clearSelection();
+            controller.setTagViewer(way);
+            controller.map.setHighlight(way, "selected", true);
+            controller.map.setHighlight(way, "showNodes", true);
+            selectedWay = way;
+            initWay = way;
+        }
+        
+        protected function clearSelection():void {
+            if ( selectedWay != null ) {
+                controller.map.setHighlight(selectedWay, "selected", false);
+                controller.map.setHighlight(selectedWay, "showNodes", false);
+                controller.setTagViewer(null);
+                selectedWay = null;
+            }
+        }
+        
+        override public function processMouseEvent(event:MouseEvent, entity:Entity):ControllerState {
+            var focus:Entity = NoSelection.getTopLevelFocusEntity(entity);
+            if ( event.type == MouseEvent.CLICK ) {
+                if ( (entity is Node && entity.hasParent(selectedWay)) || focus == selectedWay )
+                    return clickOnWay(event, entity);
+                else if ( focus is Way )
+                    selectWay(focus as Way);
+                else if ( focus is Node )
+                    trace("select poi");
+                else if ( focus == null )
+                    return previousState;
+            } else if ( event.type == MouseEvent.MOUSE_DOWN ) {
+                if ( entity is Node && entity.hasParent(selectedWay) )
+                    return new DragWayNode(selectedWay, Node(entity), event);
+            }
+
+            return this;
+        }
+
+        public function clickOnWay(event:MouseEvent, entity:Entity):ControllerState {
+            if ( event.shiftKey ) {
+                if ( entity is Way )
+                    addNode(event);
+                else
+                    trace("start new way");
+            } else {
+                if ( entity is Node )
+                    trace("select way node");
+            }
+            
+            return this;
+        }
+        
+        public function addNode(event:MouseEvent):void {
+            trace("add node");
+            var lat:Number = controller.map.coord2lat(event.localY);
+            var lon:Number = controller.map.coord2lon(event.localX);
+            var node:Node = controller.connection.createNode({}, lat, lon);
+            selectedWay.insertNodeAtClosestPosition(node, true);
+        }
+        
+        override public function enterState():void {
+            selectWay(initWay);
+        }
+        override public function exitState():void {
+            clearSelection();
+        }
+    }
+}