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