Fix bug where 'trailing' keypresses from a TextField might be passed through
[potlatch2.git] / net / systemeD / potlatch2 / EditController.as
1 package net.systemeD.potlatch2 {
2     import net.systemeD.halcyon.Map;
3     import net.systemeD.halcyon.MapController;
4     import net.systemeD.halcyon.MapEvent;
5     import net.systemeD.halcyon.connection.*;
6     import net.systemeD.halcyon.VectorLayer;
7     import net.systemeD.potlatch2.controller.*;
8     import net.systemeD.potlatch2.FunctionKeyManager;
9         import mx.managers.CursorManager;
10     import flash.external.ExternalInterface;
11     import flash.events.*;
12         import flash.geom.*;
13         import flash.ui.Keyboard;
14         import flash.text.TextField;
15     import mx.controls.TextArea;
16
17     /** Controller for the main map editing window itself. The logic that responds to mouse and keyboard events is all 
18     * buried in various ControllerState classes. */
19     public class EditController implements MapController {
20
21         private var _map:Map;
22         public var tagViewer:TagViewer;
23                 private var toolbox:Toolbox;
24         
25         public var state:ControllerState;
26         private var _connection:Connection;
27         
28                 private var keys:Object={};
29                 public var clipboards:Object={};
30                 public var cursorsEnabled:Boolean=true;
31         private var maximised:Boolean=false;
32         private var maximiseFunction:String;
33         private var minimiseFunction:String;
34         private var moveFunction:String;
35
36                 [Embed(source="../../../embedded/pen.png")]             public var pen:Class;
37                 [Embed(source="../../../embedded/pen_x.png")]           public var pen_x:Class;
38                 [Embed(source="../../../embedded/pen_o.png")]           public var pen_o:Class;
39                 [Embed(source="../../../embedded/pen_so.png")]          public var pen_so:Class;
40                 [Embed(source="../../../embedded/pen_plus.png")]        public var pen_plus:Class;
41                 
42         /** Constructor function: needs the map information, a panel to edit tags with, and the toolbox to manipulate ways with. */
43         public function EditController(map:Map, tagViewer:TagViewer, toolbox:Toolbox) {
44             this._map = map;
45             setState(new NoSelection());
46             this.tagViewer = tagViewer;
47                         this.toolbox = toolbox;
48                         this.toolbox.init(this);
49             this.maximiseFunction = Connection.getParam("maximise_function", null);
50             this.minimiseFunction = Connection.getParam("minimise_function", null);
51             this.moveFunction = Connection.getParam("move_function", null);
52
53             map.parent.addEventListener(MouseEvent.MOUSE_MOVE, mapMouseEvent);
54             map.parent.addEventListener(MouseEvent.MOUSE_UP, mapMouseEvent);
55             map.parent.addEventListener(MouseEvent.MOUSE_DOWN, mapMouseEvent);
56             map.parent.addEventListener(MouseEvent.MOUSE_WHEEL, mapMouseEvent);
57             map.parent.addEventListener(MouseEvent.CLICK, mapMouseEvent);
58             map.stage.addEventListener(KeyboardEvent.KEY_DOWN, keyDownHandler);
59             map.stage.addEventListener(KeyboardEvent.KEY_UP, keyUpHandler);
60
61             if (this.moveFunction) {
62                 map.addEventListener(MapEvent.MOVE, moveHandler);
63             }
64         }
65
66         public function setActive():void {
67             map.setController(this);
68             _connection = map.connection;
69         }
70
71         /** Accesses map object. */
72         public function get map():Map {
73             return _map;
74         }
75         
76         /** Accesss connection object. */
77         public function get connection():Connection {
78             return _connection;
79         }
80
81         /**
82         * Updates the various user interfaces that change when the selection changes.
83         * Currently this is the TagViewer and the Toolbox
84         *
85         * @param layer Optionally pass the layer of the currently selected entity, eg for BugLayers
86         */
87                 public function updateSelectionUI(layer:VectorLayer = null):void {
88                         tagViewer.setEntity(state.selection, layer);
89                         toolbox.updateSelectionUI();
90                 }
91
92                 public function updateSelectionUIWithoutTagChange():void {
93                         toolbox.updateSelectionUI();
94                 }
95         
96         private function keyDownHandler(event:KeyboardEvent):void {
97                         if ((event.target is TextField) || (event.target is TextArea)) return;
98                         keys[event.keyCode]=true;
99                 }
100
101         private function keyUpHandler(event:KeyboardEvent):void {
102             if (!keys[event.keyCode]) return;
103             delete keys[event.keyCode];
104             if ((event.target is TextField) || (event.target is TextArea)) return;                              // not meant for us
105
106                         if (FunctionKeyManager.instance().handleKeypress(event.keyCode)) { return; }
107             
108             if (event.keyCode == 77) { toggleSize(); } // 'M'
109             var newState:ControllerState = state.processKeyboardEvent(event);
110             setState(newState);            
111                 }
112
113                 /** Is the given key currently pressed? */
114                 public function keyDown(key:Number):Boolean {
115                         return Boolean(keys[key]);
116                 }
117
118         private function mapMouseEvent(event:MouseEvent):void {
119             if (isInteractionEvent(event)) map.stage.focus = map.parent;
120             if (event.type==MouseEvent.MOUSE_UP && map.dragstate==map.DRAGGING) { return; }
121             
122             var mapLoc:Point = map.globalToLocal(new Point(event.stageX, event.stageY));
123             event.localX = mapLoc.x;
124             event.localY = mapLoc.y;
125
126             var newState:ControllerState = state.processMouseEvent(event, null);
127             setState(newState);
128         }
129         
130         public function entityMouseEvent(event:MouseEvent, entity:Entity):void {
131             if (isInteractionEvent(event)) map.stage.focus = map.parent;
132             event.stopPropagation();
133                 
134             var mapLoc:Point = map.globalToLocal(new Point(event.stageX, event.stageY));
135             event.localX = mapLoc.x;
136             event.localY = mapLoc.y;
137
138             var newState:ControllerState = state.processMouseEvent(event, entity);
139             setState(newState);
140         }
141
142                 private function isInteractionEvent(event:MouseEvent):Boolean {
143                         switch (event.type) {
144                                 case MouseEvent.ROLL_OUT:       return false;
145                                 case MouseEvent.ROLL_OVER:      return false;
146                                 case MouseEvent.MOUSE_OUT:      return false;
147                                 case MouseEvent.MOUSE_OVER:     return false;
148                                 case MouseEvent.MOUSE_MOVE:     return false;
149                 }
150                         return true;
151                 }
152
153         /** Exit the current state and switch to a new one. */
154         public function setState(newState:ControllerState):void {
155             if ( newState == state )
156                 return;
157                 
158             if ( state != null )
159                 state.exitState(newState);
160             newState.setController(this);
161             newState.setPreviousState(state);
162             state = newState;
163             state.enterState();
164         }
165
166                 /** Given what is currently selected (or not), find the matching ControllerState. */
167                 public function findStateForSelection(sel:Array):ControllerState {
168                         if (sel.length==0) { return new NoSelection(); }
169                         else if (sel.length>1) { return new SelectedMultiple(sel); }
170                         else if (sel[0] is Way) { return new SelectedWay(sel[0]); }
171                         else if (sel[0] is Node && Node(sel[0]).hasParentWays) {
172                                 var way:Way=sel[0].parentWays[0] as Way;
173                                 return new SelectedWayNode(way, way.indexOfNode(sel[0] as Node));
174                         } else {
175                                 return new SelectedPOINode(sel[0] as Node);
176                         }
177                 }
178
179                 /** Set a mouse pointer. */
180                 public function setCursor(cursor:Class):void {
181                         CursorManager.removeAllCursors();
182                         if (cursor && cursorsEnabled) { CursorManager.setCursor(cursor,2,-4,0); }
183                 }
184
185         private function toggleSize():void {
186             if (maximised) {
187                 if (minimiseFunction) {
188                     ExternalInterface.call(minimiseFunction);
189                 }
190
191                 maximised = false;
192             } else {
193                 if (maximiseFunction) {
194                     ExternalInterface.call(maximiseFunction);
195                 }
196
197                 maximised = true;
198             }
199         }
200
201                 private function moveHandler(event:MapEvent):void {
202                         ExternalInterface.call(this.moveFunction,
203                                    event.params.lon, event.params.lat, event.params.scale,
204                                    event.params.minlon, event.params.minlat,
205                                    event.params.maxlon, event.params.maxlat);
206                 }
207
208     }
209     
210 }