ADD @vmatch attribute to <tag> element of feature in map_features.xml. Matching tag...
[potlatch2.git] / net / systemeD / potlatch2 / mapfeatures / Feature.as
index 39b46d0..0dda070 100644 (file)
@@ -2,60 +2,85 @@ package net.systemeD.potlatch2.mapfeatures {
 
     import flash.events.EventDispatcher;
     import flash.events.Event;
+    import flash.net.*;
+    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 _editors:Array;
 
+        [Embed(source="../../../../embedded/missing_icon.png")]
+        [Bindable]
+        public var missingIconCls:Class;
+
+
+        /** Create this Feature from an XML subtree. */
         public function Feature(mapFeatures:MapFeatures, _xml:XML) {
             this.mapFeatures = mapFeatures;
             this._xml = _xml;
-            parseTags();
+            parseConditions();
             parseEditors();
         }
-        
-        private function parseTags():void {
-            _tags = new Array();
-            
+
+        private function parseConditions():void {
+            _tags = [];
+           _withins = [];
+
+                       // parse tags
             for each(var tag:XML in definition.tag) {
-                var tagObj:Object = new Object();
-                tagObj["k"] = tag.@k;
-                tagObj["v"] = tag.@v;
-                _tags.push(tagObj);
+                _tags.push( { k:String(tag.@k), v:String(tag.@v), vmatch:String(tag.@vmatch)} );
+            }
+
+                       // parse 'within'
+            for each(var within:XML in definition.within) {
+                               var obj:Object= { entity:within.@entity, k:within.@k };
+                               if (within.attribute('v'      ).length()>0) { obj['v'      ]=within.@v;       }
+                               if (within.attribute('minimum').length()>0) { obj['minimum']=within.@minimum; }
+                               if (within.attribute('role'   ).length()>0) { obj['role'   ]=within.@role;    }
+                _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;
             var sortOrderStr:String = inputXML.@priority;
+//          _tags.push( { k:String(inputXML.@key) } ); /* add the key to tags so that e.g. addr:housenumber shows up on autocomplete */
             var editor:EditorFactory = EditorFactory.createFactory(inputType, inputXML);
             if ( editor != null ) {
                 editor.presence = Presence.getPresence(presenceStr);
@@ -63,39 +88,58 @@ package net.systemeD.potlatch2.mapfeatures {
                 _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 (@name), or null if none. */
         public function get name():String {
-            return _xml.@name;
+                       if (_xml.attribute('name').length()>0) { return _xml.@name; }
+                       return null;
         }
-    
+
         [Bindable(event="imageChanged")]
-        public function get image():String {
+        /** An icon for the feature (from icons[0]/@image). If none is defined, return default "missing icon". */
+        public function get image():ByteArray {
             var icon:XMLList = _xml.icon;
+            var imageURL:String = null;
+            var img:ByteArray;
 
             if ( icon.length() > 0 && icon[0].hasOwnProperty("@image") )
-                return icon[0].@image;
-            else
-                return null;
+                imageURL = icon[0].@image;
+
+            if ( imageURL != null ) {
+                img = CachedDataLoader.loadData(imageURL, imageLoaded);
+            }
+            if (img) {
+              return img;
+            }
+            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);
-        }   
+        }
 
+        /** Convert the contents of the "icon" tag as an HTML string, with variable substitution. */
         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]);
@@ -104,32 +148,41 @@ package net.systemeD.potlatch2.mapfeatures {
             txt = txt.replace(variablesPattern, replaceTag);
             return txt;
         }
-        
+
+        /** Basic HTML escaping. */
         public static function htmlEscape(str:String):String {
             var newStr:String = str.replace(/&/g, "&amp;");
             newStr = newStr.replace(/</g, "&lt;");
             newStr = newStr.replace(/>/g, "&gt;");
-            newStr = newStr.replace(/"/g, "&quot;");
-            newStr = newStr.replace(/'/g, "&apos;");
+            newStr = newStr.replace(/"/g, "&quot;");   // "
+            newStr = newStr.replace(/'/g, "&apos;");   // '
             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) )
@@ -137,11 +190,25 @@ package net.systemeD.potlatch2.mapfeatures {
             }
             return null;
         }
-        
+
+        /** Whether the feature is of the given type (point, line/area, relation). */
         public function isType(type:String):Boolean {
-            return _xml.elements(type).length() > 0;
+            if (type=='area') {
+                           return (_xml.elements(type).length() > 0) || (_xml.elements('line').length() > 0);
+            } else {
+                           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;
         }
-        
     }
 }