don't break when you ctrl-click the background
[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;
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             way.addEventListener(Connection.WAY_NODE_REMOVED, fixElastic);
33             way.addEventListener(Connection.WAY_NODE_ADDED, fixElastic);
34                 }
35                 
36                 override public function processMouseEvent(event:MouseEvent, entity:Entity):ControllerState {
37                         var mouse:Point;
38                         var node:Node;
39                         var paint:MapPaint = getMapPaint(DisplayObject(event.target));
40                         var isBackground:Boolean = paint && paint.isBackground;
41
42                         if (entity == null && hoverEntity) { entity=hoverEntity; }
43                         var focus:Entity = getTopLevelFocusEntity(entity);
44
45                         if ( event.type == MouseEvent.MOUSE_UP ) {
46                 controller.map.mouseUpHandler(); // in case you're still in the drag-tolerance zone, and mouse up over something.
47                                 if ( entity == null || isBackground ) {
48                                         node = createAndAddNode(event, MainUndoStack.getGlobalStack().addAction);
49                     controller.map.setHighlight(node, { selectedway: true });
50                     controller.map.setPurgable([node], false);
51                                         resetElastic(node);
52                                         lastClick=node;
53                                 } else if ( entity is Node ) {
54                                         if (entity==lastClick && (new Date().getTime()-lastClickTime.getTime())<1000) {
55                                                 if (Way(firstSelected).length==1 && Way(firstSelected).getNode(0).parentWays.length==1) {
56                                                         // double-click to create new POI
57                             stopDrawing();
58                             MainUndoStack.getGlobalStack().undo(); // undo the BeginWayAction that (presumably?) just happened
59                             
60                             var newPoiAction:CreatePOIAction = new CreatePOIAction(
61                                                                 {},
62                                                                 controller.map.coord2lat(event.localY),
63                                                                 controller.map.coord2lon(event.localX));
64                             MainUndoStack.getGlobalStack().addAction(newPoiAction);
65                             return new SelectedPOINode(newPoiAction.getNode());
66                                                 } else {
67                                                         // double-click at end of way
68                                                         return stopDrawing();
69                                                 }
70                                         } else {
71                                                 appendNode(entity as Node, MainUndoStack.getGlobalStack().addAction);
72                                                 if (focus is Way) {
73                           controller.map.setHighlightOnNodes(focus as Way, { hoverway: false });
74                         }
75                                                 controller.map.setHighlight(entity, { selectedway: true });
76                                                 resetElastic(entity as Node);
77                                                 lastClick=entity;
78                                                 if (Way(firstSelected).getNode(0)==Way(firstSelected).getLastNode()) {
79                                                         return new SelectedWay(firstSelected as Way);
80                                                 }
81                                         }
82                                 } else if ( entity is Way ) {
83                                         if (entity==firstSelected) {
84                                                 // add junction node - self-intersecting way
85                                     var lat:Number = controller.map.coord2lat(event.localY);
86                                     var lon:Number = controller.map.coord2lon(event.localX);
87                                     var undo:CompositeUndoableAction = new CompositeUndoableAction("Insert node");
88                                     node = controller.connection.createNode({}, lat, lon, undo.push);
89                                     Way(firstSelected).insertNodeAtClosestPosition(node, true, undo.push);
90                                                 appendNode(node,undo.push);
91                                     MainUndoStack.getGlobalStack().addAction(undo);
92                                         } else {
93                         // add junction node - another way
94                         var jnct:CompositeUndoableAction = new CompositeUndoableAction("Junction Node");
95                         node = createAndAddNode(event, jnct.push);
96                         Way(entity).insertNodeAtClosestPosition(node, true, jnct.push);
97                         MainUndoStack.getGlobalStack().addAction(jnct);
98                         controller.map.setHighlight(node, { selectedway: true });
99                         controller.map.setPurgable([node], false);
100                                         }
101                                         resetElastic(node);
102                                         lastClick=node;
103                                         controller.map.setHighlightOnNodes(entity as Way, { hoverway: false });
104                                         controller.map.setHighlightOnNodes(firstSelected as Way, { selectedway: true });
105                                 }
106                                 lastClickTime=new Date();
107                         } else if ( event.type == MouseEvent.MOUSE_MOVE && elastic ) {
108                                 mouse = new Point(
109                                                   controller.map.coord2lon(event.localX),
110                                                   controller.map.coord2latp(event.localY));
111                                 elastic.end = mouse;
112                         } else if ( event.type == MouseEvent.ROLL_OVER && !isBackground ) {
113                                 if (focus is Way && focus!=firstSelected) {
114                                         hoverEntity=focus;
115                                         controller.map.setHighlightOnNodes(focus as Way, { hoverway: true });
116                                 }
117                                 if (entity is Node && focus is Way && Way(focus).endsWith(Node(entity))) {
118                                         if (focus==firstSelected) { controller.setCursor(controller.pen_so); }
119                                                              else { controller.setCursor(controller.pen_o); }
120                                 } else if (entity is Node) {
121                                         controller.setCursor(controller.pen_x);
122                                 } else {
123                                         controller.setCursor(controller.pen_plus);
124                                 }
125                         } else if ( event.type == MouseEvent.MOUSE_OUT && !isBackground ) {
126                                 if (focus is Way && entity!=firstSelected) {
127                                         hoverEntity=null;
128                                         controller.map.setHighlightOnNodes(focus as Way, { hoverway: false });
129                                         // ** We could do with an optional way of calling WayUI.redraw to only do the nodes, which would be a
130                                         // useful optimisation.
131                                 }
132                                 controller.setCursor(controller.pen);
133                         }
134
135                         return this;
136                 }
137                 
138                 protected function resetElastic(node:Node):void {
139                         var mouse:Point = new Point(node.lon, node.latp);
140                         elastic.start = mouse;
141                         elastic.end = mouse;
142                 }
143
144         /* Fix up the elastic after a WayNode event - e.g. triggered by undo */
145         private function fixElastic(event:Event):void {
146             if (firstSelected == null) return;
147             var node:Node;
148             if (editEnd) {
149               node = Way(firstSelected).getLastNode();
150             } else {
151               node = Way(firstSelected).getNode(0);
152             }
153             if (node) { //maybe selectedWay doesn't have any nodes left
154               elastic.start = new Point(node.lon, node.latp);
155             }
156         }
157
158                 override public function processKeyboardEvent(event:KeyboardEvent):ControllerState {
159                         switch (event.keyCode) {
160                                 case 13:                                        return keyExitDrawing();
161                                 case 27:                                        return keyExitDrawing();
162                                 case Keyboard.DELETE:           return backspaceNode(MainUndoStack.getGlobalStack().addAction);
163                                 case Keyboard.BACKSPACE:        return backspaceNode(MainUndoStack.getGlobalStack().addAction);
164                                 case 189:                                       return backspaceNode(MainUndoStack.getGlobalStack().addAction);
165                                 case 82:                                        repeatTags(firstSelected); return this;
166                         }
167                         var cs:ControllerState = sharedKeyboardEvents(event);
168                         return cs ? cs : this;
169                 }
170                 
171                 protected function keyExitDrawing():ControllerState {
172                         var cs:ControllerState=stopDrawing();
173                         if (selectedWay.length==1) { 
174                                 if (MainUndoStack.getGlobalStack().undoIfAction(BeginWayAction)) { 
175                                         return new NoSelection();
176                                 }
177                                 return deleteWay();
178                         }
179                         return cs;
180                 }
181                 
182                 protected function stopDrawing():ControllerState {
183                         if ( hoverEntity ) {
184                                 controller.map.setHighlightOnNodes(hoverEntity as Way, { hoverway: false });
185                                 hoverEntity = null;
186                         }
187
188                         if ( leaveNodeSelected ) {
189                             return new SelectedWayNode(firstSelected as Way, editEnd ? Way(firstSelected).length-1 : 0);
190                         } else {
191                             return new SelectedWay(firstSelected as Way);
192                         }
193                 }
194
195                 public function createAndAddNode(event:MouseEvent, performAction:Function):Node {
196                     var undo:CompositeUndoableAction = new CompositeUndoableAction("Add node");
197                     
198                         var lat:Number = controller.map.coord2lat(event.localY);
199                         var lon:Number = controller.map.coord2lon(event.localX);
200                         var node:Node = controller.connection.createNode({}, lat, lon, undo.push);
201                         appendNode(node, undo.push);
202                         
203                         performAction(undo);
204                         return node;
205                 }
206                 
207                 protected function appendNode(node:Node, performAction:Function):void {
208                         if ( editEnd )
209                                 Way(firstSelected).appendNode(node, performAction);
210                         else
211                                 Way(firstSelected).insertNode(0, node, performAction);
212                 }
213                 
214                 protected function backspaceNode(performAction:Function):ControllerState {
215                         var node:Node;
216                         var undo:CompositeUndoableAction = new CompositeUndoableAction("Remove node");
217                         var newDraw:int;
218             var state:ControllerState;
219
220                         if (editEnd) {
221                                 node=Way(firstSelected).getLastNode();
222                                 Way(firstSelected).removeNodeByIndex(Way(firstSelected).length-1, undo.push);
223                                 newDraw=Way(firstSelected).length-2;
224                         } else {
225                                 node=Way(firstSelected).getNode(0);
226                                 Way(firstSelected).removeNodeByIndex(0, undo.push);
227                                 newDraw=0;
228                         }
229                         if (node.numParentWays==1 && Way(firstSelected).hasOnceOnly(node)) {
230                                 controller.map.setPurgable([node], true);
231                                 controller.connection.unregisterPOI(node);
232                                 node.remove(undo.push);
233                         }
234
235                         if (newDraw>=0 && newDraw<=Way(firstSelected).length-2) {
236                                 var mouse:Point = new Point(Way(firstSelected).getNode(newDraw).lon, Way(firstSelected).getNode(newDraw).latp);
237                                 elastic.start = mouse;
238                                 state = this;
239                         } else {
240                 Way(firstSelected).remove(undo.push);
241                 state = new NoSelection();
242                         }
243
244             performAction(undo);
245
246             if(!node.isDeleted()) { // i.e. was junction with another way (or is now POI)
247               controller.map.setHighlight(node, {selectedway: false});
248             }
249             return state;
250                 }
251                 
252                 override public function enterState():void {
253                         super.enterState();
254                         
255                         var node:Node = Way(firstSelected).getNode(editEnd ? Way(firstSelected).length-1 : 0);
256                         var start:Point = new Point(node.lon, node.latp);
257                         elastic = new Elastic(controller.map, start, start);
258                         controller.setCursor(controller.pen);
259                         Globals.vars.root.addDebug("**** -> "+this);
260                 }
261                 override public function exitState(newState:ControllerState):void {
262                         super.exitState(newState);
263                         controller.setCursor(null);
264                         elastic.removeSprites();
265                         elastic = null;
266                         Globals.vars.root.addDebug("**** <- "+this);
267                 }
268                 override public function toString():String {
269                         return "DrawWay";
270                 }
271         }
272 }