0e7aec2d923a0f4a235ab8c062bcb27160a5c5ca
[potlatch2.git] / net / systemeD / potlatch2 / controller / DrawWay.as
1 package net.systemeD.potlatch2.controller {
2         import flash.events.*;
3         import flash.geom.*;
4         import flash.display.DisplayObject;
5         import flash.ui.Keyboard;
6         import net.systemeD.potlatch2.EditController;
7         import net.systemeD.halcyon.connection.*;
8     import net.systemeD.halcyon.connection.actions.*;
9         import net.systemeD.halcyon.Elastic;
10         import net.systemeD.halcyon.Globals;
11         import net.systemeD.halcyon.MapPaint;
12
13         public class DrawWay extends SelectedWay {
14                 private var elastic:Elastic;
15                 private var editEnd:Boolean;            // if true, we're drawing from node[n-1], else "backwards" from node[0] 
16                 private var leaveNodeSelected:Boolean;
17                 private var lastClick:Entity=null;
18                 private var lastClickTime:Date;
19                 private var hoverEntity:Entity;                 // keep track of the currently rolled-over object, because
20                                                                                                 // Flash can fire a mouseDown from the map even if you
21                                                                                                 // haven't rolled out of the way
22                 
23                 public function DrawWay(way:Way, editEnd:Boolean, leaveNodeSelected:Boolean) {
24                         super(way);
25                         this.editEnd = editEnd;
26                         this.leaveNodeSelected = leaveNodeSelected;
27                         if (way.length==1 && way.getNode(0).parentWays.length==1) {
28                                 // drawing new way, so keep track of click in case creating a POI
29                                 lastClick=way.getNode(0);
30                                 lastClickTime=new Date();
31                         }
32                 }
33                 
34                 override public function processMouseEvent(event:MouseEvent, entity:Entity):ControllerState {
35                         var mouse:Point;
36                         var node:Node;
37                         var paint:MapPaint = getMapPaint(DisplayObject(event.target));
38                         var isBackground:Boolean = paint && paint.isBackground;
39
40                         if (entity == null && hoverEntity) { entity=hoverEntity; }
41                         var focus:Entity = getTopLevelFocusEntity(entity);
42
43                         if ( event.type == MouseEvent.MOUSE_UP ) {
44                 controller.map.mouseUpHandler(); // in case you're still in the drag-tolerance zone, and mouse up over something.
45                                 if ( entity == null || isBackground ) { // didn't hit anything: extend the way by one node.
46                                         node = createAndAddNode(event, MainUndoStack.getGlobalStack().addAction);
47                     controller.map.setHighlight(node, { selectedway: true });
48                     controller.map.setPurgable([node], false);
49                                         resetElastic(node);
50                                         lastClick=node;
51                                 } else if ( entity is Node ) {
52                                         if (entity==lastClick && (new Date().getTime()-lastClickTime.getTime())<1000) {
53                                                 if (Way(firstSelected).length==1 && Way(firstSelected).getNode(0).parentWays.length==1) {
54                                                         // Actually the user double-clicked to make a new node, they didn't want to draw a way at all.
55                             stopDrawing();
56                             MainUndoStack.getGlobalStack().undo(); // undo the BeginWayAction that (presumably?) just happened
57                             
58                             var newPoiAction:CreatePOIAction = new CreatePOIAction(
59                                                                 {},
60                                                                 controller.map.coord2lat(event.localY),
61                                                                 controller.map.coord2lon(event.localX));
62                             MainUndoStack.getGlobalStack().addAction(newPoiAction);
63                             return new SelectedPOINode(newPoiAction.getNode());
64                                                 } else {
65                                                         // double-click at end of way
66                                                         return stopDrawing();
67                                                 }
68                     } else if (entity==lastClick) {
69                         // clicked slowly on the end node - do nothing
70                         return this;
71                                         } else {
72                                                 // hit a node, add it to this way and carry on
73                                                 appendNode(entity as Node, MainUndoStack.getGlobalStack().addAction);
74                                                 if (focus is Way) {
75                           controller.map.setHighlightOnNodes(focus as Way, { hoverway: false });
76                         }
77                                                 controller.map.setHighlight(entity, { selectedway: true });
78                                                 resetElastic(entity as Node);
79                                                 lastClick=entity;
80                                                 if (Way(firstSelected).getNode(0)==Way(firstSelected).getLastNode()) {
81                                                         // the node just hit completes a loop, so stop drawing.
82                                                         return new SelectedWay(firstSelected as Way);
83                                                 }
84                                         }
85                                 } else if ( entity is Way ) {
86                                         if (entity==firstSelected) {
87                                                 // add junction node - self-intersecting way
88                                     var lat:Number = controller.map.coord2lat(event.localY);
89                                     var lon:Number = controller.map.coord2lon(event.localX);
90                                     var undo:CompositeUndoableAction = new CompositeUndoableAction("Insert node");
91                                     node = controller.connection.createNode({}, lat, lon, undo.push);
92                                     Way(firstSelected).insertNodeAtClosestPosition(node, true, undo.push);
93                                                 appendNode(node,undo.push);
94                                     MainUndoStack.getGlobalStack().addAction(undo);
95                                         } else {
96                         // add junction node - another way
97                         var jnct:CompositeUndoableAction = new CompositeUndoableAction("Junction Node");
98                         node = createAndAddNode(event, jnct.push);
99                         Way(entity).insertNodeAtClosestPosition(node, true, jnct.push);
100                         MainUndoStack.getGlobalStack().addAction(jnct);
101                         controller.map.setHighlight(node, { selectedway: true });
102                         controller.map.setPurgable([node], false);
103                                         }
104                                         resetElastic(node);
105                                         lastClick=node;
106                                         controller.map.setHighlightOnNodes(entity as Way, { hoverway: false });
107                                         controller.map.setHighlightOnNodes(firstSelected as Way, { selectedway: true });
108                                 }
109                                 lastClickTime=new Date();
110                         } else if ( event.type == MouseEvent.MOUSE_MOVE && elastic ) {
111                                 // mouse is roaming around freely
112                                 mouse = new Point(
113                                                   controller.map.coord2lon(event.localX),
114                                                   controller.map.coord2latp(event.localY));
115                                 elastic.end = mouse;
116                         } else if ( event.type == MouseEvent.ROLL_OVER && !isBackground ) {
117                                 // mouse has floated over something
118                                 if (focus is Way && focus!=firstSelected) {
119                                         // floating over another way, highlight its nodes
120                                         hoverEntity=focus;
121                                         controller.map.setHighlightOnNodes(focus as Way, { hoverway: true });
122                                 }
123                                 // set cursor depending on whether we're floating over the start of this way, 
124                                 // another random node, a possible junction...
125                                 if (entity is Node && focus is Way && Way(focus).endsWith(Node(entity))) {
126                                         if (focus==firstSelected) { controller.setCursor(controller.pen_so); }
127                                                              else { controller.setCursor(controller.pen_o); }
128                                 } else if (entity is Node) {
129                                         controller.setCursor(controller.pen_x);
130                                 } else {
131                                         controller.setCursor(controller.pen_plus);
132                                 }
133                         } else if ( event.type == MouseEvent.MOUSE_OUT && !isBackground ) {
134                                 if (focus is Way && entity!=firstSelected) {
135                                         hoverEntity=null;
136                                         controller.map.setHighlightOnNodes(focus as Way, { hoverway: false });
137                                         // ** We could do with an optional way of calling WayUI.redraw to only do the nodes, which would be a
138                                         // useful optimisation.
139                                 }
140                                 controller.setCursor(controller.pen);
141                         }
142
143                         return this;
144                 }
145                 
146                 protected function resetElastic(node:Node):void {
147                         var mouse:Point = new Point(node.lon, node.latp);
148                         elastic.start = mouse;
149                         elastic.end = mouse;
150                 }
151
152         /* Fix up the elastic after a WayNode event - e.g. triggered by undo */
153         private function fixElastic(event:Event):void {
154             if (firstSelected == null) return;
155             var node:Node;
156             if (editEnd) {
157               node = Way(firstSelected).getLastNode();
158             } else {
159               node = Way(firstSelected).getNode(0);
160             }
161             if (node) { //maybe selectedWay doesn't have any nodes left
162               elastic.start = new Point(node.lon, node.latp);
163             }
164         }
165
166                 override public function processKeyboardEvent(event:KeyboardEvent):ControllerState {
167                         switch (event.keyCode) {
168                                 case Keyboard.ENTER:                                    return keyExitDrawing();
169                                 case Keyboard.ESCAPE:                                   return keyExitDrawing();
170                                 //case 90: /* Z */             
171                                 case Keyboard.DELETE:           
172                                 case Keyboard.BACKSPACE:        
173                                     
174                                 case 189: /* minus */       return backspaceNode(MainUndoStack.getGlobalStack().addAction);
175                                 case 82: /* R */            repeatTags(firstSelected); return this;
176                         }
177                         var cs:ControllerState = sharedKeyboardEvents(event);
178                         //if (selectedWay.length == 0) return stopDrawing(); // to catch 'undo'ing the start of a draw.
179                         return cs ? cs : this;7
180                         
181                 }
182                 
183                 protected function keyExitDrawing():ControllerState {
184                         var cs:ControllerState=stopDrawing();
185                         if (selectedWay.length==1) { 
186                                 if (MainUndoStack.getGlobalStack().undoIfAction(BeginWayAction)) { 
187                                         return new NoSelection();
188                                 }
189                                 return deleteWay();
190                         }
191                         return cs;
192                 }
193                 
194                 protected function stopDrawing():ControllerState {
195                         if ( hoverEntity ) {
196                                 controller.map.setHighlightOnNodes(hoverEntity as Way, { hoverway: false });
197                                 hoverEntity = null;
198                         }
199
200                         if ( leaveNodeSelected ) {
201                             return new SelectedWayNode(firstSelected as Way, editEnd ? Way(firstSelected).length-1 : 0);
202                         } else {
203                             return new SelectedWay(firstSelected as Way);
204                         }
205                 }
206
207                 public function createAndAddNode(event:MouseEvent, performAction:Function):Node {
208                     var undo:CompositeUndoableAction = new CompositeUndoableAction("Add node");
209                     
210                         var lat:Number = controller.map.coord2lat(event.localY);
211                         var lon:Number = controller.map.coord2lon(event.localX);
212                         var node:Node = controller.connection.createNode({}, lat, lon, undo.push);
213                         appendNode(node, undo.push);
214                         
215                         performAction(undo);
216                         return node;
217                 }
218                 
219                 protected function appendNode(node:Node, performAction:Function):void {
220                         if ( editEnd )
221                                 Way(firstSelected).appendNode(node, performAction);
222                         else
223                                 Way(firstSelected).insertNode(0, node, performAction);
224                 }
225                 
226                 protected function backspaceNode(performAction:Function):ControllerState {
227                         if (selectedWay.length==1) return keyExitDrawing();
228
229                         var node:Node;
230                         var undo:CompositeUndoableAction = new CompositeUndoableAction("Remove node");
231                         var newDraw:int;
232             var state:ControllerState;
233
234                         if (editEnd) {
235                                 node=Way(firstSelected).getLastNode();
236                                 Way(firstSelected).removeNodeByIndex(Way(firstSelected).length-1, undo.push);
237                                 newDraw=Way(firstSelected).length-2;
238                         } else {
239                                 node=Way(firstSelected).getNode(0);
240                                 Way(firstSelected).removeNodeByIndex(0, undo.push);
241                                 newDraw=0;
242                         }
243                         // Only actually delete the node if it has no other tags, and is not part of other ways (or part of this way twice)
244                         if (node.numParentWays==1 && Way(firstSelected).hasOnceOnly(node) && !node.hasInterestingTags()) {
245                                 controller.map.setPurgable([node], true);
246                                 controller.connection.unregisterPOI(node);
247                                 node.remove(undo.push);
248                         }
249
250                         if (newDraw>=0 && newDraw<=Way(firstSelected).length-2) {
251                                 var mouse:Point = new Point(Way(firstSelected).getNode(newDraw).lon, Way(firstSelected).getNode(newDraw).latp);
252                                 elastic.start = mouse;
253                                 state = this;
254                         } else {
255                 Way(firstSelected).remove(undo.push);
256                 state = new NoSelection();
257                         }
258
259             performAction(undo);
260
261             if(!node.isDeleted()) { // i.e. was junction with another way (or is now POI)
262               controller.map.setHighlight(node, {selectedway: false});
263             }
264             return state;
265                 }
266                 
267                 override public function enterState():void {
268                         super.enterState();
269                         
270             Way(firstSelected).addEventListener(Connection.WAY_NODE_REMOVED, fixElastic);
271             Way(firstSelected).addEventListener(Connection.WAY_NODE_ADDED, fixElastic);
272
273                         var node:Node = Way(firstSelected).getNode(editEnd ? Way(firstSelected).length-1 : 0);
274                         var start:Point = new Point(node.lon, node.latp);
275                         elastic = new Elastic(controller.map, start, start);
276                         controller.setCursor(controller.pen);
277                         Globals.vars.root.addDebug("**** -> "+this);
278                 }
279                 override public function exitState(newState:ControllerState):void {
280             Way(firstSelected).removeEventListener(Connection.WAY_NODE_REMOVED, fixElastic);
281             Way(firstSelected).removeEventListener(Connection.WAY_NODE_ADDED, fixElastic);
282
283                         super.exitState(newState);
284                         controller.setCursor(null);
285                         elastic.removeSprites();
286                         elastic = null;
287                         Globals.vars.root.addDebug("**** <- "+this);
288                 }
289                 override public function toString():String {
290                         return "DrawWay";
291                 }
292         }
293 }