Add subcategories for map features.
authorRichard Fairhurst <richard@systemeD.net>
Fri, 16 Sep 2011 10:36:21 +0000 (11:36 +0100)
committerRichard Fairhurst <richard@systemeD.net>
Fri, 16 Sep 2011 10:36:21 +0000 (11:36 +0100)
Permits lesser-used settings (e.g. cutting/embankment) to be grouped under disclosure triangles.
This is pretty much the second part of the work to remove the clutter of having too many tabs.
Still needs some styling work!

13 files changed:
embedded/CollapsiblePanelAssets.swf [new file with mode: 0755]
net/systemeD/controls/CollapsiblePanel.as [new file with mode: 0644]
net/systemeD/potlatch2/TagViewer.mxml
net/systemeD/potlatch2/mapfeatures/EditorFactory.as
net/systemeD/potlatch2/mapfeatures/Feature.as
net/systemeD/potlatch2/mapfeatures/editors/ChoiceEditorFactory.as
net/systemeD/potlatch2/mapfeatures/editors/FreeTextEditorFactory.as
net/systemeD/potlatch2/mapfeatures/editors/SingleTagEditorFactory.as
resources/assets/features_pois.zip [new file with mode: 0644]
resources/assets/icons.zip [new file with mode: 0644]
resources/map_features.xml
resources/map_features/transport.xml
styles/Application.css

diff --git a/embedded/CollapsiblePanelAssets.swf b/embedded/CollapsiblePanelAssets.swf
new file mode 100755 (executable)
index 0000000..fd049dd
Binary files /dev/null and b/embedded/CollapsiblePanelAssets.swf differ
diff --git a/net/systemeD/controls/CollapsiblePanel.as b/net/systemeD/controls/CollapsiblePanel.as
new file mode 100644 (file)
index 0000000..4222107
--- /dev/null
@@ -0,0 +1,207 @@
+/*
+
+The MIT License
+
+Copyright (c) 2007-2008 Ali Rantakari of hasseg.org
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+*/
+
+package net.systemeD.controls {
+       
+       import flash.events.*;
+       import mx.effects.AnimateProperty;
+       import mx.events.*;
+       import mx.containers.Panel;
+       import mx.core.ScrollPolicy;
+       
+       /**
+       * The icon designating a "closed" state
+       */
+       [Style(name="closedIcon", property="closedIcon", type="Object")]
+       
+       /**
+       * The icon designating an "open" state
+       */
+       [Style(name="openIcon", property="openIcon", type="Object")]
+       
+       /**
+       * This is a Panel that can be collapsed and expanded by clicking on the header.
+       * 
+       * @author Ali Rantakari
+       */
+       public class CollapsiblePanel extends Panel {
+               
+
+
+               private var _creationComplete:Boolean = false;
+               private var _open:Boolean = true;
+               private var _openAnim:AnimateProperty;
+               
+               
+               
+               /**
+               * Constructor
+               * 
+               */
+               public function CollapsiblePanel(aOpen:Boolean = true):void
+               {
+                       super();
+                       open = aOpen;
+                       this.addEventListener(FlexEvent.CREATION_COMPLETE, creationCompleteHandler);
+               }
+               
+               
+               
+               
+               
+               
+               
+               
+               // BEGIN: event handlers                                ------------------------------------------------------------
+               
+               private function creationCompleteHandler(event:FlexEvent):void
+               {
+                       this.horizontalScrollPolicy = ScrollPolicy.OFF;
+                       this.verticalScrollPolicy = ScrollPolicy.OFF;
+                       
+                       _openAnim = new AnimateProperty(this);
+                       _openAnim.duration = 300;
+                       _openAnim.property = "height";
+                       
+                       titleBar.addEventListener(MouseEvent.CLICK, headerClickHandler);
+                       
+                       _creationComplete = true;
+               }
+               
+               private function headerClickHandler(event:MouseEvent):void { toggleOpen(); }
+               
+               private function callUpdateOpenOnCreationComplete(event:FlexEvent):void { updateOpen(); }
+               
+               // --end--: event handlers                      - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+               
+               
+               
+               
+               
+               
+               
+               
+               // BEGIN: private methods                               ------------------------------------------------------------
+               
+               // sets the height of the component without animation, based
+               // on the _open variable
+               private function updateOpen():void
+               {
+                       if (!_open) height = closedHeight;
+                       else height = openHeight;
+                       setTitleIcon();
+               }
+               
+               // the height that the component should be when open
+               private function get openHeight():Number {
+                       return measuredHeight;
+               }
+               
+               // the height that the component should be when closed
+               private function get closedHeight():Number {
+                       var hh:Number = getStyle("headerHeight");
+                       if (hh <= 0 || isNaN(hh)) hh = titleBar.height;
+                       return hh;
+               }
+               
+               // sets the correct title icon
+               private function setTitleIcon():void
+               {
+                       if (!_open) this.titleIcon = getStyle("closedIcon");
+                       else this.titleIcon = getStyle("openIcon");
+               }
+               
+               // --end--: private methods                     - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+               
+               
+               
+               
+               
+               
+               
+               
+               // BEGIN: public methods                                ------------------------------------------------------------
+               
+               
+               
+               /**
+               * Collapses / expands this block (with animation)
+               */
+               public function toggleOpen():void 
+               {
+                       if (_creationComplete && !_openAnim.isPlaying) {
+                               
+                               _openAnim.fromValue = _openAnim.target.height;
+                               if (!_open) {
+                                       _openAnim.toValue = openHeight;
+                                       _open = true;
+                                       dispatchEvent(new Event(Event.OPEN));
+                               }else{
+                                       _openAnim.toValue = _openAnim.target.closedHeight;
+                                       _open = false;
+                                       dispatchEvent(new Event(Event.CLOSE));
+                               }
+                               setTitleIcon();
+                               _openAnim.play();
+                               
+                       }
+                       
+               }
+               
+               
+               /**
+               * Whether the block is in a expanded (open) state or not
+               */
+               public function get open():Boolean {
+                       return _open;
+               }
+               /**
+               * @private
+               */
+               public function set open(aValue:Boolean):void {
+                       _open = aValue;
+                       if (_creationComplete) updateOpen();
+                       else this.addEventListener(FlexEvent.CREATION_COMPLETE, callUpdateOpenOnCreationComplete, false, 0, true);
+               }
+               
+               
+               /**
+               * @private
+               */
+               override public function invalidateSize():void {
+                       super.invalidateSize();
+                       if (_creationComplete)
+                               if (_open && !_openAnim.isPlaying) this.height = openHeight;
+               }
+               
+               
+               // --end--: public methods                      - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+               
+               
+       }
+
+}
+
index d0c9142..55bf692 100644 (file)
       import net.systemeD.halcyon.MapPaint;
       import net.systemeD.potlatch2.mapfeatures.*;
       import net.systemeD.potlatch2.utils.*;
+      import net.systemeD.controls.CollapsiblePanel;
 
       import mx.collections.*;
       import mx.containers.*;
       }
 
       private var tabComponents:Object = {};
+      private var subpanelComponents:Object = {};
 
       private function initialiseEditors():void {
-          editorStack.removeAllChildren();
-          if ( selectedEntity == null || feature == null )
-              return;
-
-          var editorBox:VBox = createEditorBox();
-          editorBox.label = "Basic";
-          editorBox.icon=tabIconBasic;
-          editorStack.addChild(editorBox);
+               editorStack.removeAllChildren();
+               if ( selectedEntity == null || feature == null )
+                       return;
+
+               var editorBox:VBox = createEditorBox();
+               editorBox.label = "Basic";
+               editorBox.icon=tabIconBasic;
+               editorStack.addChild(editorBox);
+
+               var tabs:Object = {};
+               tabComponents = {};
+               var subpanels:Object = {};
+               subpanelComponents = {};
+
+               // ** FIXME: we should do this so that the tabs are always in the same order
+               for each (var factory:EditorFactory in feature.editors) {
+                       if ( factory.presence.isEditorPresent(factory, selectedEntity, null) ) {
+                               var editor:DisplayObject = factory.createEditorInstance(selectedEntity);
+                               if (editor) editorBox.addChild(editor);
+                       }
 
-          var tabs:Object = {};
-          tabComponents = {};
+                       var catEditor:DisplayObject = factory.createEditorInstance(selectedEntity);
+                       if (!catEditor) continue;
+
+                       // Create tab if it doesn't already exist
+                       var category:String = factory.category;
+                       if (category=='') continue;
+                       var tab:VBox = tabs[category];
+                       if (tab == null) {
+                               tab = createEditorBox();
+                               tab.label = category;
+                               if (tabIcons[category]) tab.icon=tabIcons[category];
+                               editorStack.addChild(tab);
+                               tabs[category] = tab;
+                               tabComponents[tab] = [];
+                       }
 
-          // ** FIXME: we should do this so that the tabs are always in the same order
-          for each (var factory:EditorFactory in feature.editors) {
-              if ( factory.presence.isEditorPresent(factory, selectedEntity, null) ) {
-                  var editor:DisplayObject = factory.createEditorInstance(selectedEntity);
-                  if ( editor != null )
-                      editorBox.addChild(editor);
-              }
-              var category:String = factory.category;
-              if (category!='') {
-                     var tab:VBox = tabs[category];
-                     if ( tab == null) {
-                         tab = createEditorBox();
-                         tab.label = category;
-                         if (tabIcons[category]) tab.icon=tabIcons[category];
-                         editorStack.addChild(tab);
-                         tabs[category] = tab;
-                         tabComponents[tab] = [];
-                     }
-                     var catEditor:DisplayObject = factory.createEditorInstance(selectedEntity);
-                     if ( catEditor != null )
-                         tabComponents[tab].push(catEditor);
-                     //    tab.addChild(catEditor);
-              }
-          }
+                       // Create subcategory panel if needed
+                       if (factory.subcategory) {
+                               var subcategory:String = factory.subcategory;
+                               if (!subpanels[category]) { subpanels[category]={}; }
+                               var subpanel:CollapsiblePanel = subpanels[category][subcategory];
+                               if (!subpanel) {
+                                       subpanel=new CollapsiblePanel(false);
+                                       subpanel.percentWidth=100;
+                                       subpanel.styleName="subcategoryPanel";
+                                       subpanel.title=subcategory;
+                                       subpanels[category][subcategory]=subpanel;
+                                       tabComponents[tab].push(subpanel);
+                               }
+                               subpanel.addChild(catEditor);
+                       } else {
+                               tabComponents[tab].push(catEditor);
+                       }
+               }
       }
 
       private function createEditorBox():VBox {
index af09546..0774925 100644 (file)
@@ -34,14 +34,15 @@ package net.systemeD.potlatch2.mapfeatures {
         }
 
         /** Translates a priority string ("highest") to a const (PRIORITY_HIGHEST). */
-        public static function getPriority(priority:String):uint {
+        public function getPriority(priority:String):uint {
+                       var base:uint=subcategory ? 0 : 11;
             switch ( priority ) {
-            case "highest": return PRIORITY_HIGHEST;
-            case "high": return PRIORITY_HIGHEST;
-            case "normal": return PRIORITY_NORMAL;
-            case "low": return PRIORITY_LOW;
-            case "lowest": return PRIORITY_LOWEST;
-            default: return PRIORITY_NORMAL;
+            case "highest": return PRIORITY_HIGHEST+base;
+            case "high": return PRIORITY_HIGH+base;
+            case "normal": return PRIORITY_NORMAL+base;
+            case "low": return PRIORITY_LOW+base;
+            case "lowest": return PRIORITY_LOWEST+base;
+            default: return PRIORITY_NORMAL+base;
             }
         }
 
@@ -54,6 +55,9 @@ package net.systemeD.potlatch2.mapfeatures {
         /** Default category: "Standard" */
         public var category:String = "Standard";
 
+        /** Optional subcategory (rendered as a collapsible panel) */
+        public var subcategory:String;
+
         private var _name:String;
         private var _description:String;
 
@@ -62,6 +66,7 @@ package net.systemeD.potlatch2.mapfeatures {
             _name = String(inputXML.@name);
             _description = String(inputXML.@description);
             category = String(inputXML.@category);
+            subcategory = String(inputXML.@subcategory);
         }
 
         /** Whether the tags on an entity correspond to those for the edit control. By default, returns true - must be overriden by more useful behaviour. */
index 290cb2d..d02c119 100644 (file)
@@ -89,7 +89,7 @@ package net.systemeD.potlatch2.mapfeatures {
             var editor:EditorFactory = EditorFactory.createFactory(inputType, inputXML);
             if ( editor != null ) {
                 editor.presence = Presence.getPresence(presenceStr);
-                editor.sortOrder = EditorFactory.getPriority(sortOrderStr);
+                editor.sortOrder = editor.getPriority(sortOrderStr);
                 _editors.push(editor);
             }
         }
index 3b221ce..6beaa3d 100644 (file)
@@ -9,7 +9,7 @@ package net.systemeD.potlatch2.mapfeatures.editors {
            public var choices:Array;
         
         public function ChoiceEditorFactory(inputXML:XML) {
-            super(inputXML);
+            super(inputXML,"horizontal");
             
             choices = [];
             for each( var choiceXML:XML in inputXML.choice ) {
index dc1c3b9..80b7209 100644 (file)
@@ -8,7 +8,7 @@ package net.systemeD.potlatch2.mapfeatures.editors {
            private var _notPresentText:String;
         
         public function FreeTextEditorFactory(inputXML:XML) {
-            super(inputXML);
+            super(inputXML,"horizontal");
             _notPresentText = inputXML.hasOwnProperty("@absenceText") ? String(inputXML.@absenceText) : "Unset";
         }
         
index 05df6c9..2638b11 100644 (file)
@@ -8,10 +8,11 @@ package net.systemeD.potlatch2.mapfeatures.editors {
            private var tagKey:String;
                private var boxDirection:String;
         
-        public function SingleTagEditorFactory(inputXML:XML) {
+        public function SingleTagEditorFactory(inputXML:XML, defaultLayout:String="vertical") {
             super(inputXML);
             tagKey = inputXML.@key;
-                       boxDirection = inputXML.@layout=='horizontal' ? 'horizontal' : 'vertical';
+                       boxDirection = inputXML.@layout;
+                       if (!boxDirection) { boxDirection=defaultLayout; }
         }
         
         override public function areTagsMatching(entity:Entity):Boolean {
diff --git a/resources/assets/features_pois.zip b/resources/assets/features_pois.zip
new file mode 100644 (file)
index 0000000..7d98cad
Binary files /dev/null and b/resources/assets/features_pois.zip differ
diff --git a/resources/assets/icons.zip b/resources/assets/icons.zip
new file mode 100644 (file)
index 0000000..f3e5a5d
Binary files /dev/null and b/resources/assets/icons.zip differ
index 36455f3..19da13d 100644 (file)
@@ -45,7 +45,7 @@
   </inputSet>
 
   <inputSet id="designation">
-    <input type="freetext" category="Details" presence="onTagMatch" description="Official designation or classification" name="Designation" key="designation"/>
+    <input type="freetext" category="Details" presence="onTagMatch" description="Official designation or classification" name="Designation" key="designation" priority="lowest" />
   </inputSet>
 
   <inputSet id="common">
         name="Name" category="Details" priority="highest"
         key="name" description="The most common name"/>
     <input type="freetext" presence="onTagMatch"
-        name="International Name" category="Details"
+        name="International Name" category="Details" subcategory="Additional names"
         key="int_name" description="The internationally recognised name"/>
     <input type="freetext" presence="onTagMatch"
-        name="Historical Name" category="Details" priority="low"
+        name="Historical Name" category="Details" subcategory="Additional names" priority="low"
         key="old_name" description="The historic or previous name"/>
     <input type="freetext" presence="onTagMatch"
-        name="Alternative Name" category="Details" priority="low"
+        name="Alternative Name" category="Details" subcategory="Additional names" priority="low"
         key="alt_name" description="An alternative, currently used, name"/>
   </inputSet>
 
         name="Reference" category="Details" priority="high"
         key="ref" description="The official reference number"/>
     <input type="freetext" presence="onTagMatch"
-        name="International Reference" category="Details"
+        name="International Reference" category="Details" subcategory="Additional names" 
         key="int_ref" description="The official international reference number"/>
     <input type="freetext" presence="onTagMatch"
-        name="Old Reference" category="Details" priority="low"
+        name="Old Reference" category="Details" subcategory="Additional names" priority="low"
         key="old_ref" description="The historic or previous reference number"/>
   </inputSet>
 
   <inputSet id="roadPhysical">
     <input type="freetext" presence="onTagMatch"
-        name="Width" category="Details"
+        name="Width" category="Details" subcategory="Physical" 
         key="width" description="Width of the road" layout="horizontal"/>
     <input type="choice" presence="onTagMatch"
         name="Surface" category="Details" description="Type of road surface"
     <inputSet ref="tunnel"/>
     <inputSet ref="embankment-cutting"/>
     <!-- not sure which category best suits put area=yes -->
-    <input type="checkbox" presence="onTagMatch" category="Restrictions" key="area" name="Open area" description="The way is a large open space, like at a dock, where vehicles can move anywhere within the space, rather than just along the edge." />
+    <input type="checkbox" presence="onTagMatch" category="Details" subcategory="Physical" key="area" name="Open area" description="The way is a large open space, like at a dock, where vehicles can move anywhere within the space, rather than just along the edge." />
   </inputSet>
 
   <inputSet id="roadLanes">
   <inputSet id="tunnel">
     <!-- Not ideal, used for non-roads too. -->  
     <input type="choice" presence="onTagMatch"
-        name="Tunnel" category="Details" description="Road goes into a tunnel"
+        name="Tunnel" category="Details" subcategory="Physical" description="Road goes into a tunnel"
         key="tunnel" layout="horizontal">
       <choice value="yes" text="Tunnel" description="Generic tunnel"/>
     </input>
 
   <inputSet id="embankment-cutting">
     <input type="choice"
-           name="Embankment" category="Details" description="Road supported on a raised bed of earth and rock."
+           name="Embankment" category="Details" subcategory="Physical" description="Road supported on a raised bed of earth and rock."
            key="embankment" layout="horizontal">
       <choice value="yes" text="Embankment"/>
     </input>
     <input type="choice"
-           name="Cutting" category="Details" description="Road carved out of hill on one or both sides."
+           name="Cutting" category="Details" subcategory="Physical" description="Road carved out of hill on one or both sides."
            key="cutting" layout="horizontal">
       <choice value="yes" text="Cutting"/>
     </input>
   </inputSet>
 
   <inputSet id="rail-electrification">
-    <input type="choice" name="Electrified" category="Details" description="Is the track electrified (whether by 3rd rail, overhead wires, etc)?"
+    <input type="choice" name="Electrified" category="Details" subcategory="Electrification" description="Is the track electrified (whether by 3rd rail, overhead wires, etc)?"
            key="electrified">
       <choice value="yes" text="Yes"/>
       <choice value="no" text="No"/>
     </input>
-    <input type="choice" name="Voltage" category="Details" description="Nominal voltage of electric wires"
+    <input type="choice" name="Voltage" category="Details" subcategory="Electrification" description="Nominal voltage of electric wires"
            key="voltage" presence="withCategory">
       <choice value="600" text="600V"/>
       <choice value="750" text="750V"/>
       <choice value="15000" text="15kV"/>
       <choice value="25000" text="25kV"/>
     </input>
-    <input type="choice" name="Frequency" category="Details" description="Frequency in Hertz of alternating current power supply"
+    <input type="choice" name="Frequency" category="Details" subcategory="Electrification" description="Frequency in Hertz of alternating current power supply"
            key="frequency" presence="withCategory">
       <choice value="0" text="DC"/>
       <choice value="16.67" text="16.67 Hz"/>
     </input>
   </inputSet>
 
-  <inputSet id="rail-usage">
-    <input type="choice" name="Usage" category="Usage" description="Main use of the line" key="usage">
-      <choice value="main" text="Main line" description="The principal artery of a rail system."/>
-      <choice value="branch" text="Branch line" description="A secondary line, branching off a main line."/>
-      <choice value="industrial" text="Industrial"/>
-      <choice value="tourism" text="Tourism" />
-      <choice value="military" text="Military"/>
-    </input>
-  </inputSet>
-
   <inputSet id="fee">
     <input type="freetext" presence="onTagMatch" category="Restrictions" description="The charge/cost of using this amenity" name="Fee" key="fee"/>
   </inputSet>
index f3a5076..d1c9c96 100644 (file)
@@ -13,7 +13,6 @@
         <inputSet ref="tunnel"/>
         <inputSet ref="embankment-cutting"/>
         <inputSet ref="rail-electrification"/>
-        <inputSet ref="rail-usage"/>
         <inputSet ref="common"/>
       </feature>
     
index 097833f..6481249 100644 (file)
@@ -233,6 +233,33 @@ mx|ApplicationControlBar {
 
 .accordionHeader { fillColors: #888888, #999999; fillAlphas: 1,1; color: white; fontWeight: bold; fontSize: 11; }
 
+/* Simple tag view - panels for subcategories */
+
+.subcategoryPanel {
+       borderThicknessLeft: 1;
+       borderThicknessTop: 1;
+       borderThicknessBottom: 1;
+       borderThicknessRight: 1;
+       borderColor: gray;
+       borderAlpha: 1;
+       dropShadowEnabled: false;
+
+       backgroundColor: gray;
+       backgroundAlpha: 0.2;
+
+       headerHeight: 20;
+       headerColors: #d9d9d9, #ffffff;
+
+       paddingTop: 4;
+       paddingLeft: 4;
+       paddingRight: 4;
+       paddingBottom: 4;
+       verticalGap: 4;
+
+       openIcon: Embed(source="../embedded/CollapsiblePanelAssets.swf", symbol="CollapseButtonDown");
+       closedIcon: Embed(source="../embedded/CollapsiblePanelAssets.swf", symbol="CollapseButtonOver");
+}
+
 /* Toolbox */
 
 .theToolBox {