Beginnings of status API support, including WTFE and dedicated UI for selecting it
authorRichard Fairhurst <richard@systemeD.net>
Fri, 10 Jun 2011 14:51:45 +0000 (15:51 +0100)
committerRichard Fairhurst <richard@systemeD.net>
Fri, 10 Jun 2011 14:51:45 +0000 (15:51 +0100)
net/systemeD/halcyon/EntityUI.as
net/systemeD/halcyon/NodeUI.as
net/systemeD/halcyon/WayUI.as
net/systemeD/halcyon/connection/Connection.as
net/systemeD/halcyon/connection/Entity.as
net/systemeD/halcyon/connection/StatusFetcher.as [new file with mode: 0644]
net/systemeD/halcyon/connection/XMLBaseConnection.as
net/systemeD/potlatch2/options/OptionsDialog.mxml
resources/stylesheets/potlatch.css

index d5fb8f4cf6fb7cbfde5cdc2a659b8d8981580c66..4a2c62d11adf1f61fdde7741ecf2eb82970b7c02 100644 (file)
@@ -55,6 +55,7 @@ package net.systemeD.halcyon {
                        this.entity=entity;
                        this.paint=paint;
             entity.addEventListener(Connection.TAG_CHANGED, tagChanged, false, 0, true);
+            entity.addEventListener(Connection.STATUS_CHANGED, statusChanged, false, 0, true);
                        entity.addEventListener(Connection.ADDED_TO_RELATION, relationAdded, false, 0, true);
                        entity.addEventListener(Connection.REMOVED_FROM_RELATION, relationRemoved, false, 0, true);
                        entity.addEventListener(Connection.SUSPEND_REDRAW, suspendRedraw, false, 0, true);
@@ -71,6 +72,7 @@ package net.systemeD.halcyon {
                /** Remove the default event listeners. */
                protected function removeGenericEventListeners():void {
             entity.removeEventListener(Connection.TAG_CHANGED, tagChanged);
+            entity.removeEventListener(Connection.STATUS_CHANGED, statusChanged);
                        entity.removeEventListener(Connection.ADDED_TO_RELATION, relationAdded);
                        entity.removeEventListener(Connection.REMOVED_FROM_RELATION, relationRemoved);
                        entity.removeEventListener(Connection.SUSPEND_REDRAW, suspendRedraw);
@@ -123,6 +125,11 @@ package net.systemeD.halcyon {
             redraw();
         }
                
+        protected function statusChanged(event:EntityEvent):void {
+                       invalidateStyleList();
+            redraw();
+        }
+
         protected function mouseEvent(event:MouseEvent):void {
                        paint.map.entityMouseEvent(event, entity);
         }
index 204c14f8ab2f2647c17e117d76c9bd5ea405910a..4f8431f2b4a45199210361dffccc52eccd5b1492 100644 (file)
@@ -74,6 +74,7 @@ package net.systemeD.halcyon {
             setStateClass('hasTags', entity.hasInterestingTags());
             setStateClass('dupe', Node(entity).isDupe());
                        tags=applyStateClasses(tags);
+                       if (entity.status) { tags['_status']=entity.status; }
                        if (!styleList || !styleList.isValidAt(paint.map.scale)) {
                                styleList=paint.ruleset.getStyles(entity,tags,paint.map.scale); 
                        }
index 00007f0c7ae599c18c050c9ce9644acb1ae66c32..0f5ebdd46e631a5c39c68b22bda7d55fa386b2ca 100644 (file)
@@ -233,6 +233,7 @@ package net.systemeD.halcyon {
             setStateClass('area', Way(entity).isArea());
             setStateClass('tiger', (entity.isUneditedTiger() && Globals.vars.highlightTiger));
             tags=applyStateClasses(tags);
+                       if (entity.status) { tags['_status']=entity.status; }
 
                        // Keep track of maximum stroke width for hitzone
                        var maxwidth:Number=4;
index 1c5790668f065ca68a654d98131ea17cb3de3376..56f474e0e46510a8b69ceac47a9972c3f4210323 100644 (file)
@@ -12,6 +12,7 @@ package net.systemeD.halcyon.connection {
        public class Connection extends EventDispatcher {
 
                public var name:String;
+               public var statusFetcher:StatusFetcher;
         protected var apiBaseURL:String;
         protected var policyURL:String;
         protected var params:Object;
@@ -57,7 +58,8 @@ package net.systemeD.halcyon.connection {
         public static var NODE_RENUMBERED:String = "node_renumbered";
         public static var WAY_RENUMBERED:String = "way_renumbered";
         public static var RELATION_RENUMBERED:String = "relation_renumbered";
-        public static var TAG_CHANGED:String = "tag_change";
+        public static var TAG_CHANGED:String = "tag_changed";
+        public static var STATUS_CHANGED:String = "status_changed";
         public static var NODE_MOVED:String = "node_moved";
         public static var NODE_ALTERED:String = "node_altered";
         public static var WAY_NODE_ADDED:String = "way_node_added";
@@ -320,6 +322,14 @@ package net.systemeD.halcyon.connection {
             return list;
         }
 
+               public function getAllLoadedEntities():Array {
+                       var list:Array = []; var entity:Entity;
+                       for each (entity in relations) { if (entity.loaded && !entity.deleted) list.push(entity); }
+                       for each (entity in ways     ) { if (entity.loaded && !entity.deleted) list.push(entity); }
+                       for each (entity in nodes    ) { if (entity.loaded && !entity.deleted) list.push(entity); }
+                       return list;
+               }
+
         /** Returns all available relations that match all of {k1: [v1,v2,...], k2: [v1...] ...} 
         * where p1 is an array [v1, v2, v3...] */
         public function getMatchingRelationIDs(match:Object):Array {
index c975324d1d66db569c52f8f49567081897b66378..2837f39860064427abcc7ddc8049fc067b436588 100644 (file)
@@ -1,5 +1,6 @@
 package net.systemeD.halcyon.connection {
 
+    import flash.events.Event;
     import flash.events.EventDispatcher;
     import flash.utils.Dictionary;
     
@@ -17,6 +18,7 @@ package net.systemeD.halcyon.connection {
         private var modified:Boolean = false;
         private var _loaded:Boolean = true;
         private var parents:Dictionary = new Dictionary();
+               public var status:String;
         /** Lock against purging when off-screen */
         public var locked:Boolean = false;
         public var deleted:Boolean = false;
@@ -178,6 +180,14 @@ package net.systemeD.halcyon.connection {
             return copy;
         }
 
+               /** Change entity status. */
+               public function setStatus(s:String):void {
+                       if (s=='') s=null;
+                       if (s==status) return;
+                       status=s;
+                       dispatchEvent(new EntityEvent(Connection.STATUS_CHANGED,this));
+               }
+
                // Clean/dirty methods
 
         /** Check if entity is modified since last markClean(). */
@@ -394,7 +404,7 @@ package net.systemeD.halcyon.connection {
                }
 
 
-                /** Basic description of Entity - should be overriden by subclass. */
+               /** Basic description of Entity - should be overriden by subclass. */
                public function getDescription():String {
                        var basic:String=this.getType()+" "+_id;
                        if (tags['ref'] && tags['name']) { return tags['ref']+' '+tags['name']+' ('+basic+')'; }
diff --git a/net/systemeD/halcyon/connection/StatusFetcher.as b/net/systemeD/halcyon/connection/StatusFetcher.as
new file mode 100644 (file)
index 0000000..25cf8ca
--- /dev/null
@@ -0,0 +1,85 @@
+package net.systemeD.halcyon.connection {
+
+    import flash.net.*;
+    import flash.events.*;
+    import net.systemeD.halcyon.AttentionEvent;
+
+       public class StatusFetcher {
+
+               /* Class to fetch the status for newly loaded objects.
+                  At present this is rather specialised to WTFE (wtfe.gryph.de).
+               */
+
+               public var _url:String;
+               public var conn:Connection;
+               
+               private static var STATUSES:Array=["no","partial","unsure",''];
+               
+               public function StatusFetcher(url:String, connection:Connection) {
+                       _url=url;
+                       conn=connection;
+               }
+
+               public function fetch(entities:Array):void {
+                       if (entities.length==0) return;
+                       // Create URL request
+            var vars:URLVariables = new URLVariables();
+                       vars.nodes='';
+                       vars.ways='';
+                       vars.relations='';
+                       for each (var entity:Entity in entities) {
+                               if (entity is Node) vars.nodes+=entity.id+",";
+                               else if (entity is Way) vars.ways+=entity.id+",";
+                               else if (entity is Relation) vars.relations+=entity.id+",";
+                       }
+                       if (vars.ways.substr(vars.ways.length-1,1)==',') vars.ways=vars.ways.substr(0,vars.ways.length-1);
+                       if (vars.nodes.substr(vars.nodes.length-1,1)==',') vars.nodes=vars.nodes.substr(0,vars.nodes.length-1);
+                       if (vars.relations.substr(vars.relations.length-1,1)==',') vars.relations=vars.relations.substr(0,vars.relations.length-1);
+            var request:URLRequest = new URLRequest(_url);
+            request.data = vars;
+                       request.method = "POST";
+
+                       // Make request
+                       var loader:URLLoader = new URLLoader();
+                       loader.addEventListener(Event.COMPLETE, loadedFetch);
+                       loader.addEventListener(IOErrorEvent.IO_ERROR, errorOnFetchLoad);
+                       loader.addEventListener(HTTPStatusEvent.HTTP_STATUS, fetchLoadStatus);
+                       loader.load(request);
+               }
+               
+               private function loadedFetch(event:Event):void {
+                       var xml:XML=new XML(URLLoader(event.target).data);
+                       var entity:Entity, status:String;
+
+                       for each (var exml:XML in xml.*) {
+                               switch (exml.name().localName) {
+                                       case "way":             entity=conn.getWay(exml.@id); break;
+                                       case "relation":entity=conn.getRelation(exml.@id); break;
+                                       case "node":    entity=conn.getNode(exml.@id); break;
+                               }
+
+                               // **** Specific WTFE-parsing code starts here
+                               // FIXME: should be generalised
+                               //              if all users are "yes" or "auto", status is 'ok' (green)
+                               //              if first user is "no", status is 'no' (red)
+                               //              if any other users are no, status is 'partial' (softer red)
+                               //              otherwise, status is 'unsure' (yellow)
+                               var s:uint=3;   // ok
+                               for each (var user:XML in exml.user) {
+                                       if (user.@decision=='no' && user.@version=='first') { s=0; }    // no from v1
+                                       else if (user.@decision=='no') { s=Math.min(s,1); }                             // no from later version
+                                       else if (user.@decision=='undecided' || user.@decision=='anonymous') { s=Math.min(s,2); }       // unsure
+                               }
+                               status=STATUSES[s];
+                               // **** Specific WTFE-parsing code ends here
+                               entity.setStatus(status);
+                       }
+               }
+
+        private function errorOnFetchLoad(event:Event):void {
+                       conn.dispatchEvent(new AttentionEvent(AttentionEvent.ALERT, null, "Couldn't load status information"));
+        }
+        private function fetchLoadStatus(event:HTTPStatusEvent):void { }
+
+       }
+}
index 65b9f2586e3c77333618dc210a11aff83929185d..d2e5164ea0601f221f28d930abc4d8428af4e8dd 100644 (file)
@@ -26,6 +26,7 @@ package net.systemeD.halcyon.connection {
             var tags:Object;
             var node:Node, newNode:Node;
             var unusedNodes:Object={};
+                       var createdEntities:Array=[];
 
                        var minlon:Number, maxlon:Number, minlat:Number, maxlat:Number;
                        var singleEntityRequest:Boolean=true;
@@ -78,9 +79,11 @@ package net.systemeD.halcyon.connection {
                             members.push(new RelationMember(member, role));
                     }
                     
-                    if ( rel == null )
-                        setRelation(new Relation(this, id, version, tags, true, members, uid, timestamp), false);
-                    else {
+                    if ( rel == null ) {
+                        rel=new Relation(this, id, version, tags, true, members, uid, timestamp);
+                        setRelation(rel, false);
+                        createdEntities.push(rel);
+                    } else {
                         rel.update(version, tags, true, false, members, uid, timestamp);
                         sendEvent(new EntityEvent(NEW_RELATION, rel), false);
                     }
@@ -107,6 +110,7 @@ package net.systemeD.halcyon.connection {
                                        // the node didn't exist before, so create/update it
                                        newNode.parentsLoaded=newNode.within(minlon,maxlon,minlat,maxlat);
                                        setOrUpdateNode(newNode, true);
+                                       createdEntities.push(newNode);
                                } else {
                                        // the node's already in memory, but store it in case one of the new ways needs it
                                        if (newNode.within(minlon,maxlon,minlat,maxlat)) newNode.parentsLoaded=true;
@@ -122,20 +126,22 @@ package net.systemeD.halcyon.connection {
 
                 var way:Way = getWay(id);
                 if ( way == null || !way.loaded || singleEntityRequest) {
-                    var nodes:Array = [];
+                    var nodelist:Array = [];
                     for each(var nd:XML in data.nd) {
                                                var nodeid:Number=Number(nd.@ref)
                                                if (getNode(nodeid).isDeleted() && unusedNodes[nodeid]) { 
                                                        setOrUpdateNode(unusedNodes[nodeid], true); 
                                                }
-                        nodes.push(getNode(nodeid));
+                        nodelist.push(getNode(nodeid));
                                        }
                     tags = parseTags(data.tag);
                     if ( way == null ) {
-                        setWay(new Way(this, id, version, tags, true, nodes, uid, timestamp),false);
+                        way=new Way(this, id, version, tags, true, nodelist, uid, timestamp)
+                        setWay(way,false);
+                        createdEntities.push(way);
                     } else {
                                                waycount++;
-                        way.update(version, tags, true, true, nodes, uid, timestamp);
+                        way.update(version, tags, true, true, nodelist, uid, timestamp);
                         sendEvent(new EntityEvent(NEW_WAY, way), false);
                     }
                 }
@@ -144,6 +150,8 @@ package net.systemeD.halcyon.connection {
             markBboxLoaded(minlon,maxlon,maxlat,minlat);
             registerPOINodes();
             dispatchEvent(new Event(LOAD_COMPLETED));
+
+            if (statusFetcher) statusFetcher.fetch(createdEntities); 
         }
         
         protected function registerPOINodes():void {
index 8771576c8b6496e3909a206bd13fda63ed39da47..c98cab8d051e7f970dcf6cf00651e1d68b536244 100644 (file)
@@ -9,6 +9,9 @@
     import mx.events.CloseEvent;
     import mx.core.Application;
     import net.systemeD.halcyon.Globals;
+    import net.systemeD.halcyon.Map;
+    import net.systemeD.halcyon.connection.Connection;
+    import net.systemeD.halcyon.connection.StatusFetcher;
     
     public function init():void {
         PopUpManager.addPopUp(this, Application(Application.application), true);
@@ -21,6 +24,7 @@
         cursorcheck.selected = Application.application.theController.cursorsEnabled;
         tigercheck.selected = obj.data['tiger_highlighted'];
         latlongcheck.selected = Application.application.coordsbox.visible;
+               licencecheck.selected = Map(Globals.vars.root).editableLayer.connection.statusFetcher!=null;
     }
     
     private function optionsDialog_close(evt:CloseEvent):void {
         obj.flush();
     }
 
+       private function licenceToggle():void {
+               // ** FIXME: this is an inelegant patch for the short-term issue of highlighting licensing status
+               var conn:Connection=Map(Globals.vars.root).editableLayer.connection;
+               if (conn.statusFetcher) {
+                       conn.statusFetcher=null;
+               } else {
+                       conn.statusFetcher=new StatusFetcher("http://wtfe.gryph.de/api/0.6/userlist",conn);
+                       conn.statusFetcher.fetch(conn.getAllLoadedEntities());
+                       // ** FIXME: needs to also switch map style
+               }
+       }
+
               ]]>
   </mx:Script>
        <mx:CheckBox width="100%" label="Show toolbox" selected="true" id="tbcheck"
@@ -50,6 +66,9 @@
        <mx:CheckBox width="100%" label="Show mouse latitude/longitude" selected="false" id="latlongcheck" 
                change="Application.application.coordsbox.visible=latlongcheck.selected" />
 
+    <mx:CheckBox width="100%" label="Show licence status" selected="false" id="licencecheck"
+        change="licenceToggle()" />
+
   <mx:ControlBar>
     <mx:Spacer width="100%"/>
     <mx:Button label="Ok" click="PopUpManager.removePopUp(this);" styleName="titleWindowButton" />
index 625d5462f5140a9c7264bf13e9e2cacdfd4c0a1e..08864f66c2194ea596d887b4b090a2da7b5cdcdf 100644 (file)
 way .area_small_name {text-color: black; font-size: 9; text: name; text-halo: #ffffaa; text-halo-radius: 2; text-position: center;}
 @import("stylesheets/core_interactive.css");
 
+/* Test rendering for licence status */
+
+way[_status=no]       { z-index: 0; width: 20; color: red; }
+way[_status=partial]  { z-index: 0; width: 20; color: red; opacity: 0.4; }
+way[_status=maybe]    { z-index: 0; width: 20; color: orange; opacity: 0.3; }
+node[_status=no]      { z-index: 0; icon-image: square; icon-width: 15; color: red; }
+node[_status=partial] { z-index: 0; icon-image: square; icon-width: 15; color: red; opacity: 0.4; }
+node[_status=maybe]   { z-index: 0; icon-image: square; icon-width: 15; color: orange; opacity: 0.3; }