Also replaced a few tab characters with 4 spaces. Hope that's ok.
<?xml version="1.0" encoding="utf-8"?>
<mx:VBox
- xmlns:mx="http://www.adobe.com/2006/mxml"
+ xmlns:mx="http://www.adobe.com/2006/mxml"
xmlns:halcyon="net.systemeD.halcyon.*"
backgroundColor="white" borderStyle="inset">
- <mx:HBox horizontalGap="0">
+ <mx:HBox horizontalGap="0">
<mx:ToggleButtonBar height="100%" dataProvider="{categoryStack}" direction="vertical" styleName="catToggleButtonBar"/>
<mx:ViewStack id="categoryStack" width="100%" height="100%"
- change="_formerIndex=event.newIndex;"
+ change="_formerIndex=event.newIndex;"
creationComplete="setSelectedFeature(_selectedType);">
<mx:Repeater id="catRep" dataProvider="{MapFeatures.getInstance().getCategoriesForType(limit)}">
<mx:VBox label="{catRep.currentItem.name}">
</mx:Repeater>
</mx:ViewStack>
</mx:HBox>
-
+
<!-- mx:Label id="hoverInfo" text="Hover Info goes here"/>-->
-
<mx:Script><![CDATA[
+
+
import net.systemeD.halcyon.connection.*;
import net.systemeD.potlatch2.mapfeatures.*;
import mx.controls.*;
import mx.containers.*;
import mx.events.IndexChangedEvent;
-
+
+ /** Set briefly by ensureSelection() (presumably to avoid an event race condition). */
private var settingSelection:Boolean = false;
private var _selectedType:Feature;
private var _limit:String;
private var _formerIndex:int=0;
-
+
[Bindable(event="selectedType")]
+ /** The Feature type (eg, "residential road") currently selected. */
public function get selectedType():Feature {
return _selectedType;
}
-
+
[Bindable(event="limitChanged")]
+ /** The type of features currently shown ("area", "node"...) */
public function get limit():String {
return _limit;
}
-
+
+ /** Determine which feature was clicked on, and set that as the currently selected type. */
private function itemSelected(event:Event):void {
_selectedType = Feature(TileList(event.currentTarget).selectedItem);
if ( !settingSelection )
dispatchEvent(new Event("selectedType"));
}
-
+
+ /** Set the current type of Features that will be shown ("area", "node"... )*/
public function setLimitTypes(type:String):void {
_limit = type;
dispatchEvent(new Event("limitChanged"));
}
-
+
/**
* Set the selected feature displayed in the selector.
*
* The tab is switched to the first category the feature
* is part of.
*
+ * @param feature The feature to make selected.
*/
public function setSelectedFeature(feature:Feature):void {
_selectedType = feature;
-
+
// check whether stack built yet, if not we get called again when it's made
if ( categoryStack == null )
return;
// set the tab to the selected item's category
- // (we have to manually fire the IndexChangedEvent because Flex's ViewStack.as isn't competent
+ // (we have to manually fire the IndexChangedEvent because Flex's ViewStack.as isn't competent
// enough to do it reliably. This is basically a direct crib from dispatchChangeEvent)
var index:int=0;
if ( feature != null ) {
categoryStack.selectedIndex = index;
categoryStack.dispatchEvent(event);
_formerIndex=index;
-
+
// finalise the item selection
ensureSelection();
}
public function setNoSelectedFeature():void {
- _selectedType=null;
+ /*_selectedType=null;*/
+ setSelectedFeature(null);
}
-
+
/**
* Sets the selected feature on each category page to the current
* value of _selectedType. If there is no selected feature then all
*
* Called both from setSelectedFeature, and category tab completion
* (so that newly created tabs work as expected)
- */
+ */
private function ensureSelection():void {
settingSelection = true;
for (var i:Number = 0; i < categoryStack.numChildren; i++) {
}
settingSelection = false;
}
-
+
]]></mx:Script>
</mx:VBox>
<mx:DataGridColumn editable="true" dataField="key" headerText="Key">
<mx:itemEditor>
<mx:Component>
- <controls:AutoComplete
- dataProvider="{MapFeatures.getInstance().getAutoCompleteKeys(outerDocument.getEntityType())}"
- labelField="name"
+ <controls:AutoComplete
+ dataProvider="{MapFeatures.getInstance().getAutoCompleteKeys(outerDocument.getEntityType())}"
+ labelField="name"
rowCount="10"
restrict=" -"
typedText="{outerDocument.selectedItem.key}">
<mx:DataGridColumn editable="true" dataField="value" headerText="Value">
<mx:itemEditor>
<mx:Component>
- <controls:AutoComplete
- dataProvider="{MapFeatures.getInstance().getAutoCompleteValues(outerDocument.getEntityType(),outerDocument.selectedItem.key)}"
- labelField="name"
+ <controls:AutoComplete
+ dataProvider="{MapFeatures.getInstance().getAutoCompleteValues(outerDocument.getEntityType(),outerDocument.selectedItem.key)}"
+ labelField="name"
rowCount="10"
restrict=" -"
typedText="{outerDocument.selectedItem.value}">
<mx:DataGridColumn width="12" editable="false">
<mx:itemRenderer>
<mx:Component>
- <mx:Image source="@Embed('../../../embedded/delete_small.svg')"
+ <mx:Image source="@Embed('../../../embedded/delete_small.svg')"
click='event.stopPropagation();outerDocument.removeTag();'
buttonMode="true" useHandCursor="true">
</mx:Image>
private var selectedEntity:Entity;
private var tagDataProvider:ArrayCollection;
+ /** Assign a new selected Entity, and update editing controls appropriately. */
public function init(entity:Entity):void {
if ( tagDataProvider == null ) {
tagDataProvider = new ArrayCollection();
for each(var tag:Tag in tags) { tagDataProvider.addItem(tag); }
}
+ /** Create editing controls for a new key/value pair, with default values. */
public function addNewTag():void {
var newKey:String = "(new tag)";
var newTag:Tag = new Tag(selectedEntity, newKey, "(new value)");
editedItemPosition = {rowIndex: tagDataProvider.getItemIndex(newTag), columnIndex: 0};
}
+ /** Remove the selected tag from the selected entity. */
public function removeTag():void {
var k:String = selectedItem.key;
selectedEntity.setTag(k, null, MainUndoStack.getGlobalStack().addAction);
updateTagDataProvider();
}
-
+
+ /** Return the type ("node", "way", "relation") of the selected entity. */
public function getEntityType():String {
return selectedEntity.getType();
}
import flash.events.EventDispatcher;
import flash.events.Event;
+ /** A Category is a (non-exclusive) grouping of related Features used to help the user find the map feature they are interested in using. */
public class Category extends EventDispatcher {
private var mapFeatures:MapFeatures;
+ /** The human-meaningful name of the category (eg, "Roads") */
private var _name:String;
private var _id:String;
+ /** The features that belong to this category. */
private var _features:Array;
private var _index:uint;
this._name = name;
this._id = id;
this._index = globalIndex;
-
+
_features = new Array();
for each( var feature:Feature in mapFeatures.features ) {
if ( feature.isInCategory(id) )
public function get id():String {
return _id;
}
-
+
public function get index():uint {
return _index;
}
public function get name():String {
return _name;
}
-
+
[Bindable(event="featuresChanged")]
public function get features():Array {
return _features;
}
-
+
[Bindable(event="featuresChanged")]
+ /** Get an array of all features in this category that have the requested type, or possibly empty list. */
public function getFeaturesForType(type:String):Array {
if ( type == null || type == "" )
return []; //_features;
-
+
var filteredFeatures:Array = new Array();
for each( var feature:Feature in _features ) {
if ( feature.isType(type) )
}
return filteredFeatures;
}
-
+
}
}
import flash.utils.ByteArray;
import mx.core.BitmapAsset;
import mx.graphics.codec.PNGEncoder;
-
+
import net.systemeD.halcyon.connection.Entity;
import net.systemeD.potlatch2.utils.CachedDataLoader;
+ /** A "map feature" is sort of a template for a map entity. It consists of a few crucial key/value pairs that define the feature, so that
+ * entities can be recognised. It also contains optional keys, with associated editing controls, that are defined as being appropriate
+ * for the feature. */
public class Feature extends EventDispatcher {
private var mapFeatures:MapFeatures;
private var _xml:XML;
private static var variablesPattern:RegExp = /[$][{]([^}]+)[}]/g;
private var _tags:Array;
- private var _withins:Array;
+ private var _withins:Array;
private var _editors:Array;
[Embed(source="../../../../embedded/missing_icon.png")]
parseConditions();
parseEditors();
}
-
+
private function parseConditions():void {
_tags = [];
- _withins = [];
-
+ _withins = [];
+
// parse tags
for each(var tag:XML in definition.tag) {
_tags.push( { k:String(tag.@k), v:String(tag.@v)} );
_withins.push(obj);
}
}
-
+
private function parseEditors():void {
_editors = new Array();
-
+
addEditors(definition);
-
+
_editors.sortOn(["sortOrder", "name"], [Array.DESCENDING | Array.NUMERIC, Array.CASEINSENSITIVE]);
}
-
+
private function addEditors(xml:XML):void {
var inputXML:XML;
-
+
for each(var inputSetRef:XML in xml.inputSet) {
var setName:String = String(inputSetRef.@ref);
for each (inputXML in mapFeatures.definition.inputSet.(@id==setName)) {
addEditors(inputXML);
}
}
-
+
for each(inputXML in xml.input) {
addEditor(inputXML);
}
}
-
+
private function addEditor(inputXML:XML):void {
var inputType:String = inputXML.@type;
var presenceStr:String = inputXML.@presence;
_editors.push(editor);
}
}
-
+
+ /** List of editing controls associated with this feature. */
public function get editors():Array {
return _editors;
}
-
+
+ /** The XML subtree that this feature was loaded from. */
public function get definition():XML {
return _xml;
}
-
+
[Bindable(event="nameChanged")]
+ /** The human-readable name of the feature, or null if none. */
public function get name():String {
if (_xml.attribute('name').length()>0) { return _xml.@name; }
return null;
}
-
+
[Bindable(event="imageChanged")]
+ /** An icon for the feature. If none is defined, return default "missing icon". */
public function get image():ByteArray {
var icon:XMLList = _xml.icon;
var imageURL:String = null;
var bitmap:BitmapAsset = new missingIconCls() as BitmapAsset;
return new PNGEncoder().encode(bitmap.bitmapData);
}
-
+
private function imageLoaded(url:String, data:ByteArray):void {
dispatchEvent(new Event("imageChanged"));
}
-
+
public function htmlDetails(entity:Entity):String {
var icon:XMLList = _xml.icon;
return makeHTMLIcon(icon, entity);
- }
+ }
public static function makeHTMLIcon(icon:XMLList, entity:Entity):String {
if ( icon == null )
return "";
-
+
var txt:String = icon.children().toXMLString();
var replaceTag:Function = function():String {
var value:String = entity.getTag(arguments[1]);
txt = txt.replace(variablesPattern, replaceTag);
return txt;
}
-
+
public static function htmlEscape(str:String):String {
var newStr:String = str.replace(/&/g, "&");
newStr = newStr.replace(/</g, "<");
newStr = newStr.replace(/'/g, "'"); // '
return newStr;
}
-
+ /** Whether this feature belongs to the given category or not, as defined by its definition in the XML file. */
public function isInCategory(category:String):Boolean {
var cats:XMLList = _xml.category;
if ( cats.length() == 0 )
return false;
-
+
for each( var cat:XML in cats )
if ( cat.text()[0] == category )
return true;
return false;
}
-
+
+
+ /** List of {k, v} pairs that define the feature. */
public function get tags():Array {
return _tags;
}
+ /** List of "withins" which further restrict the applicability of the feature. Each within is a {entity, k, ?v, ?minimum, ?role} object. */
public function get withins():Array {
return _withins;
}
-
+
+ /** The first category that the feature belongs to, as defined by the order of the map features XML file. */
public function findFirstCategory():Category {
for each( var cat:Category in mapFeatures.categories ) {
if ( isInCategory(cat.id) )
}
return null;
}
-
+
+ /** Whether the feature is of the given type (point, line/area, relation). */
public function isType(type:String):Boolean {
if (type=='area') {
return (_xml.elements(type).length() > 0) || (_xml.elements('line').length() > 0);
return _xml.elements(type).length() > 0;
}
}
-
+
+ /** Whether there is a help string defined. */
public function hasHelpURL():Boolean {
return _xml.help.length() > 0;
}
-
+
+ /** The defined help string, if any. */
public function get helpURL():String {
return _xml.help;
}
import net.systemeD.halcyon.connection.*;
import net.systemeD.halcyon.DebugURLRequest;
+ /** All the information about all available map features that can be selected by the user or matched against entities in the map.
+ * The list of map features is populated from an XML file the first time the MapFeatures instance is accessed.
+ *
+ * <p>There are four "types" of features: point, line, area, relation. However, the autocomplete functions refer to these as node,
+ * way (line/area) and relation.</p>
+ */
public class MapFeatures extends EventDispatcher {
private static var instance:MapFeatures;
+ /** Instantiates MapFeatures by loading it if required. */
public static function getInstance():MapFeatures {
if ( instance == null ) {
instance = new MapFeatures();
// private var _keys:Array = null;
private var _tags:Object = null;
+ /** Loads list of map features from XML file which it first retrieves. */
protected function loadFeatures():void {
var request:DebugURLRequest = new DebugURLRequest("map_features.xml");
var loader:URLLoader = new URLLoader();
loader.load(request.request);
}
+ /** The loaded source XML file itself. */
internal function get definition():XML {
return xml;
}
-
+
+ /** Load XML file, then trawl over it, setting up convenient indexes into the list of map features. */
private function onFeatureLoad(event:Event):void {
var f:Feature;
xml = new XML(URLLoader(event.target).data);
_features = [];
- _tags = { relation:{}, way:{}, node:{} };
+ _tags = { relation:{}, way:{}, node:{} };
for each(var feature:XML in xml.feature) {
f=new Feature(this,feature);
- _features.push(f);
- for each (var tag:Object in f.tags) {
- if (f.isType('line') || f.isType('area')) { addToTagList('way',tag); }
- if (f.isType('relation')) { addToTagList('relation',tag); }
- if (f.isType('point')) { addToTagList('node',tag); }
- }
- }
+ _features.push(f);
+ for each (var tag:Object in f.tags) {
+ if (f.isType('line') || f.isType('area')) { addToTagList('way',tag); }
+ if (f.isType('relation')) { addToTagList('relation',tag); }
+ if (f.isType('point')) { addToTagList('node',tag); }
+ }
+ }
_categories = new Array();
for each(var catXML:XML in xml.category) {
}
dispatchEvent(new Event("featuresLoaded"));
}
-
+ /** Add one item to tagList index, which will end up being a list like: ["way"]["highway"]["residential"] */
private function addToTagList(type:String,tag:Object):void {
if (tag.v=='*') { return; }
if (!_tags[type][tag.k]) { _tags[type][tag.k]=new Array(); }
if (_tags[type][tag.k].indexOf(tag.v)==-1) { _tags[type][tag.k].push(tag.v); }
}
+ /** Indicates whether the XML file has finished being loaded. */
public function hasLoaded():Boolean {
return xml != null;
}
+ /** Find the first Feature (template) that matches the given Entity (actual existing object in the map).
+ *
+ * This is done to provide appropriate editing controls that correspond to the selected Entity.
+ *
+ * @param entity The Entity to try and match against.
+ * @return The first suitable Feature, or null. */
+
public function findMatchingFeature(entity:Entity):Feature {
if ( xml == null )
return null;
if ( !match ) break;
}
- // check for matching withins
- if (match) {
- for each (var within:Object in feature.withins) {
- match = entity.countParentObjects(within) >= (within.minimum ? within.minimum : 1);
- if (!match) { break; }
- }
- }
+ // check for matching withins
+ if (match) {
+ for each (var within:Object in feature.withins) {
+ match = entity.countParentObjects(within) >= (within.minimum ? within.minimum : 1);
+ if (!match) { break; }
+ }
+ }
if (match) {
return feature;
- }
+ }
}
return null;
}
-
+
[Bindable(event="featuresLoaded")]
+ /** Array of every Category found in the map features file. */
public function get categories():Array {
if ( xml == null )
- return null;
+ return null;
return _categories;
}
[Bindable(event="featuresLoaded")]
+ /** Categories that contain at least one Feature corresponding to a certain type, such as "area" or "point".
+ *
+ * @return Filtered Array of Category objects, possibly empty. null if XML file is not yet processed.
+ */
+
public function getCategoriesForType(type:String):Array {
if ( xml == null )
return null;
- if ( type == null || type == "" )
+ if ( type == null || type == "" )
return []; //_categories;
-
+
var filteredCategories:Array = new Array();
for each( var cat:Category in _categories ) {
if ( cat.getFeaturesForType(type).length > 0 )
}
[Bindable(event="featuresLoaded")]
+ /** All features.
+ *
+ * @return null if XML file not yet processed. */
public function get features():Array {
if ( xml == null )
- return null;
+ return null;
return _features;
}
[Bindable(event="featuresLoaded")]
+ /** All Features of type "point".
+ *
+ * @return null if XML file not yet processed.
+ */
+
public function get pois():Array {
if (xml == null )
return null;
var pois:Array = [];
var counter:int = 0;
for each ( var feature:Feature in _features ) {
- if (feature.isType("point")) {
- pois.push(feature);
- }
+ if (feature.isType("point")) {
+ pois.push(feature);
+ }
}
return pois;
}
- [Bindable(event="featuresLoaded")]
- public function getAutoCompleteKeys(type:String):Array {
- var list:Array=[];
- var a:Array=[];
+ [Bindable(event="featuresLoaded")]
+ /** A list of all Keys for all features of the given type, sorted.
+ * @example <listing version="3.0">getAutoCompleteKeys ("way")</listing>
+ * Returns: [{name: "building"}, {name: "highway"}...]
+ */
+ public function getAutoCompleteKeys(type:String):Array {
+ var list:Array=[];
+ var a:Array=[];
+
+ for (var k:String in _tags[type]) { list.push(k); }
+ list.sort();
+
+ for each (k in list) { a.push( { name: k } ); }
+ return a;
+ }
- for (var k:String in _tags[type]) { list.push(k); }
- list.sort();
+ [Bindable(event="featuresLoaded")]
+ /** Get all the possible values that could go with a given key and type.
+ * TODO: Include values previously entered by the user, but not existent in XML file.
+ *
+ * @example <listing version="3.0">getAutoCompleteValues("way", "highway")</listing>
+ * Returns: [{name: "motorway"}, {name: "residential"}...]
+ */
+ public function getAutoCompleteValues(type:String,key:String):Array {
+ var a:Array=[];
+ if (_tags[type][key]) {
+ _tags[type][key].sort();
+ for each (var v:String in _tags[type][key]) { a.push( { name: v } ); }
+ }
+ return a;
+ }
- for each (k in list) { a.push( { name: k } ); }
- return a;
- }
-
- [Bindable(event="featuresLoaded")]
- public function getAutoCompleteValues(type:String,key:String):Array {
- var a:Array=[];
- if (_tags[type][key]) {
- _tags[type][key].sort();
- for each (var v:String in _tags[type][key]) { a.push( { name: v } ); }
- }
- return a;
- }
-
}
}