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 d0c914258b7869e124bc6a7550e5a2e6f2278ab8..55bf6925e7f8ff0d8c4e820e148274ba8982b273 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 af09546fede8aa1a54dba4618b5f2c2c5562de0a..077492533f4356e47d4266444250dd7d85026711 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 290cb2da3251027b51ede17cceb1eb40d63bceb4..d02c1195bd9f7d70ee671c96d0dc6c8db4010529 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 3b221ce0c730b38d6265a9fba28257e3285b4158..6beaa3d478e4f8296b8d7e0bd38ecc6ea11984f9 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 dc1c3b93aec59aa3f5d7a3c970da9077a85aa0e2..80b72096851cb2180f76eda606ee84cea63c0fd8 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 05df6c9c58dca4e70578367104cedf9290304f7c..2638b115df2d7f2825ba521b54f1833d36ee95f5 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 36455f33a5bcb3c45069dafb50d3fe4eb1fe04c1..19da13d25322c0762404f56dcbd62859d1b6a28a 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 f3a50768691f84cabcd9683798c351f82160f9b3..d1c9c96e4cac3f17fa51c445f992ec6eb2152e1d 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 097833fe987b23a8456c1c176900fd914b680891..6481249a62804c02b76da930af550f2ca928cea5 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 {