Support tags-on-relation multipolygons
authorRichard Fairhurst <richard@systemeD.net>
Fri, 24 Mar 2017 11:56:55 +0000 (11:56 +0000)
committerRichard Fairhurst <richard@systemeD.net>
Fri, 24 Mar 2017 11:56:55 +0000 (11:56 +0000)
net/systemeD/halcyon/WayUI.as
net/systemeD/halcyon/connection/Entity.as
net/systemeD/halcyon/connection/Relation.as
net/systemeD/halcyon/connection/Way.as
net/systemeD/potlatch2/TagViewer.mxml
net/systemeD/potlatch2/controller/SelectedMultiple.as

index 09434f3..29a44e6 100644 (file)
@@ -239,12 +239,14 @@ package net.systemeD.halcyon {
             // Copy tags object, and add states
                        var multis:Array=entity.findParentRelationsOfType('multipolygon','outer');
                        var tags:Object;
-                       if (!entity.hasTags() && multis.length>0) {
+                       var isArea:Boolean = Way(entity).isArea();
+                       if (!entity.hasInterestingTags() && multis.length>0) {
                                tags = multis[0].getTagsCopy();
+                               isArea = true;
                        } else {
                                tags = entity.getTagsCopy();
                        }
-            setStateClass('area', Way(entity).isArea());
+            setStateClass('area', isArea);
             setStateClass('background', paint.isBackground);
             setStateClass('tiger', (entity.isUneditedTiger() && Globals.vars.highlightTiger));
             tags=applyStateClasses(tags);
index 7728dd5..19a101c 100644 (file)
@@ -393,6 +393,12 @@ package net.systemeD.halcyon.connection {
             return parents[entity] == true;
         }
 
+               /** Get all entities that the user might want to edit after clicking this.
+               *   Usually just the entity itself, but could also include related multipolygon relations. */           
+               public function getRelatedEntities():Array {
+                       return [this];
+               }
+
             /** Returns all relations that this Entity is part of, as array of {relation, position, role}, sorted by position. */
             public function get memberships():Array {
                        var list:Array=[];
index 0842cac..0332710 100644 (file)
@@ -157,6 +157,14 @@ package net.systemeD.halcyon.connection {
                        return relTags["type"] ? relTags["type"] : getType();
                }
                
+               public function hasKeysOtherThanType():Boolean {
+                       // used for detecting old-style vs new-style multipolygons
+                       for (var key:String in getTagsHash()) {
+                               if (key!='type' && key!='created_by' && key!='source') { return false; }
+                       }
+                       return true;
+               }
+
                private function getSignificantName(entity:Entity):String {
                        if (!entity.loaded || (entity is Relation)) return '';
 
index 66d922a..99862c5 100644 (file)
@@ -144,6 +144,19 @@ package net.systemeD.halcyon.connection {
             markDirty();
         }
 
+               override public function getRelatedEntities():Array {
+                       var multis:Array = findParentRelationsOfType('multipolygon');
+                       var related:Array = [this];
+                       var wayHasInteresting:Boolean = hasInterestingTags();
+                       for each (var multi:Relation in multis) {
+                               // if an old-style multipolygon, add to end of list
+                               if (wayHasInteresting && multi.hasKeysOtherThanType()) { related.push(multi); }
+                               // if a new-style multipolygon, add to start of list
+                               else { related.unshift(multi); }
+                       }
+                       return related;
+               }
+
                /** Merges another way into this one, removing the other one. */
                public function mergeWith(way:Way,topos:int,frompos:int, performAction:Function):void {
                        performAction(new MergeWaysAction(this, way, topos, frompos));
index f7bf541..928fc99 100644 (file)
                        enabled="{editorStack is TabNavigator &amp;&amp; stack.selectedIndex==0}" />
          </mx:HBox>
 
-      <mx:Label id="entityDisplayID" click="new HistoryDialog().init(selectedEntity);" paddingLeft="4" paddingBottom="3">
-        <mx:htmlText><![CDATA[<i>No Selection</i>]]></mx:htmlText>
-      </mx:Label>
+         <mx:HBox width="100%">
+             <mx:Label id="entityDisplayID" click="new HistoryDialog().init(selectedEntity);" paddingLeft="4" paddingBottom="3">
+               <mx:htmlText><![CDATA[<i>No Selection</i>]]></mx:htmlText>
+             </mx:Label>
+                 <mx:PopUpButton id="entitySelectButton" arrowButtonWidth="12" paddingLeft="0" paddingRight="0" 
+                                 width="12" height="12" enabled="false"
+                                 creationComplete="{createEntityMenu(PopUpButton(event.target));}" />
+         </mx:HBox>
+
   </mx:VBox>
 
   <!-- Multiple selection -->
       public var mapFeatures:MapFeatures;
       public var controller:EditController;
       private var selectedEntity:Entity;
+      private var relatedEntities:Array;
       private var connection:Connection;
       private var currentCategorySelector:CategorySelector;
       private var categorySelectors:Object = {};       // hash of categorySelectors for each limitType
 
       private var rowData:Object;              // relation membership reference, needed so it's accessible from relation actions menu
 
-         public function setEntity(entities:Array, layer:MapPaint=null):void {
+         /** Set the entity for the tag viewer, and refresh all the UI
+          *  If autoselect is true, then the tag viewer may choose to show a 'parent' multipolygon element instead
+          */
+         public function setEntity(entities:Array, layer:MapPaint=null, autoselect:Boolean=true):void {
                UIComponent.suspendBackgroundProcessing();
                connection=null;
 
 
                } else if (entities.length==1) {
                        // Single entity selected, so show tag panel
-                       if (firstSelected!=null && selectedEntity!=firstSelected) { 
-                               firstSelected.addEventListener(Connection.TAG_CHANGED, tagChanged, false, 0, true);
+                       var lastSelected:Entity = selectedEntity;
+                       if (autoselect) {
+                               relatedEntities = firstSelected.getRelatedEntities();
+                               selectedEntity = relatedEntities[0];
+                       } else {
+                               selectedEntity = firstSelected;
                        }
-                       selectedEntity=firstSelected;
+                       if (lastSelected!=selectedEntity) { relatedEntities[0].addEventListener(Connection.TAG_CHANGED, tagChanged, false, 0, true); }
+
+                       updateEntityMenuDataProvider();
                        connection=firstSelected.connection;
-                       if (entityDisplayID!=null) { setupAdvanced(firstSelected); }
+                       if (entityDisplayID!=null) {
+                               setupAdvanced(selectedEntity);
+                               updateEntityDisplay(selectedEntity);
+                       }
                        if (selectedEntity is Relation) { stack.addChild(membersVBox); }
+                       else if (membersVBox.parent==stack) { stack.removeChild(membersVBox); }
             if (selectedEntity is Marker && connection is BugConnection) {
               bugPanelContents.init(selectedEntity, BugConnection(connection));
               sidebar.selectedChild = bugPanel;
       private function limitType(entity:Entity):String {
           if      (entity is Node    ) return "point";
           else if (entity is Way     ) return Way(entity).isArea() ? "area" : "line";
-          else if (entity is Relation) return "relation";
+          else if (entity is Relation) return Relation(entity).getRelationType()=='multipolygon' ? "area" : "relation";
           return null;
       }
 
                stack.selectedChild=advancedContainer;
        }
 
-      private function checkAdvanced():void {
-          if ( selectedEntity != null )
-             setupAdvanced(selectedEntity);
-      }
+       private function checkAdvanced():void {
+               if ( selectedEntity != null ) {
+                       setupAdvanced(selectedEntity);
+                       updateEntityDisplay(selectedEntity);
+               }
+       }
 
       private var listeningToRelations:Array = [];
 
                        entityDisplayID.htmlText = entityText+": <b>"+entity.id+"</b> "+(entity.status ? entity.status : '');
                }
        }
+       
+       [bindable] private var entityMenuList:ArrayCollection = new ArrayCollection([]);
+
+       // Create drop-down entity select menu (for multipolygons)
+       private function createEntityMenu(button:PopUpButton):void {
+               var menu:Menu = new Menu(); 
+               menu.dataProvider = entityMenuList;
+               button.popUp = menu;
+               menu.addEventListener("itemClick", selectEntityMenu); 
+       }
+
+       // Update entries in entity select menu
+       private function updateEntityMenuDataProvider():void {
+               entityMenuList.removeAll();
+               for each (var e:Entity in relatedEntities) {
+                       entityMenuList.addItem({ label: e.getType()+" "+e.id, data: e });
+               }
+               entitySelectButton.enabled = relatedEntities.length>1;
+       }
+       
+       // An entity has been selected from the drop-down menu
+       private function selectEntityMenu(event:MenuEvent):void {
+               selectedEntity = event.item.data;
+               setEntity([selectedEntity],null,false);
+       }
 
       private function setupAdvanced(entity:Entity):void {
                if (!advancedTagGrid) advancedContainer.createComponentsFromDescriptors();      // if Flex hasn't created it, force it
                advancedTagGrid.init(entity);
-               updateEntityDisplay(entity);
                removeRelationListeners();
 
                if ( entity == null ) {
           }
 
           // remove tags from the current feature
+          // (but don't drop multipolygon tags)
           if ( feature != null ) {
               for each( var oldtag:Object in feature.tags ) {
+                  if (oldtag["k"]=='type' && oldtag["v"]=='multipolygon' && selectedEntity is Relation) { continue; }
                   if ( editableTags.indexOf(oldtag["k"]) < 0 ) {
                       selectedEntity.setTag(oldtag["k"], null, action.push);
                   }
index 4ccc48b..edcbe99 100644 (file)
@@ -117,22 +117,25 @@ package net.systemeD.potlatch2.controller {
                                controller.dispatchEvent(new AttentionEvent(AttentionEvent.ALERT, null, "Couldn't make the multipolygon"));
                                return this;
                        }
+                       var action:CompositeUndoableAction = new CompositeUndoableAction("Add to multipolygon");
 
                        // If relation exists, add any inners that aren't currently present
                        if (multi.relation) {
-                               var action:CompositeUndoableAction = new CompositeUndoableAction("Add to multipolygon");
                                for each (inner in multi.inners) {
                                        if (!multi.relation.hasMemberInRole(inner,'inner'))
                                                multi.relation.appendMember(new RelationMember(inner,'inner'),action.push);
                                }
                                MainUndoStack.getGlobalStack().addAction(action);
                                
-                       // Otherwise, create whole new relation
+                       // Otherwise, create whole new relation, and transfer the tags
                        } else {
                                var memberlist:Array=[new RelationMember(multi.outer,'outer')];
-                               for each (inner in multi.inners) 
-                                       memberlist.push(new RelationMember(inner,'inner'));
-                               layer.connection.createRelation( { type: 'multipolygon' }, memberlist, MainUndoStack.getGlobalStack().addAction);
+                               for each (inner in multi.inners) { memberlist.push(new RelationMember(inner,'inner')); }
+                               var tags:Object = multi.outer.getTagsCopy();
+                               tags['type'] = 'multipolygon';
+                               for (var key:String in tags) { multi.outer.setTag(key, null, action.push); }
+                               layer.connection.createRelation( tags, memberlist, action.push);
+                               MainUndoStack.getGlobalStack().addAction(action);
                        }
 
                        return new SelectedWay(multi.outer);