ImproveWayAccuracy, basically
[potlatch2.git] / net / systemeD / potlatch2 / controller / SelectedWay.as
1 package net.systemeD.potlatch2.controller {
2         import flash.display.*;
3         import flash.events.*;
4         import flash.geom.Point;
5         import flash.ui.Keyboard;
6         
7         import net.systemeD.halcyon.MapPaint;
8         import net.systemeD.halcyon.connection.*;
9         import net.systemeD.potlatch2.tools.Quadrilateralise;
10         import net.systemeD.potlatch2.tools.Simplify;
11
12     /** Behaviour that takes place while a way is selected includes: adding a node to the way, straightening/reshaping the way, dragging it. */
13     public class SelectedWay extends ControllerState {
14         /** The selected way itself. */
15         protected var initWay:Way;
16         private var clicked:Point;              // did the user enter this state by clicking at a particular point?
17                 private var wayList:Array;              // list of ways to cycle through with '/' keypress
18                 private var initIndex: int;     // index of last selected node if entered from SelectedWayNode
19         
20         /** 
21         * @param way The way that is now selected.
22         * @param point The location that was clicked.
23         * @param ways An ordered list of ways sharing a node, to make "way cycling" work. */
24         public function SelectedWay(way:Way, layer:MapPaint=null, point:Point=null, ways:Array=null, index:int=0) {
25                         if (layer) this.layer=layer;
26             initWay = way;
27                         clicked = point;
28                         wayList = ways;
29                         initIndex=index;
30         }
31
32         private function updateSelectionUI(e:Event):void {
33             controller.updateSelectionUIWithoutTagChange();
34         }
35
36         /** Tidy up UI as we transition to a new state without the current selection. */
37         protected function clearSelection(newState:ControllerState):void {
38             if ( selectCount ) {
39                 layer.setHighlight(firstSelected, { selected: false, hover: false });
40                 layer.setHighlightOnNodes(firstSelected as Way, { selectedway: false });
41                 selection = [];
42                 if (!newState.isSelectionState()) { controller.updateSelectionUI(); }
43             }
44         }
45         
46         /** Behaviour includes: start drawing a new way, insert a node within this way, select an additional way */
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 paint:MapPaint = getMapPaint(DisplayObject(event.target));
50             var focus:Entity = getTopLevelFocusEntity(entity);
51
52             if ( event.type == MouseEvent.MOUSE_DOWN && entity is Node && focus==firstSelected && event.shiftKey && !layer.isBackground ) {
53                                 // start new way
54                                 var way:Way = entity.connection.createWay({}, [entity], MainUndoStack.getGlobalStack().addAction);
55                                 return new DrawWay(way, true, false);
56                         } else if ( event.type == MouseEvent.MOUSE_DOWN && entity is Way && focus==firstSelected && event.shiftKey && !layer.isBackground ) {
57                                 // shift-clicked way to insert node
58                                 var d:DragWayNode=new DragWayNode(firstSelected as Way, -1, event, true);
59                                 d.forceDragStart();
60                                 return d;
61                         } else if ( event.type == MouseEvent.MOUSE_DOWN && entity is Node && focus!=firstSelected && event.shiftKey && !layer.isBackground ) {
62                                 // shift-clicked POI node to insert it
63                                 Way(firstSelected).insertNodeAtClosestPosition(Node(entity), false, MainUndoStack.getGlobalStack().addAction);
64                                 return this;
65                         } else if ( event.type == MouseEvent.MOUSE_UP && !entity && event.shiftKey ) {
66                                 // shift-clicked nearby to insert node
67                                 var lat:Number = controller.map.coord2lat(event.localY);
68                                 var lon:Number = controller.map.coord2lon(event.localX);
69                                 var undo:CompositeUndoableAction = new CompositeUndoableAction("Insert node");
70                                 var node:Node = firstSelected.connection.createNode({}, lat, lon, undo.push);
71                                 Way(firstSelected).insertNodeAtClosestPosition(node, false, undo.push);
72                                 MainUndoStack.getGlobalStack().addAction(undo);
73                                 return this;
74                         } else if ( event.type == MouseEvent.MOUSE_DOWN && event.ctrlKey && !event.altKey && entity && entity!=firstSelected) {
75                                 // multiple selection
76                                 return new SelectedMultiple([firstSelected,entity],layer);
77                         } else if ( event.type == MouseEvent.MOUSE_UP && focus==firstSelected ) {
78                                 return this;
79                         }
80                         var cs:ControllerState = sharedMouseEvents(event, entity);
81                         return cs ? cs : this;
82         }
83         
84                 /** Behaviour includes: parallel way, repeat tags, reverse direction, simplify, cycle way selection, delete */
85                 override public function processKeyboardEvent(event:KeyboardEvent):ControllerState {
86                         switch (event.keyCode) {
87                                 case 82:  /* R */           { if (! event.shiftKey) repeatTags(firstSelected); 
88                                                               else                  repeatRelations(firstSelected);
89                                                               return this; }
90                                 case 191: /* / */           return cycleWays();
91                                 case Keyboard.BACKSPACE:        
92                                 case Keyboard.DELETE:           if (event.shiftKey) { return deleteWay(); } break;
93                         }
94                         if (!layer.isBackground) {
95                                 switch (event.keyCode) {
96                                         case 80:  /* P */       return new SelectedParallelWay(firstSelected as Way); 
97                                         case 81:  /* Q */       Quadrilateralise.quadrilateralise(firstSelected as Way, MainUndoStack.getGlobalStack().addAction); return this;
98                                         case 86:  /* V */       Way(firstSelected).reverseNodes(MainUndoStack.getGlobalStack().addAction); return this;
99                                         case 89:  /* Y */       Simplify.simplify(firstSelected as Way, controller.map, true); return this;
100                                         case 188: /* , */       return new SelectedWayNode(initWay, initIndex); // allows navigating from one way to another by keyboard
101                                         case 190: /* . */       return new SelectedWayNode(initWay, initIndex); //  using <, > and /           
102                                 }
103                         }
104                         var cs:ControllerState = sharedKeyboardEvents(event);
105                         return cs ? cs : this;
106                 }
107         
108                 private function cycleWays():ControllerState {
109                         if (!clicked || (wayList && wayList.length<2)) { return this; }
110
111                         if (!wayList) {
112                                 wayList=[initWay].concat(layer.findWaysAtPoint(clicked.x,clicked.y,initWay));
113                         }
114                         wayList=wayList.slice(1).concat(wayList[0]);
115                         // Find the new way's index of the currently "selected" node, to facilitate keyboard navigation
116                         var newindex:int = Way(wayList[0]).indexOfNode(initWay.getNode(initIndex));
117                         return new SelectedWay(wayList[0], layer, clicked, wayList, newindex);
118                 }
119
120                 /** Perform deletion of currently selected way. */
121                 public function deleteWay():ControllerState {
122                         layer.setHighlightOnNodes(firstSelected as Way, {selectedway: false});
123                         selectedWay.remove(MainUndoStack.getGlobalStack().addAction);
124                         return new NoSelection();
125                 }
126
127         /** Officially enter this state by marking the previously nominated way as selected. */
128         override public function enterState():void {
129             if (firstSelected!=initWay) {
130                     clearSelection(this);
131                     layer.setHighlight(initWay, { selected: true, hover: false });
132                     layer.setHighlightOnNodes(initWay, { selectedway: true });
133                     selection = [initWay];
134                     controller.updateSelectionUI();
135                     initWay.addEventListener(Connection.WAY_REORDERED, updateSelectionUI, false, 0, true);
136                         }
137                         layer.setPurgable(selection,false);
138         }
139         /** Officially leave the state, remembering the current way's tags and relations for future repeats. */
140         // TODO: tweak this so that repeat tags aren't remembered if you only select a way in order to branch off it. (a la PL1) 
141         override public function exitState(newState:ControllerState):void {
142                         if (firstSelected.hasTags()) {
143               controller.clipboards['way']=firstSelected.getTagsCopy();
144             }
145             copyRelations(firstSelected);
146                         layer.setPurgable(selection,true);
147             firstSelected.removeEventListener(Connection.WAY_REORDERED, updateSelectionUI);
148             clearSelection(newState);
149         }
150
151         /** @return "SelectedWay" */
152         override public function toString():String {
153             return "SelectedWay";
154         }
155
156     }
157 }