5cb00fbd0d1b80d1d02c67ad6a0b94e10504b296
[potlatch2.git] / net / systemeD / potlatch2 / EditController.as
1 package net.systemeD.potlatch2 {
2     import net.systemeD.halcyon.Map;
3     import net.systemeD.halcyon.MapPaint;
4     import net.systemeD.halcyon.MapController;
5     import net.systemeD.halcyon.MapEvent;
6     import net.systemeD.halcyon.connection.*;
7     import net.systemeD.halcyon.Globals;
8     import net.systemeD.potlatch2.controller.*;
9     import net.systemeD.potlatch2.FunctionKeyManager;
10         import mx.managers.CursorManager;
11     import flash.external.ExternalInterface;
12     import flash.events.*;
13         import flash.geom.*;
14         import flash.display.*;
15         import flash.ui.Keyboard;
16         import flash.ui.Mouse;
17         import flash.ui.MouseCursorData;
18         import flash.system.Capabilities;
19         import flash.text.TextField;
20     import mx.controls.TextArea;
21
22     /** Controller for the main map editing window itself. The logic that responds to mouse and keyboard events is all 
23     * buried in various ControllerState classes. */
24     public class EditController extends EventDispatcher implements MapController {
25
26         private var _map:Map;
27         public var tagViewer:TagViewer;
28                 private var toolbox:Toolbox;
29
30         /** The current ControllerState */
31         public var state:ControllerState;
32         
33                 /** Hash of when a key was pressed. A user can keyDown within a TextInput, press Enter (leaving
34                     the TextInput), and then keyup - resulting in the keypress being interpreted again. 
35                     We prevent this by tracking keyDowns within the TextInput and ignoring corresponding keyUps. */
36                 private var keys:Object={};
37
38                 public var spaceHeld:Boolean=false;
39                 public var clipboards:Object={};
40                 public var cursorsEnabled:Boolean=true;
41         private var maximised:Boolean=false;
42         private var maximiseFunction:String;
43         private var minimiseFunction:String;
44         private var moveFunction:String;
45
46                 [Embed(source="../../../embedded/pen.png")]             public var pen:Class;
47                 [Embed(source="../../../embedded/pen_x.png")]           public var pen_x:Class;
48                 [Embed(source="../../../embedded/pen_o.png")]           public var pen_o:Class;
49                 [Embed(source="../../../embedded/pen_so.png")]          public var pen_so:Class;
50                 [Embed(source="../../../embedded/pen_plus.png")]        public var pen_plus:Class;
51                 
52         /** Constructor function: needs the map information, a panel to edit tags with, and the toolbox to manipulate ways with. */
53         public function EditController(map:Map, tagViewer:TagViewer, toolbox:Toolbox) {
54             this._map = map;
55             setState(new NoSelection());
56             this.tagViewer = tagViewer;
57             this.tagViewer.controller = this;
58                         this.toolbox = toolbox;
59                         this.toolbox.init(this);
60                         this.toolbox.updateSelectionUI();
61             this.maximiseFunction = Globals.vars.flashvars["maximise_function"];
62             this.minimiseFunction = Globals.vars.flashvars["minimise_function"];
63             this.moveFunction     = Globals.vars.flashvars["move_function"];
64
65             map.parent.addEventListener(MouseEvent.MOUSE_MOVE, mapMouseEvent);
66             map.parent.addEventListener(MouseEvent.MOUSE_UP, mapMouseEvent);
67             map.parent.addEventListener(MouseEvent.MOUSE_DOWN, mapMouseEvent);
68             map.parent.addEventListener(MouseEvent.MOUSE_WHEEL, mapMouseEvent);
69             map.parent.addEventListener(MouseEvent.CLICK, mapMouseEvent);
70             map.stage.addEventListener(KeyboardEvent.KEY_DOWN, keyDownHandler);
71             map.stage.addEventListener(KeyboardEvent.KEY_UP, keyUpHandler);
72
73             if (this.moveFunction) {
74                 map.addEventListener(MapEvent.MOVE, moveHandler);
75             }
76
77                         if (supportsMouseCursors()) {
78                                 createBitmapCursor("pen"     ,new pen());
79                                 createBitmapCursor("pen_x"   ,new pen_x());
80                                 createBitmapCursor("pen_o"   ,new pen_o());
81                                 createBitmapCursor("pen_so"  ,new pen_so());
82                                 createBitmapCursor("pen_plus",new pen_plus());
83                         }
84         }
85
86         public function setActive():void {
87             map.setController(this);
88         }
89
90         /** Accesses map object. */
91         public function get map():Map {
92             return _map;
93         }
94         
95         /**
96         * Updates the various user interfaces that change when the selection changes.
97         * Currently this is the TagViewer and the Toolbox
98         *
99         * @param layer Optionally pass the layer of the currently selected entity, eg for BugLayers
100         */
101                 public function updateSelectionUI(layer:MapPaint = null):void {
102                         tagViewer.setEntity(state.selection, layer);
103                         toolbox.updateSelectionUI();
104                 }
105
106                 public function updateSelectionUIWithoutTagChange():void {
107                         toolbox.updateSelectionUI();
108                 }
109         
110         private function keyDownHandler(event:KeyboardEvent):void {
111                         if ((event.target is TextField) || (event.target is TextArea)) {
112                                 keys[event.keyCode]=new Date().getTime();
113                                 return;
114                         }
115                         delete keys[event.keyCode];
116                         if (event.keyCode==Keyboard.SPACE) spaceHeld=true;
117                 }
118
119         private function keyUpHandler(event:KeyboardEvent):void {
120                         if ((event.target is TextField) || (event.target is TextArea)) return;
121                         if (event.keyCode==Keyboard.SPACE) spaceHeld=false;
122                         if (keys[event.keyCode] && new Date().getTime()-keys[event.keyCode]<300) return;
123                         delete keys[event.keyCode];
124
125                         if (FunctionKeyManager.instance().handleKeypress(event.keyCode)) { return; }
126             
127             if (event.keyCode == 77) { toggleSize(); } // 'M'
128             var newState:ControllerState = state.processKeyboardEvent(event);
129             setState(newState);            
130                 }
131
132         private function mapMouseEvent(event:MouseEvent):void {
133             if (isInteractionEvent(event)) map.stage.focus = map.parent;
134             if (event.type==MouseEvent.MOUSE_UP && map.dragstate==map.DRAGGING) { return; }
135             
136             var mapLoc:Point = map.globalToLocal(new Point(event.stageX, event.stageY));
137             event.localX = mapLoc.x;
138             event.localY = mapLoc.y;
139
140             var newState:ControllerState = state.processMouseEvent(event, null);
141             setState(newState);
142         }
143         
144         public function entityMouseEvent(event:MouseEvent, entity:Entity):void {
145             if (isInteractionEvent(event)) map.stage.focus = map.parent;
146             event.stopPropagation();
147                 
148             var mapLoc:Point = map.globalToLocal(new Point(event.stageX, event.stageY));
149             event.localX = mapLoc.x;
150             event.localY = mapLoc.y;
151
152             var newState:ControllerState = state.processMouseEvent(event, entity);
153             setState(newState);
154         }
155
156                 private function isInteractionEvent(event:MouseEvent):Boolean {
157                         switch (event.type) {
158                                 case MouseEvent.ROLL_OUT:       return false;
159                                 case MouseEvent.ROLL_OVER:      return false;
160                                 case MouseEvent.MOUSE_OUT:      return false;
161                                 case MouseEvent.MOUSE_OVER:     return false;
162                                 case MouseEvent.MOUSE_MOVE:     return false;
163                 }
164                         return true;
165                 }
166
167         /** Exit the current state and switch to a new one.
168         *
169         *   @param newState The ControllerState to switch to. */
170         public function setState(newState:ControllerState):void {
171             if ( newState == state )
172                 return;
173                 
174             if ( state != null )
175                 state.exitState(newState);
176             newState.setController(this);
177             newState.setPreviousState(state);
178             state = newState;
179             state.enterState();
180         }
181
182                 /** Given what is currently selected (or not), find the matching ControllerState. */
183                 public function findStateForSelection(sel:Array):ControllerState {
184                         if (sel.length==0) { return new NoSelection(); }
185                         var layer:MapPaint=_map.getLayerForEntity(sel[0]);
186                         
187                         if (sel.length>1) { return new SelectedMultiple(sel, layer); }
188                         else if (sel[0] is Way) { return new SelectedWay(sel[0], layer); }
189                         else if (sel[0] is Node && Node(sel[0]).hasParentWays) {
190                                 var way:Way=sel[0].parentWays[0] as Way;
191                                 return new SelectedWayNode(way, way.indexOfNode(sel[0] as Node));
192                         } else {
193                                 return new SelectedPOINode(sel[0] as Node, layer);
194                         }
195                 }
196
197                 /** Set a mouse pointer. */
198                 public function setCursor(name:String=""):void {
199                         if (name && cursorsEnabled && supportsMouseCursors()) { Mouse.cursor=name; }
200                         else { Mouse.cursor=flash.ui.MouseCursor.AUTO; }
201                 }
202
203                 private function createBitmapCursor(name:String, source:Bitmap, hotX:int=4, hotY:int=0):void {
204                         var bitmapVector:Vector.<BitmapData> = new Vector.<BitmapData>(1, true);
205                         bitmapVector[0] = source.bitmapData;
206                         var cursorData:MouseCursorData = new MouseCursorData();
207                         cursorData.hotSpot = new Point(hotX,hotY);
208                         cursorData.data = bitmapVector;
209                         Mouse.registerCursor(name, cursorData);
210                 }
211
212                 private function supportsMouseCursors():Boolean {
213                         var fpArray:Array=Capabilities.version.split(",");
214                         var fpVersion:Number=Number(fpArray[0].split(" ")[1])+Number(fpArray[1])/10;
215                         return (fpVersion>10.1);
216                 }
217
218         private function toggleSize():void {
219             if (maximised) {
220                 if (minimiseFunction) {
221                     ExternalInterface.call(minimiseFunction);
222                 }
223
224                 maximised = false;
225             } else {
226                 if (maximiseFunction) {
227                     ExternalInterface.call(maximiseFunction);
228                 }
229
230                 maximised = true;
231             }
232         }
233
234                 private function moveHandler(event:MapEvent):void {
235                         ExternalInterface.call(this.moveFunction,
236                                    event.params.lon, event.params.lat, event.params.scale,
237                                    event.params.minlon, event.params.minlat,
238                                    event.params.maxlon, event.params.maxlat);
239                 }
240
241     }
242     
243 }