speed up selection by making editors added lazily with tabs
[potlatch2.git] / net / systemeD / potlatch2 / TagViewer.mxml
1 <?xml version="1.0" encoding="utf-8"?>
2 <mx:VBox
3         xmlns:mx="http://www.adobe.com/2006/mxml"
4         xmlns:flexlib="flexlib.containers.*"
5     backgroundColor="white"
6     initialize="loadFeatures()">
7
8   <mx:ViewStack id="stack" width="100%" height="100%">
9   <mx:VBox width="100%" height="100%" label="Simple">
10     <mx:HBox borderStyle="inset" verticalAlign="middle" width="100%" paddingLeft="3" id="iconContainer">
11         <mx:Image id="iconImage"/>
12         <mx:VBox width="100%" verticalGap="1">
13           <mx:PopUpButton id="popupChange" creationComplete="initFeatureBox()" openAlways="true" width="100%"/>
14           <mx:Text condenseWhite="true" width="100%" id="iconText"/>
15         </mx:VBox>
16         <mx:LinkButton label="?" click="openDescription()" id="helpLabel"/>
17     </mx:HBox>
18     <flexlib:SuperTabNavigator id="editorStack" width="100%" height="100%" paddingLeft="2" paddingRight="2"
19         allowTabSqueezing="false" minTabWidth="10" closePolicy="close_never"
20         scrollSpeed="20" change="ensureEditorsPopulated(IndexChangedEvent(event).relatedObject as VBox)"/>
21   </mx:VBox>
22
23   <mx:VBox width="100%" height="100%" label="Advanced" initialize="checkAdvanced()" verticalGap="1">
24     <mx:Label id="advancedID">
25       <mx:htmlText><![CDATA[<i>No Selection</i>]]></mx:htmlText>
26     </mx:Label>
27
28     <mx:DataGrid editable="true" width="100%" height="75%" id="advancedTagGrid">
29             <mx:columns>
30                 <mx:DataGridColumn editable="true" dataField="key" headerText="Key"/>
31                 <mx:DataGridColumn editable="true" dataField="value" headerText="Value"/>
32             </mx:columns>
33     </mx:DataGrid>
34
35     <mx:HBox horizontalAlign="right" width="100%">
36       <mx:LinkButton label="Delete" click="removeTag()" enabled="{advancedTagGrid.selectedItem != null? true : false}"/>
37       <mx:LinkButton label="Add" click="addNewTag()"/>
38     </mx:HBox>
39     
40     <mx:DataGrid editable="true" width="100%" height="25%" id="relationsGrid"
41         doubleClickEnabled="true"
42         itemDoubleClick="editRelation(ListEvent(event).rowIndex)">
43         <mx:columns>
44             <mx:DataGridColumn editable="false" dataField="description" headerText="Relation"/>
45             <mx:DataGridColumn editable="false" dataField="id_idx" headerText="ID"/>
46             <mx:DataGridColumn editable="true" dataField="role" headerText="Role"/>
47         </mx:columns>
48     </mx:DataGrid>
49
50     <mx:HBox horizontalAlign="right" width="100%">
51       <mx:LinkButton label="Remove from" click="removeFromRelation(relationsGrid.selectedItem.id, relationsGrid.selectedItem.index)" 
52                       enabled="{relationsGrid.selectedItem != null? true : false}"/>
53       <mx:LinkButton label="Add to" click="addToRelation()"/>
54     </mx:HBox>
55
56   </mx:VBox>
57
58   </mx:ViewStack>
59
60   <mx:LinkBar dataProvider="{stack}"/>
61
62   <mx:Script><![CDATA[
63       import net.systemeD.halcyon.connection.*;
64       import net.systemeD.potlatch2.mapfeatures.*;
65
66       import mx.collections.*;
67       import mx.containers.*;
68       import mx.events.*;
69       import mx.core.*;
70       import mx.managers.PopUpManager;
71       import flash.geom.Point;
72       import flash.net.*;
73       
74       private var mapFeatures:MapFeatures;
75       private var selectedEntity:Entity;
76       private var tagDataProvider:ArrayCollection;
77       private var tw:CategorySelector = null;
78       private var feature:Feature = null;
79
80       public function setEntity(entity:Entity):void {
81           if ( selectedEntity != entity ) {
82               if ( selectedEntity != null )
83                   selectedEntity.removeEventListener(Connection.TAG_CHANGE, tagChanged);
84               selectedEntity = entity;
85               if ( selectedEntity != null )
86                   selectedEntity.addEventListener(Connection.TAG_CHANGE, tagChanged);
87           }
88
89           if ( advancedID != null )
90               setupAdvanced(entity);
91
92           refreshFeatureIcon();
93       }
94
95       private function refreshFeatureIcon():void {
96           var oldFeature:Feature = feature;
97           feature = selectedEntity == null ? null : mapFeatures.findMatchingFeature(selectedEntity);
98           if ( feature != oldFeature )
99               initialiseEditors();
100
101           if ( feature != null )
102               setFeatureIcon(selectedEntity, feature);
103           else
104               blankFeatureIcon(selectedEntity);
105       }
106
107       private function setFeatureIcon(entity:Entity, feature:Feature):void {
108           //blankFeatureIcon(entity);
109           
110           iconImage.source = feature.image;
111
112           var txt:String = feature.htmlDetails(entity);
113           iconText.htmlText = txt;
114           popupChange.label = feature.name;
115           setLimitTypes(entity);
116           tw.setSelectedFeature(feature);
117           helpLabel.visible = feature.hasHelpURL();
118       }
119       
120       private function setLimitTypes(entity:Entity):void {
121           var type:String = null;
122           if ( entity is Node )
123               type = "point";
124           else if ( entity is Way )
125               type = Way(entity).isArea() ? "area" : "line";
126           else if ( entity is Relation )
127               type = "relation";
128           tw.setLimitTypes(type);
129       }
130
131       private function blankFeatureIcon(entity:Entity):void {
132           iconImage.source = null;
133           iconText.htmlText = entity == null ?
134                "<i>Nothing selected</i>" :
135                "<b>Not recognised</b><br/>Try looking at the tags under the advanced properties";
136           popupChange.label = "unknown";
137           setLimitTypes(entity);
138           tw.setSelectedFeature(null);
139           helpLabel.visible = false;
140       }
141
142       private var tabComponents:Object = {};
143       
144       private function initialiseEditors():void {
145           editorStack.removeAllChildren();
146           if ( selectedEntity == null || feature == null )
147               return;
148           
149           var editorBox:VBox = createEditorBox();
150           editorBox.label = "Basic";
151           editorStack.addChild(editorBox);
152           
153           var tabs:Object = {};
154           tabComponents = {};
155           
156           for each (var factory:EditorFactory in feature.editors) {
157               if ( factory.presence.isEditorPresent(factory, selectedEntity, null) ) {
158                   var editor:DisplayObject = factory.createEditorInstance(selectedEntity);
159                   if ( editor != null )
160                       editorBox.addChild(editor);
161               }
162               var category:String = factory.category;
163               var tab:VBox = tabs[category];
164               if ( tab == null ) {
165                   tab = createEditorBox();
166                   tab.label = category;
167                   editorStack.addChild(tab);
168                   tabs[category] = tab;
169                   tabComponents[tab] = [];
170               }
171               var catEditor:DisplayObject = factory.createEditorInstance(selectedEntity);
172               if ( catEditor != null )
173                   tabComponents[tab].push(catEditor);
174               //    tab.addChild(catEditor);
175           }
176       }
177       
178       private function createEditorBox():VBox {
179           var box:VBox = new VBox();
180           box.percentWidth = 100;
181           box.percentHeight = 100;
182           return box;
183       }
184
185       private function ensureEditorsPopulated(tab:VBox):void {
186           var components:Array = tabComponents[tab];
187           if ( components == null || tab == null || tab.numChildren >= components.length )
188               return;
189           for each (var component:DisplayObject in components ) {
190               tab.addChild(component);
191           }
192       }
193       
194       private function checkAdvanced():void {
195           if ( selectedEntity != null )
196              setupAdvanced(selectedEntity);
197       }
198
199       private function setupAdvanced(entity:Entity):void {
200           if ( tagDataProvider == null ) {
201               tagDataProvider = new ArrayCollection();
202               advancedTagGrid.dataProvider = tagDataProvider;
203           }
204
205           tagDataProvider.removeAll();
206           
207           if ( entity == null ) {
208               advancedID.htmlText = "";
209           } else {
210               var entityText:String = "xx";
211               if ( entity is Node ) entityText = "Node";
212               else if ( entity is Way ) entityText = "Way";
213               else if ( entity is Relation ) entityText = "Relation";
214               advancedID.htmlText = entityText+": <b>"+entity.id+"</b>";
215
216               var tags:Array = entity.getTagArray();
217               tags.sortOn("key");
218               for each(var tag:Tag in tags)
219                   tagDataProvider.addItem(tag);
220           }
221           
222           if ( entity == null ) {
223               relationsGrid.dataProvider = null;
224           } else {
225               var relations:Array = [];
226               for each( var rel:Relation in entity.parentRelations ) {
227                   for each( var memberIndex:int in rel.findEntityMemberIndexes(entity)) {
228                     var props:Object = {};
229                     props["relation"] = rel;
230                     props["id"] = rel.id;
231                     props["index"] = memberIndex;
232                     props["role"] = rel.getMember(memberIndex).role;
233                     props["description"] = rel.getDescription();
234                     props["id_idx"] = rel.id + "/"+memberIndex;
235                     
236                     relations.push(props);
237                   }
238               }
239               relationsGrid.dataProvider = relations;
240           }
241       }
242
243       private function editRelation(index:uint):void {
244           trace("edit relation "+index+" "+selectedEntity.parentRelations[index]);
245           var panel:RelationEditorPanel = RelationEditorPanel(
246               PopUpManager.createPopUp(Application(Application.application), RelationEditorPanel, true));
247           panel.setRelation(selectedEntity.parentRelations[index]);
248           PopUpManager.centerPopUp(panel);
249       }
250       
251       private function tagChanged(event:TagEvent):void {
252           refreshFeatureIcon();
253           
254           if ( tagDataProvider != null ) {
255               // check to see if the key is already in our list
256               var exists:Boolean = false;
257               var tag:Tag = null;
258               var i:uint;
259               for ( i = 0; i < tagDataProvider.length && !exists; i++ ) {
260                   tag = Tag(tagDataProvider.getItemAt(i));
261                   exists = tag.key == event.key;
262               }
263               if ( !exists ) {
264                   tag = new Tag(selectedEntity, event.key, event.newValue);
265                   tagDataProvider.addItem(tag);
266                   tagDataProvider.refresh();
267               } else {
268                   if ( event.newValue == null ) {
269                       tagDataProvider.removeItemAt(i-1);
270                       tagDataProvider.refresh();
271                   } else {
272                       tagDataProvider.itemUpdated(tag, "value");
273                   }
274               }
275           }
276       }
277
278       public function loadFeatures():void {
279           mapFeatures = MapFeatures.getInstance();
280       }
281
282       public function openDescription():void {
283           trace("open description here");
284           if ( feature != null && feature.hasHelpURL() )
285               navigateToURL(new URLRequest(feature.helpURL), "potlatch_help");
286       }
287
288       public function addNewTag():void {
289           var newKey:String = "(new tag)";
290           var newTag:Tag = new Tag(selectedEntity, newKey, "(new value)");
291           tagDataProvider.addItem(newTag);
292           advancedTagGrid.editedItemPosition = {rowIndex: tagDataProvider.getItemIndex(newTag), columnIndex: 0};
293       }
294
295       public function removeTag():void {
296           var k:String = advancedTagGrid.selectedItem.key;
297           selectedEntity.setTag(k, null);
298       }
299       
300       public function addToRelation():void {
301           new RelationSelectPanel().init(selectedEntity);        
302       }
303       
304       public function removeFromRelation(id:Number, index:int):void {
305           Connection.getConnectionInstance().getRelation(id).removeMemberByIndex(index);
306       }
307       
308       public function initFeatureBox():void {
309           tw = new CategorySelector();
310           tw.addEventListener("selectedType", changeFeatureType);
311           popupChange.popUp = tw;
312       }
313       
314       public function changeFeatureType(event:Event):void {
315           if ( selectedEntity == null )
316               return;
317
318           var newFeature:Feature = tw.selectedType;
319           
320           // remove tags from the current feature
321           if ( feature != null ) {
322               for each( var oldtag:Object in feature.tags ) {
323                   selectedEntity.setTag(oldtag["k"], null);
324               }
325           }
326           
327           // set tags for new feature
328           if ( newFeature != null ) {
329               for each( var newtag:Object in newFeature.tags ) {
330                   selectedEntity.setTag(newtag["k"], newtag["v"]);
331               }
332           }
333           
334           popupChange.close();
335       }
336   ]]></mx:Script>
337 </mx:VBox>
338