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.*;
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;
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 {
27 public var tagViewer:TagViewer;
28 private var toolbox:Toolbox;
30 /** The current ControllerState */
31 public var state:ControllerState;
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={};
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;
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;
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) {
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"];
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);
74 if (this.moveFunction) {
75 map.addEventListener(MapEvent.MOVE, moveHandler);
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());
87 public function setActive():void {
88 map.setController(this);
91 /** Accesses map object. */
92 public function get map():Map {
97 * Updates the various user interfaces that change when the selection changes.
98 * Currently this is the TagViewer and the Toolbox
100 * @param layer Optionally pass the layer of the currently selected entity, eg for BugLayers
102 public function updateSelectionUI(layer:MapPaint = null):void {
103 tagViewer.setEntity(state.selection, layer);
104 toolbox.updateSelectionUI();
107 public function updateSelectionUIWithoutTagChange():void {
108 toolbox.updateSelectionUI();
111 private function keyDownHandler(event:KeyboardEvent):void {
112 if ((event.target is TextField) || (event.target is TextArea)) {
113 keys[event.keyCode]=new Date().getTime();
116 delete keys[event.keyCode];
117 if (event.keyCode==Keyboard.SPACE) spaceHeld=true;
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];
126 if (FunctionKeyManager.instance().handleKeypress(event.keyCode)) { return; }
128 if (event.keyCode == 77) { toggleSize(); } // 'M'
129 var newState:ControllerState = state.processKeyboardEvent(event);
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; }
137 var mapLoc:Point = map.globalToLocal(new Point(event.stageX, event.stageY));
138 event.localX = mapLoc.x;
139 event.localY = mapLoc.y;
141 var newState:ControllerState = state.processMouseEvent(event, null);
145 public function entityMouseEvent(event:MouseEvent, entity:Entity):void {
146 if (isInteractionEvent(event)) map.stage.focus = map.parent;
147 event.stopPropagation();
149 var mapLoc:Point = map.globalToLocal(new Point(event.stageX, event.stageY));
150 event.localX = mapLoc.x;
151 event.localY = mapLoc.y;
153 var newState:ControllerState = state.processMouseEvent(event, entity);
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;
168 /** Exit the current state and switch to a new one.
170 * @param newState The ControllerState to switch to. */
171 public function setState(newState:ControllerState):void {
172 if ( newState == state )
176 state.exitState(newState);
177 newState.setController(this);
178 newState.setPreviousState(state);
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]);
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));
194 return new SelectedPOINode(sel[0] as Node, layer);
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; }
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);
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);
219 private function toggleSize():void {
221 if (minimiseFunction) {
222 ExternalInterface.call(minimiseFunction);
227 if (maximiseFunction) {
228 ExternalInterface.call(maximiseFunction);
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);