1 package net.systemeD.potlatch2.controller {
3 import flash.geom.Point;
4 import flash.ui.Keyboard;
6 import net.systemeD.halcyon.AttentionEvent;
7 import net.systemeD.halcyon.WayUI;
8 import net.systemeD.halcyon.connection.*;
9 import net.systemeD.halcyon.connection.actions.*;
10 import net.systemeD.potlatch2.tools.Quadrilateralise;
12 public class SelectedWayNode extends ControllerState {
13 private var parentWay:Way;
14 private var initIndex:int;
15 private var selectedIndex:int;
17 public function SelectedWayNode(way:Way,index:int) {
22 protected function selectNode(way:Way,index:int):void {
23 var node:Node=way.getNode(index);
24 if ( way == parentWay && node == firstSelected )
28 editableLayer.setHighlight(way, { hover: false });
29 editableLayer.setHighlight(node, { selected: true });
30 editableLayer.setHighlightOnNodes(way, { selectedway: true });
31 selection = [node]; parentWay = way;
32 controller.updateSelectionUI();
33 selectedIndex = index; initIndex = index;
36 protected function clearSelection(newState:ControllerState):void {
38 editableLayer.setHighlight(parentWay, { selected: false });
39 editableLayer.setHighlight(firstSelected, { selected: false });
40 editableLayer.setHighlightOnNodes(parentWay, { selectedway: false });
42 if (!newState.isSelectionState()) { controller.updateSelectionUI(); }
46 override public function processMouseEvent(event:MouseEvent, entity:Entity):ControllerState {
47 if (event.type==MouseEvent.MOUSE_MOVE || event.type==MouseEvent.ROLL_OVER || event.type==MouseEvent.MOUSE_OUT) { return this; }
48 var focus:Entity = getTopLevelFocusEntity(entity);
50 if ( event.type == MouseEvent.MOUSE_UP && entity is Node && event.shiftKey ) {
52 var way:Way = controller.connection.createWay({}, [entity],
53 MainUndoStack.getGlobalStack().addAction);
54 return new DrawWay(way, true, false);
55 } else if ( event.type == MouseEvent.MOUSE_UP && entity is Node && focus == parentWay ) {
56 // select node within way
57 return selectOrEdit(parentWay, getNodeIndex(parentWay,Node(entity)));
58 } else if ( event.type == MouseEvent.MOUSE_DOWN && entity is Way && focus==parentWay && event.shiftKey) {
59 // insert node within way (shift-click)
60 var d:DragWayNode=new DragWayNode(parentWay, -1, event, true);
64 var cs:ControllerState = sharedMouseEvents(event, entity);
65 return cs ? cs : this;
68 override public function processKeyboardEvent(event:KeyboardEvent):ControllerState {
69 switch (event.keyCode) {
70 case 189: return removeNode(); // '-'
71 case 88: return splitWay(); // 'X'
72 case 81: /* Q */ Quadrilateralise.quadrilateralise(parentWay, MainUndoStack.getGlobalStack().addAction); return this;
73 case 82: repeatTags(firstSelected); return this; // 'R'
74 case 87: return new SelectedWay(parentWay); // 'W'
75 case 191: return cycleWays(); // '/'
76 case 74: if (event.shiftKey) { return unjoin() }; return join();// 'J'
77 case Keyboard.BACKSPACE: return deleteNode();
78 case Keyboard.DELETE: return deleteNode();
79 case 188: /* , */ return stepNode(-1);
80 case 190: /* . */ return stepNode(+1);
82 var cs:ControllerState = sharedKeyboardEvents(event);
83 return cs ? cs : this;
86 override public function get selectedWay():Way {
90 public function get selectedNode():Node {
91 return parentWay.getNode(selectedIndex);
94 private function cycleWays():ControllerState {
95 var wayList:Array=firstSelected.parentWays;
96 if (wayList.length==1) { return this; }
97 wayList.splice(wayList.indexOf(parentWay),1);
98 // find index of this node in the newly selected way, to maintain state for keyboard navigation
99 var newindex:int = Way(wayList[0]).indexOfNode(parentWay.getNode(initIndex));
100 return new SelectedWay(wayList[0],
101 new Point(controller.map.lon2coord(Node(firstSelected).lon),
102 controller.map.latp2coord(Node(firstSelected).latp)),
103 wayList.concat(parentWay),
107 override public function enterState():void {
108 selectNode(parentWay,initIndex);
109 editableLayer.setPurgable(selection,false);
111 override public function exitState(newState:ControllerState):void {
112 if (firstSelected.hasTags()) {
113 controller.clipboards['node']=firstSelected.getTagsCopy();
115 editableLayer.setPurgable(selection,true);
116 clearSelection(newState);
119 override public function toString():String {
120 return "SelectedWayNode";
123 public static function selectOrEdit(selectedWay:Way, index:int):ControllerState {
124 var isFirst:Boolean = false;
125 var isLast:Boolean = false;
126 var node:Node = selectedWay.getNode(index);
127 isFirst = selectedWay.getNode(0) == node;
128 isLast = selectedWay.getLastNode() == node;
129 if ( isFirst == isLast ) // both == looped, none == central node
130 return new SelectedWayNode(selectedWay, index);
132 return new DrawWay(selectedWay, isLast, true);
135 /** Splits a way into two separate ways, at the currently selected node. Handles simple loops and P-shapes. Untested for anything funkier. */
136 public function splitWay():ControllerState {
137 var n:Node=firstSelected as Node;
138 var ni:uint = parentWay.indexOfNode(n);
139 // abort if start or end
140 if (parentWay.isPShape() && !parentWay.hasOnceOnly(n)) {
141 // If P-shaped, we want to split at the midway point on the stem, not at the end of the loop
142 ni = parentWay.getPJunctionNodeIndex();
145 if (parentWay.getNode(0) == n) { return this; }
146 if (parentWay.getLastNode() == n) { return this; }
149 editableLayer.setHighlightOnNodes(parentWay, { selectedway: false } );
150 editableLayer.setPurgable([parentWay],true);
151 MainUndoStack.getGlobalStack().addAction(new SplitWayAction(parentWay, ni));
153 return new SelectedWay(parentWay);
156 public function removeNode():ControllerState {
157 if (firstSelected.numParentWays==1 && parentWay.hasOnceOnly(firstSelected as Node) && !(firstSelected as Node).hasInterestingTags()) {
160 parentWay.removeNodeByIndex(selectedIndex, MainUndoStack.getGlobalStack().addAction);
161 return new SelectedWay(parentWay);
164 public function deleteNode():ControllerState {
165 editableLayer.setPurgable(selection,true);
166 firstSelected.remove(MainUndoStack.getGlobalStack().addAction);
167 return new SelectedWay(parentWay);
170 public function unjoin():ControllerState {
171 Node(firstSelected).unjoin(parentWay, MainUndoStack.getGlobalStack().addAction);
175 /** Attempt to either merge the currently selected node with another very nearby node, or failing that,
176 * attach it mid-way along a very nearby way. */
177 public function join():ControllerState {
178 var p:Point = new Point(controller.map.lon2coord(Node(firstSelected).lon),
179 controller.map.latp2coord(Node(firstSelected).latp));
180 var q:Point = map.localToGlobal(p);
182 // First, look for POI nodes in 20x20 pixel box around the current node
183 var hitnodes:Array = controller.connection.getObjectsByBbox(
184 map.coord2lon(p.x-10),
185 map.coord2lon(p.x+10),
186 map.coord2lat(p.y-10),
187 map.coord2lat(p.y+10)).poisInside;
189 for each (var n: Node in hitnodes) {
190 if (!n.hasParent(selectedWay)) {
191 return doMergeNodes(n);
195 var ways:Array=[]; var w:Way;
196 for each (var wayui:WayUI in editableLayer.wayuis) {
197 w=wayui.hitTest(q.x, q.y);
198 if (w && w!=selectedWay) {
199 // hit a way, now let's see if we hit a specific node
200 for (var i:uint = 0; i < w.length; i++) {
202 var x:Number = map.lon2coord(n.lon);
203 var y:Number = map.latp2coord(n.latp);
204 if (n != selectedNode && Math.abs(x-p.x) + Math.abs(y-p.y) < 10) {
205 return doMergeNodes(n);
212 // No nodes hit, so join our node onto any overlapping ways.
213 Node(firstSelected).join(ways,MainUndoStack.getGlobalStack().addAction);
217 private function doMergeNodes(n:Node): ControllerState {
218 n.mergeWith(Node(firstSelected), MainUndoStack.getGlobalStack().addAction);
219 // only merge one node at a time - too confusing otherwise?
220 var msg:String = "Nodes merged."
221 if (MergeNodesAction.lastProblemTags) {
222 msg += " *Warning* The following tags conflicted and need attention: " + MergeNodesAction.lastProblemTags;
224 // somethingorother.dispatchEvent(new AttentionEvent(AttentionEvent.ALERT, null, msg));
225 return new SelectedWayNode(n.parentWays[0], Way(n.parentWays[0]).indexOfNode(n));
228 /** Move the selection one node further up or down this way, looping if necessary. */
229 public function stepNode(delta:int):ControllerState {
230 var ni:int = (selectedIndex + delta + parentWay.length) % parentWay.length
231 controller.map.scrollIfNeeded(parentWay.getNode(ni).lat,parentWay.getNode(ni).lon);
232 return new SelectedWayNode(parentWay, ni);