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