ADD @vmatch attribute to <tag> element of feature in map_features.xml. Matching tag...
[potlatch2.git] / net / systemeD / potlatch2 / mapfeatures / Feature.as
index 2a25052..0dda070 100644 (file)
@@ -2,76 +2,141 @@ 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();
-            
-            for each(var inputXML:XML in definition.input) {
-                var inputType:String = inputXML.@type;
-                var presenceStr:String = inputXML.@presence;
-                var sortOrderStr:String = inputXML.@priority;
-                var editor:EditorFactory = EditorFactory.createFactory(inputType, inputXML);
-                if ( editor != null ) {
-                    editor.presence = Presence.getPresence(presenceStr);
-                    editor.sortOrder = EditorFactory.getPriority(sortOrderStr);
-                    _editors.push(editor);
+
+            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);
                 }
             }
-            
-            _editors.sortOn(["sortOrder", "name"], [Array.DESCENDING | Array.NUMERIC, Array.CASEINSENSITIVE]);
+
+            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);
+                editor.sortOrder = EditorFactory.getPriority(sortOrderStr);
+                _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 "";
 
@@ -83,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) )
@@ -116,6 +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 {
+            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;
+        }
     }
 }