Also a bunch of ASDoc documentation.
import net.systemeD.halcyon.styleparser.RuleSet;
import net.systemeD.halcyon.connection.*;
+ /** Parent class of representations of map Entities, with properties about how they should be drawn. */
public class EntityUI {
+ /** The entity represented by this class. */
protected var entity:Entity;
- protected var styleList:StyleList; // current StyleList for this entity
- protected var sprites:Array=new Array(); // instances in display list
- protected var listenSprite:Sprite=new Sprite(); // clickable sprite to receive events
- protected var hitzone:Sprite; // hitzone for above
- protected var stateClasses:Object=new Object(); // special context-sensitive classes, e.g. :hover
- protected var layer:Number=0; // map layer
- protected var suspended:Boolean=false; // suspend redrawing?
- protected var redrawDue:Boolean=false; // redraw called while suspended?
- protected var clearLimit:uint=0; // sprite to clear back to
- public var paint:MapPaint; // reference to parent MapPaint
- public var ruleset:RuleSet; // reference to ruleset in operation
- public var interactive:Boolean=true; // does object respond to clicks?
- public var purgable:Boolean=true; // can it be deleted when offscreen?
+ /** Current StyleList for this entity. */
+ protected var styleList:StyleList;
+ /** Instances in display list */
+ protected var sprites:Array=new Array();
+ /** The clickable sprite that will receive events. */
+ protected var listenSprite:Sprite=new Sprite();
+ /** Hitzone for the sprite */
+ protected var hitzone:Sprite;
+ /** Special context-sensitive classes such as :hover. */
+ protected var stateClasses:Object=new Object();
+ /** Map layer */
+ protected var layer:Number=0;
+ /** Is drawing suspended? */
+ protected var suspended:Boolean=false;
+ /** Redraw called while suspended? */
+ protected var redrawDue:Boolean=false;
+ /** Sprite to clear back to */
+ protected var clearLimit:uint=0;
+ /** Reference to parent MapPaint */
+ public var paint:MapPaint;
+ /** Reference to ruleset (MapCSS) in operation */
+ public var ruleset:RuleSet;
+ /** Does object respond to clicks? */
+ public var interactive:Boolean=true;
+ /** Can it be deleted when offscreen? */
+ public var purgable:Boolean=true;
protected const FILLSPRITE:uint=0;
protected const CASINGSPRITE:uint=1;
gridFitType: GridFitType.NONE
};
+ /** Constructor function, adds a bunch of event listeners. */
public function EntityUI(entity:Entity, paint:MapPaint) {
this.entity=entity;
this.paint=paint;
listenSprite.addEventListener(MouseEvent.MOUSE_MOVE, mouseEvent);
}
+ /** Remove the default event listeners. */
protected function removeGenericEventListeners():void {
entity.removeEventListener(Connection.TAG_CHANGED, tagChanged);
entity.removeEventListener(Connection.ADDED_TO_RELATION, relationAdded);
// -----------------------------------------------------------------
- // Add object (stroke/fill/roadname) to layer sprite
+ /** Add object (stroke/fill/roadname) to layer sprite*/
protected function addToLayer(s:DisplayObject,t:uint,sublayer:int=-1):void {
var l:Sprite, o:Sprite;
}
}
+ /** Remove all sprites associated with this entity, and clear hitzone. */
public function removeSprites():void {
while (sprites.length>clearLimit) {
var d:DisplayObject=sprites.pop();
return "[EntityUI "+entity+"]";
}
- // Redraw control
+ /** Request redraw */
public function redraw():Boolean {
if (suspended) { redrawDue=true; return false; }
return doRedraw();
}
+ /** Actually do the redraw. To be overwritten. */
public function doRedraw():Boolean {
- // to be overwritten
return false;
}
+ /** Temporarily suspend redrawing of object. */
public function suspendRedraw(event:EntityEvent):void {
suspended=true;
redrawDue=false;
}
+ /** Resume redrawing. */
public function resumeRedraw(event:EntityEvent):void {
suspended=false;
if (redrawDue) {
/** Manages the drawing of map entities, allocating their sprites etc. */
public class MapPaint extends Sprite {
+ /** Access Map object */
public var map:Map;
+ /** Entities on layers below minlayer will not be shown by this paint object. (Confirm?) */
public var minlayer:int;
+ /** Entities on layers above maxlayer will not be shown by this paint object. (Confirm?) */
public var maxlayer:int;
- public var ruleset:RuleSet; // rules
+ /** The MapCSS rules used for drawing entities. */
+ public var ruleset:RuleSet;
+ /** WayUI objects attached to Way entities that are currently visible. */
public var wayuis:Object=new Object(); // sprites for ways and (POI/tagged) nodes
- public var nodeuis:Object=new Object(); // |
+ /** NodeUI objects attached to POI/tagged node entities that are currently visible. */
+ // confirm this - it seems to me all nodes in ways get nodeuis? --Steve B
+ public var nodeuis:Object=new Object();
+ /** MarkerUI objects attached to Marker entities that are currently visible. */
public var markeruis:Object=new Object();
- public var isBackground:Boolean = true; // is it a background layer or the core paint object?
- public var sublayerIndex:Object={}; // hash of index->position
+ /** Is this a background layer or the core paint object? */
+ public var isBackground:Boolean = true;
+ /** Hash of index->position */
+ public var sublayerIndex:Object={};
private const VERYBIG:Number=Math.pow(2,16);
private static const NO_LAYER:int=-99999; // same as NodeUI
}
}
+ /** Returns the paint surface for the given layer. */
public function getPaintSpriteAt(l:int):Sprite {
return getChildAt(l-minlayer) as Sprite;
}
return getChildAt((l-minlayer) + (maxlayer-minlayer+1)) as Sprite;
}
+ /** Is ruleset loaded? */
public function get ready():Boolean {
if (!ruleset) { return false; }
if (!ruleset.loaded) { return false; }
* @param redraw If true, all UIs for entities on "inside" lists will be redrawn
* @param remove If true, all UIs for entites on "outside" lists will be removed. The purgable flag on UIs
can override this, for example for selected objects.
+ * fixme? add smarter behaviour for way nodes - remove NodeUIs from way nodes off screen, create them for ones
+ * that scroll onto screen (for highlights etc)
*/
public function updateEntityUIs(o:Object, redraw:Boolean, remove:Boolean):void {
var way:Way, poi:Node, marker:Marker;
for each (way in o.waysInside) {
if (!wayuis[way.id]) { createWayUI(way); }
else if (redraw) { wayuis[way.id].recalculate(); wayuis[way.id].redraw(); }
+ else wayuis[way.id].updateHighlights();//dubious
}
if (remove) {
return wayuis[way.id];
}
+ /** Respond to event by removing the WayUI. */
public function wayDeleted(event:EntityEvent):void {
deleteWayUI(event.entity as Way);
}
return nodeuis[node.id];
}
+ /** Respond to event by deleting NodeUI. */
public function nodeDeleted(event:EntityEvent):void {
deleteNodeUI(event.entity as Node);
}
return markeruis[marker.id];
}
+ /** Respond to event by deleting MarkerUI. */
public function markerDeleted(event:EntityEvent):void {
deleteMarkerUI(event.entity as Marker);
}
return renderFromStyle(tags);
}
+ /** Assemble the layers of icons to draw the node, with hit zone if interactive. */
private function renderFromStyle(tags:Object):Boolean {
var r:Boolean=false; // ** rendered
var maxwidth:Number=4; // biggest width
import net.systemeD.halcyon.connection.*;
import net.systemeD.halcyon.Globals;
+ /** The graphical representation of a Way. */
public class WayUI extends EntityUI {
- public var pathlength:Number; // length of path
- public var patharea:Number; // area of path
- public var centroid_x:Number; // centroid
+ /** Total length of way */
+ public var pathlength:Number;
+ /** Area of the way */
+ public var patharea:Number;
+ /** X coord of the centre of the area */
+ public var centroid_x:Number;
+ /** Y coord of the centre of the area */
public var centroid_y:Number; // |
- public var heading:Array=new Array(); // angle at each node
- public var drawExcept:Number; // vertex to draw exclusively, or not at all (used by DragWayNode)
- public var drawOnly:Number; // |
- private var indexStart:uint; // |
- private var indexEnd:uint; // |
+ /** Angle at each node */
+ public var heading:Array=new Array();
+ /** vertex to draw exclusively, or not at all (used by DragWayNode) */
+ public var drawExcept:Number;
+ /** " */
+ public var drawOnly:Number;
+ private var indexStart:uint;
+ private var indexEnd:uint;
public var nameformat:TextFormat;
private var recalculateDue:Boolean=false;
+ // Store the temporary highlight settings applied to all nodes.
+ private var nodehighlightsettings: Object={};
private const NODESIZE:uint=6;
redrawMultis();
}
+ /** Don't redraw until further notice. */
override public function suspendRedraw(event:EntityEvent):void {
super.suspendRedraw(event);
recalculateDue=false;
}
+ /** Continue redrawing as normal. */
override public function resumeRedraw(event:EntityEvent):void {
suspended=false;
if (recalculateDue) { recalculate(); }
super.resumeRedraw(event);
}
+ /** Redraw other ways that are the "outer" part of a multipolygon of which we are the "inner" */
public function redrawMultis():void {
var multis:Array=entity.findParentRelationsOfType('multipolygon','inner');
for each (var m:Relation in multis) {
}
}
}
-
+
+ /** Mark every node in this way as highlighted, and redraw it.
+ *
+ * @param settings Style state that it applies to. @see EntityUI#setStateClass()
+ * */
public function setHighlightOnNodes(settings:Object):void {
for (var i:uint = 0; i < Way(entity).length; i++) {
var node:Node = Way(entity).getNode(i);
if (paint.nodeuis[node.id]) {
- paint.nodeuis[node.id].setHighlight(settings);
- paint.nodeuis[node.id].redraw();
+ // Speed things up a bit by only setting the highlight if it's either:
+ // a) an "un-highlight" (so we don't leave mess behind when scrolling)
+ // b) currently onscreen
+ // Currently this means if you highlight an object then scroll, nodes will scroll
+ // into view that should be highlighted but aren't.
+ if (settings.hoverway==false ||
+ settings.selectedway==false ||
+ node.lat >= paint.map.edge_b && node.lat <= paint.map.edge_t &&
+ node.lon >= paint.map.edge_l && node.lon <= paint.map.edge_r) {
+ paint.nodeuis[node.id].setHighlight(settings); // Triggers redraw if required
+ }
+ if (settings.selectedway || settings.hoverway)
+ nodehighlightsettings=settings;
+ else
+ nodehighlightsettings={};
}
}
}
+
+ // An ugly hack to allow nodes that have recently scrolled into view to get highlighted.
+ public function updateHighlights():void {
+ if (nodehighlightsettings)
+ setHighlightOnNodes(nodehighlightsettings);
+ }
// ------------------------------------------------------------------------------------------
- // Calculate length etc.
- // ** this could be made scale-independent - would speed up redraw
-
+ /** Calculate pathlength, patharea, centroid_x, centroid_y, heading[]
+ * ** this could be made scale-independent - would speed up redraw
+ */
public function recalculate():void {
if (suspended) { recalculateDue=true; return; }
}
// ------------------------------------------------------------------------------------------
- // Redraw
-
+
+ /** Go through the complex process of drawing this way, including applying styles, casings, fills, fonts... */
override public function doRedraw():Boolean {
interactive=false;
removeSprites();
// ------------------------------------------------------------------------------------------
// Drawing support functions
- // Draw solid polyline
+ /** Draw solid polyline */
public function solidLines(g:Graphics,inners:Array):void {
solidLine(g);
}
}
- // Draw dashed polyline
+ /** Draw dashed polyline */
private function dashedLine(g:Graphics,dashes:Array):Array {
var way:Way=entity as Way;
else { g.moveTo(x,y); }
}
- // Draw decoration (arrows etc.)
+ /** Draw decoration (arrows etc.) */
private function lineDecoration(g:Graphics,s:ShapeStyle,segments:Array):void {
var c:int=s.color ? s.color : 0;
}
- // Find point partway (0-1) along a path
- // returns (x,y,angle)
- // inspired by senocular's Path.as
+ /** Find point partway (0-1) along a path
+ * @return (x,y,angle)
+ * inspired by senocular's Path.as */
private function pointAt(t:Number):Array {
var way:Way=entity as Way;
return new Array(0, 0, 0);
}
- // Draw name along path
- // based on code by Tom Carden
+ /** Draw name along path
+ * based on code by Tom Carden
+ * */
private function writeNameOnPath(s:Sprite,a:String,textOffset:Number=0):void {
}
// ------------------------------------------------------------------------------------------
- // Interaction
-
+ /* Interaction */
+ // TODO: can this be sped up? Hit testing for long ways (that go off the screen) seems to be very slow. */
public function hitTest(x:Number, y:Number):Way {
if (hitzone.hitTestPoint(x,y,true)) { return entity as Way; }
return null;
import flash.geom.*;
import flash.ui.Keyboard;
+ /** 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 implements MapController {
private var _map:Map;
[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());
_connection = map.connection;
}
+ /** Accesses map object. */
public function get map():Map {
return _map;
}
+ /** Accesss connection object. */
public function get connection():Connection {
return _connection;
}
setState(newState);
}
+ /** Is the given key currently pressed? */
public function keyDown(key:Number):Boolean {
return Boolean(keys[key]);
}
return true;
}
+ /** Exit the current state and switch to a new one. */
public function setState(newState:ControllerState):void {
if ( newState == state )
return;
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); }
}
}
+ /** Set a mouse pointer. */
public function setCursor(cursor:Class):void {
CursorManager.removeAllCursors();
if (cursor && cursorsEnabled) { CursorManager.setCursor(cursor,2,-4,0); }
if ( event.type == MouseEvent.MOUSE_UP ) {
controller.map.mouseUpHandler(); // in case you're still in the drag-tolerance zone, and mouse up over something.
- if ( entity == null || isBackground ) {
+ if ( entity == null || isBackground ) { // didn't hit anything: extend the way by one node.
node = createAndAddNode(event, MainUndoStack.getGlobalStack().addAction);
controller.map.setHighlight(node, { selectedway: true });
controller.map.setPurgable([node], false);
} else if ( entity is Node ) {
if (entity==lastClick && (new Date().getTime()-lastClickTime.getTime())<1000) {
if (Way(firstSelected).length==1 && Way(firstSelected).getNode(0).parentWays.length==1) {
- // double-click to create new POI
+ // Actually the user double-clicked to make a new node, they didn't want to draw a way at all.
stopDrawing();
MainUndoStack.getGlobalStack().undo(); // undo the BeginWayAction that (presumably?) just happened
// clicked slowly on the end node - do nothing
return this;
} else {
+ // hit a node, add it to this way and carry on
appendNode(entity as Node, MainUndoStack.getGlobalStack().addAction);
if (focus is Way) {
controller.map.setHighlightOnNodes(focus as Way, { hoverway: false });
resetElastic(entity as Node);
lastClick=entity;
if (Way(firstSelected).getNode(0)==Way(firstSelected).getLastNode()) {
+ // the node just hit completes a loop, so stop drawing.
return new SelectedWay(firstSelected as Way);
}
}
}
lastClickTime=new Date();
} else if ( event.type == MouseEvent.MOUSE_MOVE && elastic ) {
+ // mouse is roaming around freely
mouse = new Point(
controller.map.coord2lon(event.localX),
controller.map.coord2latp(event.localY));
elastic.end = mouse;
} else if ( event.type == MouseEvent.ROLL_OVER && !isBackground ) {
+ // mouse has floated over something
if (focus is Way && focus!=firstSelected) {
+ // floating over another way, highlight its nodes
hoverEntity=focus;
+ // FIXME this call is incredibly slow for long ways, really hurts usability
controller.map.setHighlightOnNodes(focus as Way, { hoverway: true });
}
+ // set cursor depending on whether we're floating over the start of this way,
+ // another random node, a possible junction...
if (entity is Node && focus is Way && Way(focus).endsWith(Node(entity))) {
if (focus==firstSelected) { controller.setCursor(controller.pen_so); }
else { controller.setCursor(controller.pen_o); }
override public function processKeyboardEvent(event:KeyboardEvent):ControllerState {
switch (event.keyCode) {
- case 13: return keyExitDrawing();
- case 27: return keyExitDrawing();
+ case Keyboard.ENTER: return keyExitDrawing();
+ case Keyboard.ESCAPE: return keyExitDrawing();
case Keyboard.DELETE: return backspaceNode(MainUndoStack.getGlobalStack().addAction);
case Keyboard.BACKSPACE: return backspaceNode(MainUndoStack.getGlobalStack().addAction);
- case 189: return backspaceNode(MainUndoStack.getGlobalStack().addAction);
- case 82: repeatTags(firstSelected); return this;
+ case 189: /* minus */ return backspaceNode(MainUndoStack.getGlobalStack().addAction);
+ case 82: /* R */ repeatTags(firstSelected); return this;
}
var cs:ControllerState = sharedKeyboardEvents(event);
return cs ? cs : this;