Merge branch 'master' into history
[potlatch2.git] / net / systemeD / potlatch2 / EditController.as
index 39c3d3e..5cb00fb 100644 (file)
@@ -1,26 +1,41 @@
 package net.systemeD.potlatch2 {
     import net.systemeD.halcyon.Map;
+    import net.systemeD.halcyon.MapPaint;
     import net.systemeD.halcyon.MapController;
     import net.systemeD.halcyon.MapEvent;
     import net.systemeD.halcyon.connection.*;
-    import net.systemeD.halcyon.VectorLayer;
+    import net.systemeD.halcyon.Globals;
     import net.systemeD.potlatch2.controller.*;
     import net.systemeD.potlatch2.FunctionKeyManager;
        import mx.managers.CursorManager;
     import flash.external.ExternalInterface;
     import flash.events.*;
        import flash.geom.*;
-
-    public class EditController implements MapController {
+       import flash.display.*;
+       import flash.ui.Keyboard;
+       import flash.ui.Mouse;
+       import flash.ui.MouseCursorData;
+       import flash.system.Capabilities;
+       import flash.text.TextField;
+    import mx.controls.TextArea;
+
+    /** Controller for the main map editing window itself. The logic that responds to mouse and keyboard events is all 
+    * buried in various ControllerState classes. */
+    public class EditController extends EventDispatcher implements MapController {
 
         private var _map:Map;
         public var tagViewer:TagViewer;
                private var toolbox:Toolbox;
-        
+
+        /** The current ControllerState */
         public var state:ControllerState;
-        private var _connection:Connection;
         
+               /** Hash of when a key was pressed. A user can keyDown within a TextInput, press Enter (leaving
+                   the TextInput), and then keyup - resulting in the keypress being interpreted again. 
+                   We prevent this by tracking keyDowns within the TextInput and ignoring corresponding keyUps. */
                private var keys:Object={};
+
+               public var spaceHeld:Boolean=false;
                public var clipboards:Object={};
                public var cursorsEnabled:Boolean=true;
         private var maximised:Boolean=false;
@@ -34,72 +49,88 @@ package net.systemeD.potlatch2 {
                [Embed(source="../../../embedded/pen_so.png")]          public var pen_so:Class;
                [Embed(source="../../../embedded/pen_plus.png")]        public var pen_plus:Class;
                
+        /** Constructor function: needs the map information, a panel to edit tags with, and the toolbox to manipulate ways with. */
         public function EditController(map:Map, tagViewer:TagViewer, toolbox:Toolbox) {
             this._map = map;
             setState(new NoSelection());
             this.tagViewer = tagViewer;
+            this.tagViewer.controller = this;
                        this.toolbox = toolbox;
                        this.toolbox.init(this);
-            this.maximiseFunction = Connection.getParam("maximise_function", null);
-            this.minimiseFunction = Connection.getParam("minimise_function", null);
-            this.moveFunction = Connection.getParam("move_function", null);
+                       this.toolbox.updateSelectionUI();
+            this.maximiseFunction = Globals.vars.flashvars["maximise_function"];
+            this.minimiseFunction = Globals.vars.flashvars["minimise_function"];
+            this.moveFunction     = Globals.vars.flashvars["move_function"];
 
             map.parent.addEventListener(MouseEvent.MOUSE_MOVE, mapMouseEvent);
             map.parent.addEventListener(MouseEvent.MOUSE_UP, mapMouseEvent);
             map.parent.addEventListener(MouseEvent.MOUSE_DOWN, mapMouseEvent);
             map.parent.addEventListener(MouseEvent.MOUSE_WHEEL, mapMouseEvent);
             map.parent.addEventListener(MouseEvent.CLICK, mapMouseEvent);
-            map.parent.addEventListener(KeyboardEvent.KEY_DOWN, keyDownHandler);
-            map.parent.addEventListener(KeyboardEvent.KEY_UP, keyUpHandler);
+            map.stage.addEventListener(KeyboardEvent.KEY_DOWN, keyDownHandler);
+            map.stage.addEventListener(KeyboardEvent.KEY_UP, keyUpHandler);
 
             if (this.moveFunction) {
                 map.addEventListener(MapEvent.MOVE, moveHandler);
             }
+
+                       if (supportsMouseCursors()) {
+                               createBitmapCursor("pen"     ,new pen());
+                               createBitmapCursor("pen_x"   ,new pen_x());
+                               createBitmapCursor("pen_o"   ,new pen_o());
+                               createBitmapCursor("pen_so"  ,new pen_so());
+                               createBitmapCursor("pen_plus",new pen_plus());
+                       }
         }
 
         public function setActive():void {
             map.setController(this);
-            _connection = map.connection;
         }
 
+        /** Accesses map object. */
         public function get map():Map {
             return _map;
         }
         
-        public function get connection():Connection {
-            return _connection;
-        }
-
         /**
         * Updates the various user interfaces that change when the selection changes.
         * Currently this is the TagViewer and the Toolbox
         *
         * @param layer Optionally pass the layer of the currently selected entity, eg for BugLayers
         */
-               public function updateSelectionUI(layer:VectorLayer = null):void {
+               public function updateSelectionUI(layer:MapPaint = null):void {
                        tagViewer.setEntity(state.selection, layer);
                        toolbox.updateSelectionUI();
                }
+
+               public function updateSelectionUIWithoutTagChange():void {
+                       toolbox.updateSelectionUI();
+               }
         
         private function keyDownHandler(event:KeyboardEvent):void {
-                       keys[event.keyCode]=true;
+                       if ((event.target is TextField) || (event.target is TextArea)) {
+                               keys[event.keyCode]=new Date().getTime();
+                               return;
+                       }
+                       delete keys[event.keyCode];
+                       if (event.keyCode==Keyboard.SPACE) spaceHeld=true;
                }
 
         private function keyUpHandler(event:KeyboardEvent):void {
-            trace("key code "+event.keyCode);
-                       if (keys[event.keyCode]) { delete keys[event.keyCode]; }
+                       if ((event.target is TextField) || (event.target is TextArea)) return;
+                       if (event.keyCode==Keyboard.SPACE) spaceHeld=false;
+                       if (keys[event.keyCode] && new Date().getTime()-keys[event.keyCode]<300) return;
+                       delete keys[event.keyCode];
+
                        if (FunctionKeyManager.instance().handleKeypress(event.keyCode)) { return; }
-            if (event.keyCode == 77) { toggleSize(); }
+            
+            if (event.keyCode == 77) { toggleSize(); } // 'M'
             var newState:ControllerState = state.processKeyboardEvent(event);
             setState(newState);            
                }
 
-               public function keyDown(key:Number):Boolean {
-                       return Boolean(keys[key]);
-               }
-
         private function mapMouseEvent(event:MouseEvent):void {
-            if (event.type!=MouseEvent.ROLL_OVER) map.stage.focus = map.parent;
+            if (isInteractionEvent(event)) map.stage.focus = map.parent;
             if (event.type==MouseEvent.MOUSE_UP && map.dragstate==map.DRAGGING) { return; }
             
             var mapLoc:Point = map.globalToLocal(new Point(event.stageX, event.stageY));
@@ -111,8 +142,7 @@ package net.systemeD.potlatch2 {
         }
         
         public function entityMouseEvent(event:MouseEvent, entity:Entity):void {
-            if (event.type!=MouseEvent.ROLL_OVER) map.stage.focus = map.parent;
-            //if ( event.type == MouseEvent.MOUSE_DOWN )
+            if (isInteractionEvent(event)) map.stage.focus = map.parent;
             event.stopPropagation();
                 
             var mapLoc:Point = map.globalToLocal(new Point(event.stageX, event.stageY));
@@ -122,7 +152,21 @@ package net.systemeD.potlatch2 {
             var newState:ControllerState = state.processMouseEvent(event, entity);
             setState(newState);
         }
-        
+
+               private function isInteractionEvent(event:MouseEvent):Boolean {
+                       switch (event.type) {
+                               case MouseEvent.ROLL_OUT:       return false;
+                               case MouseEvent.ROLL_OVER:      return false;
+                               case MouseEvent.MOUSE_OUT:      return false;
+                               case MouseEvent.MOUSE_OVER:     return false;
+                               case MouseEvent.MOUSE_MOVE:     return false;
+               }
+                       return true;
+               }
+
+        /** Exit the current state and switch to a new one.
+        *
+        *   @param newState The ControllerState to switch to. */
         public function setState(newState:ControllerState):void {
             if ( newState == state )
                 return;
@@ -135,21 +179,40 @@ package net.systemeD.potlatch2 {
             state.enterState();
         }
 
+               /** Given what is currently selected (or not), find the matching ControllerState. */
                public function findStateForSelection(sel:Array):ControllerState {
                        if (sel.length==0) { return new NoSelection(); }
-                       else if (sel.length>1) { return new SelectedMultiple(sel); }
-                       else if (sel[0] is Way) { return new SelectedWay(sel[0]); }
+                       var layer:MapPaint=_map.getLayerForEntity(sel[0]);
+                       
+                       if (sel.length>1) { return new SelectedMultiple(sel, layer); }
+                       else if (sel[0] is Way) { return new SelectedWay(sel[0], layer); }
                        else if (sel[0] is Node && Node(sel[0]).hasParentWays) {
                                var way:Way=sel[0].parentWays[0] as Way;
                                return new SelectedWayNode(way, way.indexOfNode(sel[0] as Node));
                        } else {
-                               return new SelectedPOINode(sel[0] as Node);
+                               return new SelectedPOINode(sel[0] as Node, layer);
                        }
                }
 
-               public function setCursor(cursor:Class):void {
-                       CursorManager.removeAllCursors();
-                       if (cursor && cursorsEnabled) { CursorManager.setCursor(cursor,2,-4,0); }
+               /** Set a mouse pointer. */
+               public function setCursor(name:String=""):void {
+                       if (name && cursorsEnabled && supportsMouseCursors()) { Mouse.cursor=name; }
+                       else { Mouse.cursor=flash.ui.MouseCursor.AUTO; }
+               }
+
+               private function createBitmapCursor(name:String, source:Bitmap, hotX:int=4, hotY:int=0):void {
+                       var bitmapVector:Vector.<BitmapData> = new Vector.<BitmapData>(1, true);
+                       bitmapVector[0] = source.bitmapData;
+                       var cursorData:MouseCursorData = new MouseCursorData();
+                       cursorData.hotSpot = new Point(hotX,hotY);
+                       cursorData.data = bitmapVector;
+                       Mouse.registerCursor(name, cursorData);
+               }
+
+               private function supportsMouseCursors():Boolean {
+                       var fpArray:Array=Capabilities.version.split(",");
+                       var fpVersion:Number=Number(fpArray[0].split(" ")[1])+Number(fpArray[1])/10;
+                       return (fpVersion>10.1);
                }
 
         private function toggleSize():void {