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.Globals;
8 import net.systemeD.halcyon.WayUI;
9 import net.systemeD.halcyon.connection.*;
10 import net.systemeD.halcyon.connection.actions.*;
11 import net.systemeD.potlatch2.tools.Quadrilateralise;
13 public class SelectedWayNode extends ControllerState {
14 private var parentWay:Way;
15 private var initIndex:int;
16 private var selectedIndex:int;
18 public function SelectedWayNode(way:Way,index:int) {
23 protected function selectNode(way:Way,index:int):void {
24 var node:Node=way.getNode(index);
25 if ( way == parentWay && node == firstSelected )
29 controller.map.setHighlight(way, { hover: false });
30 controller.map.setHighlight(node, { selected: true });
31 controller.map.setHighlightOnNodes(way, { selectedway: true });
32 selection = [node]; parentWay = way;
33 controller.updateSelectionUI();
34 selectedIndex = index; initIndex = index;
37 protected function clearSelection(newState:ControllerState):void {
39 controller.map.setHighlight(parentWay, { selected: false });
40 controller.map.setHighlight(firstSelected, { selected: false });
41 controller.map.setHighlightOnNodes(parentWay, { selectedway: false });
43 if (!newState.isSelectionState()) { controller.updateSelectionUI(); }
47 override public function processMouseEvent(event:MouseEvent, entity:Entity):ControllerState {
48 if (event.type==MouseEvent.MOUSE_MOVE || event.type==MouseEvent.ROLL_OVER || event.type==MouseEvent.MOUSE_OUT) { return this; }
49 var focus:Entity = getTopLevelFocusEntity(entity);
51 if ( event.type == MouseEvent.MOUSE_UP && entity is Node && event.shiftKey ) {
53 var way:Way = controller.connection.createWay({}, [entity],
54 MainUndoStack.getGlobalStack().addAction);
55 return new DrawWay(way, true, false);
56 } else if ( event.type == MouseEvent.MOUSE_UP && entity is Node && focus == parentWay ) {
57 // select node within way
58 return selectOrEdit(parentWay, getNodeIndex(parentWay,Node(entity)));
59 } else if ( event.type == MouseEvent.MOUSE_DOWN && entity is Way && focus==parentWay && event.shiftKey) {
60 // insert node within way (shift-click)
61 var d:DragWayNode=new DragWayNode(parentWay, -1, event, true);
65 var cs:ControllerState = sharedMouseEvents(event, entity);
66 return cs ? cs : this;
69 override public function processKeyboardEvent(event:KeyboardEvent):ControllerState {
70 switch (event.keyCode) {
71 case 189: return removeNode(); // '-'
72 case 88: return splitWay(); // 'X'
73 case 81: /* Q */ Quadrilateralise.quadrilateralise(parentWay, MainUndoStack.getGlobalStack().addAction); return this;
74 case 82: repeatTags(firstSelected); return this; // 'R'
75 case 87: return new SelectedWay(parentWay); // 'W'
76 case 191: return cycleWays(); // '/'
77 case 74: if (event.shiftKey) { return unjoin() }; return join();// 'J'
78 case Keyboard.BACKSPACE: return deleteNode();
79 case Keyboard.DELETE: return deleteNode();
81 var cs:ControllerState = sharedKeyboardEvents(event);
82 return cs ? cs : this;
85 override public function get selectedWay():Way {
89 public function get selectedNode():Node {
90 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 return new SelectedWay(wayList[0],
99 new Point(controller.map.lon2coord(Node(firstSelected).lon),
100 controller.map.latp2coord(Node(firstSelected).latp)),
101 wayList.concat(parentWay));
104 override public function enterState():void {
105 selectNode(parentWay,initIndex);
106 controller.map.setPurgable(selection,false);
107 Globals.vars.root.addDebug("**** -> "+this);
109 override public function exitState(newState:ControllerState):void {
110 if (firstSelected.hasTags()) {
111 controller.clipboards['node']=firstSelected.getTagsCopy();
113 controller.map.setPurgable(selection,true);
114 clearSelection(newState);
115 Globals.vars.root.addDebug("**** <- "+this);
118 override public function toString():String {
119 return "SelectedWayNode";
122 public static function selectOrEdit(selectedWay:Way, index:int):ControllerState {
123 var isFirst:Boolean = false;
124 var isLast:Boolean = false;
125 var node:Node = selectedWay.getNode(index);
126 isFirst = selectedWay.getNode(0) == node;
127 isLast = selectedWay.getLastNode() == node;
128 if ( isFirst == isLast ) // both == looped, none == central node
129 return new SelectedWayNode(selectedWay, index);
131 return new DrawWay(selectedWay, isLast, true);
134 /** Splits a way into two separate ways, at the currently selected node. Handles simple loops and P-shapes. Untested for anything funkier. */
135 public function splitWay():ControllerState {
136 var n:Node=firstSelected as Node;
137 var ni:uint = parentWay.indexOfNode(n);
138 // abort if start or end
139 if (parentWay.isPShape() && !parentWay.hasOnceOnly(n)) {
140 // If P-shaped, we want to split at the midway point on the stem, not at the end of the loop
141 ni = parentWay.getPJunctionNodeIndex();
144 if (parentWay.getNode(0) == n) { return this; }
145 if (parentWay.getLastNode() == n) { return this; }
148 controller.map.setHighlightOnNodes(parentWay, { selectedway: false } );
149 controller.map.setPurgable([parentWay],true);
150 MainUndoStack.getGlobalStack().addAction(new SplitWayAction(parentWay, ni));
152 return new SelectedWay(parentWay);
155 public function removeNode():ControllerState {
156 if (firstSelected.numParentWays==1 && parentWay.hasOnceOnly(firstSelected as Node) && !(firstSelected as Node).hasInterestingTags()) {
159 parentWay.removeNodeByIndex(selectedIndex, MainUndoStack.getGlobalStack().addAction);
160 return new SelectedWay(parentWay);
163 public function deleteNode():ControllerState {
164 controller.map.setPurgable(selection,true);
165 firstSelected.remove(MainUndoStack.getGlobalStack().addAction);
166 return new SelectedWay(parentWay);
169 public function unjoin():ControllerState {
170 Node(firstSelected).unjoin(parentWay, MainUndoStack.getGlobalStack().addAction);
174 /** Attempt to either merge the currently selected node with another very nearby node, or failing that,
175 * attach it mid-way along a very nearby way. */
176 public function join():ControllerState {
177 var p:Point = new Point(controller.map.lon2coord(Node(firstSelected).lon),
178 controller.map.latp2coord(Node(firstSelected).latp));
179 var q:Point = map.localToGlobal(p);
181 // First, look for POI nodes in 20x20 pixel box around the current node
182 var hitnodes:Array = map.connection.getObjectsByBbox(
183 map.coord2lon(p.x-10),
184 map.coord2lon(p.x+10),
185 map.coord2lat(p.y-10),
186 map.coord2lat(p.y+10)).poisInside;
188 for each (var n: Node in hitnodes) {
189 if (!n.hasParent(selectedWay)) {
190 return doMergeNodes(n);
194 var ways:Array=[]; var w:Way;
195 for each (var wayui:WayUI in controller.map.paint.wayuis) {
196 w=wayui.hitTest(q.x, q.y);
197 if (w && w!=selectedWay) {
198 // hit a way, now let's see if we hit a specific node
199 for (var i:uint = 0; i < w.length; i++) {
201 var x:Number = map.lon2coord(n.lon);
202 var y:Number = map.latp2coord(n.latp);
203 if (n != selectedNode && Math.abs(x-p.x) + Math.abs(y-p.y) < 10) {
204 return doMergeNodes(n);
211 // No nodes hit, so join our node onto any overlapping ways.
212 Node(firstSelected).join(ways,MainUndoStack.getGlobalStack().addAction);
216 private function doMergeNodes(n:Node): ControllerState {
217 n.mergeWith(Node(firstSelected), MainUndoStack.getGlobalStack().addAction);
218 // only merge one node at a time - too confusing otherwise?
219 var msg:String = "Nodes merged."
220 if (MergeNodesAction.lastProblemTags) {
221 msg += " *Warning* The following tags conflicted and need attention: " + MergeNodesAction.lastProblemTags;
223 map.connection.dispatchEvent(new AttentionEvent(AttentionEvent.ALERT, null, msg));
224 return new SelectedWayNode(n.parentWays[0], Way(n.parentWays[0]).indexOfNode(n));