c6ab83316145c8746f752d6f06fa7ece9400a983
[potlatch2.git] / net / systemeD / potlatch2 / controller / SelectedMultiple.as
1 package net.systemeD.potlatch2.controller {
2         import flash.events.*;
3         
4         import net.systemeD.halcyon.AttentionEvent;
5         import net.systemeD.halcyon.connection.*;
6         import net.systemeD.halcyon.connection.actions.MergeWaysAction;
7     import net.systemeD.halcyon.MapPaint;
8
9         public class SelectedMultiple extends ControllerState {
10                 protected var initSelection:Array;
11                 
12                 public function SelectedMultiple(sel:Array, layer:MapPaint=null) {
13                         if (layer) this.layer=layer;
14                         initSelection=sel.concat();
15                 }
16
17                 override public function processMouseEvent(event:MouseEvent, entity:Entity):ControllerState {
18                         if (event.type==MouseEvent.MOUSE_MOVE || event.type==MouseEvent.ROLL_OVER || event.type==MouseEvent.MOUSE_OUT) { return this; }
19                         var focus:Entity = getTopLevelFocusEntity(entity);
20
21                         if ( event.type == MouseEvent.MOUSE_DOWN && entity && event.ctrlKey ) {
22                                 // modify selection
23                                 layer.setHighlight(entity, { selected: toggleSelection(entity) });
24                                 controller.updateSelectionUI();
25
26                                 if (selectCount>1) { return this; }
27                                 return controller.findStateForSelection(selection);
28
29                         } else if ( event.type == MouseEvent.MOUSE_UP && selection.indexOf(focus)>-1 ) {
30                                 return this;
31                         }
32                         var cs:ControllerState = sharedMouseEvents(event, entity);
33                         return cs ? cs : this;
34                 }
35
36                 override public function processKeyboardEvent(event:KeyboardEvent):ControllerState {
37                         if (event.keyCode==74) return mergeWays();      // 'J'
38                         if (event.keyCode==75) return createMultipolygon();
39                         var cs:ControllerState = sharedKeyboardEvents(event);
40                         return cs ? cs : this;
41                 }
42                 
43                 public function mergeWays():ControllerState {
44                         var changed:Boolean;
45                         var waylist:Array=selectedWays;
46                         var tagConflict:Boolean=false; 
47                         var relationConflict:Boolean=false;
48                         var mergers:uint=0;
49                         do {
50                                 // ** FIXME - we should have one CompositeUndoableAction for the whole caboodle,
51                                 // but that screws up the execution order and can make the merge not work
52                                 var undo:CompositeUndoableAction = new CompositeUndoableAction("Merge ways");
53                                 changed=tryMerge(waylist, undo);
54                                 if (changed) mergers++;
55                                 MainUndoStack.getGlobalStack().addAction(undo);
56                                 tagConflict     ||= MergeWaysAction.lastTagsMerged;
57                                 relationConflict||= MergeWaysAction.lastRelationsMerged;
58
59                         } while (changed==true);
60
61             if (mergers>0) {                                    
62                             var msg:String = 1 + mergers + " ways merged";
63                 if (tagConflict && relationConflict) msg+=": check tags and relations";
64                 else if (tagConflict) msg+=": check conflicting tags";
65                 else if (relationConflict) msg+=": check relations";
66                 controller.dispatchEvent(new AttentionEvent(AttentionEvent.ALERT, null, msg));
67             }
68
69                         return controller.findStateForSelection(waylist);
70                 }
71                 
72                 private function tryMerge(waylist:Array, undo:CompositeUndoableAction):Boolean {
73                         var way1:Way, way2:Way, del:uint;
74                         for (var i:uint=0; i<waylist.length; i++) {
75                                 for (var j:uint=0; j<waylist.length; j++) {
76                                         if (waylist[i]!=waylist[j]) {
77
78                                                 // Preserve positive IDs if we can
79                                                 if (waylist[i].id < waylist[j].id && waylist[i].id >= 0) {
80                                                         way1=waylist[i]; way2=waylist[j]; del=j;
81                                                 } else {
82                                                         way1=waylist[j]; way2=waylist[i]; del=i;
83                                                 }
84
85                                                 // Merge as appropriate
86                                                 if (way1.getNode(0)==way2.getNode(0)) {
87                                                         waylist.splice(del,1);
88                                                         undo.push(new MergeWaysAction(way1,way2,0,0));
89                                                         return true;
90                                                 } else if (way1.getNode(0)==way2.getLastNode()) { 
91                                                         waylist.splice(del,1);
92                                                         undo.push(new MergeWaysAction(way1,way2,0,way2.length-1));
93                                                         return true;
94                                                 } else if (way1.getLastNode()==way2.getNode(0)) {
95                                                         waylist.splice(del,1);
96                                                         undo.push(new MergeWaysAction(way1,way2,way1.length-1,0));
97                                                         return true;
98                                                 } else if (way1.getLastNode()==way2.getLastNode()) { 
99                                                         waylist.splice(del,1);
100                                                         undo.push(new MergeWaysAction(way1,way2,way1.length-1,way2.length-1));
101                                                         return true;
102                                                 }
103                                         }
104                                 }
105                         }
106                         return false;
107                 }
108                 
109                 /** Create multipolygon from selection, or add to existing multipolygon. */
110                 
111                 public function createMultipolygon():ControllerState {
112                         var entity:Entity;
113                         var relation:Relation;
114                         var outer:Way;
115                         var inner:Way;
116                         var inners:Array=[];
117
118                         // If there's an existing outer in the selection, use that
119                         for each (entity in selection) {
120                                 if (!entity is Way) continue;
121                                 var r:Array=entity.findParentRelationsOfType('multipolygon','outer');
122                                 if (r.length) { outer=Way(entity); relation=r[0]; }
123                         }
124
125                         // Otherwise, find the way with the biggest area
126                         var largest:Number=0;
127                         if (!outer) {
128                                 for each (entity in selection) {
129                                         if (!entity is Way) continue;
130                                         if (!Way(entity).isArea()) continue;
131                                         var props:Object=layer.wayUIProperties(entity as Way);
132                                         if (props.patharea>largest) { outer=Way(entity); }
133                                 }
134                         }
135                         
136                         // If we still don't have an outer, then squawk
137                         if (!outer) {
138                                 controller.dispatchEvent(new AttentionEvent(AttentionEvent.ALERT, null, "No areas selected"));
139                                 return this;
140                         }
141                         
142                         // Identify the inners
143                         for each (entity in selection) {
144                                 if (entity==outer) continue;
145                                 if (!entity is Way) continue;
146                                 if (!Way(entity).isArea()) continue;
147                                 var node:Node=Way(entity).getFirstNode();
148                                 if (outer.pointWithin(node.lon,node.lat)) inners.push(entity);
149                         }
150                         if (inners.length==0) {
151                 controller.dispatchEvent(new AttentionEvent(AttentionEvent.ALERT, null, "Couldn't identify inner areas"));
152                                 return this;
153                         }
154
155                         // If relation exists, add any inners that aren't currently present
156                         if (relation) {
157                                 var action:CompositeUndoableAction = new CompositeUndoableAction("Add to multipolygon");
158                                 for each (inner in inners) {
159                                         if (!relation.hasMemberInRole(inner,'inner'))
160                                                 relation.appendMember(new RelationMember(inner,'inner'),action.push);
161                                 }
162                                 MainUndoStack.getGlobalStack().addAction(action);
163                                 
164                         // Otherwise, create whole new relation
165                         } else {
166                                 var memberlist:Array=[new RelationMember(outer,'outer')];
167                                 for each (inner in inners) 
168                                         memberlist.push(new RelationMember(inner,'inner'));
169                                 relation = entity.connection.createRelation( { type: 'multipolygon' }, memberlist, MainUndoStack.getGlobalStack().addAction);
170                         }
171
172                         return new SelectedWay(outer);
173                 }
174
175                 override public function enterState():void {
176                         selection=initSelection.concat();
177                         for each (var entity:Entity in selection) {
178                                 layer.setHighlight(entity, { selected: true, hover: false });
179                         }
180                         controller.updateSelectionUI();
181                         layer.setPurgable(selection,false);
182                 }
183
184                 override public function exitState(newState:ControllerState):void {
185                         layer.setPurgable(selection,true);
186                         for each (var entity:Entity in selection) {
187                                 layer.setHighlight(entity, { selected: false, hover: false });
188                         }
189                         selection = [];
190                         if (!newState.isSelectionState()) { controller.updateSelectionUI(); }
191                 }
192
193                 override public function toString():String {
194                         return "SelectedMultiple";
195                 }
196
197         }
198 }