source: osm/applications/editors/josm/plugins/utilsplugin/UtilsPlugin/SuperSelectionAction.java@ 5075

Last change on this file since 5075 was 5075, checked in by gabriel, 18 years ago

Import the latest upstream version of utilsplugin.

File size: 9.6 KB
Line 
1package UtilsPlugin;
2
3import static org.openstreetmap.josm.tools.I18n.tr;
4
5import org.openstreetmap.josm.actions.mapmode.*;
6
7import java.awt.Point;
8import java.awt.Rectangle;
9import java.awt.event.KeyEvent;
10import java.awt.event.MouseEvent;
11import java.util.Collection;
12import java.util.HashMap;
13import java.util.HashSet;
14import java.util.LinkedList;
15import java.util.Map;
16
17import org.openstreetmap.josm.Main;
18import org.openstreetmap.josm.actions.GroupAction;
19import org.openstreetmap.josm.data.osm.Node;
20import org.openstreetmap.josm.data.osm.OsmPrimitive;
21import org.openstreetmap.josm.data.osm.Segment;
22import org.openstreetmap.josm.gui.MapFrame;
23import org.openstreetmap.josm.gui.NavigatableComponent;
24import org.openstreetmap.josm.gui.SelectionManager;
25import org.openstreetmap.josm.gui.SelectionManager.SelectionEnded;
26import org.openstreetmap.josm.tools.ImageProvider;
27
28/**
29 * This MapMode enables the user to easy make a selection of different objects.
30 *
31 * The selected objects are drawn in a different style.
32 *
33 * Holding and dragging the left mouse button draws an selection rectangle.
34 * When releasing the left mouse button, all objects within the rectangle get
35 * selected.
36 *
37 * When releasing the left mouse button while the right mouse button pressed,
38 * nothing happens (the selection rectangle will be cleared, however).
39 *
40 * When releasing the mouse button and one of the following keys was hold:
41 *
42 * If Alt key was hold, select all objects that are touched by the
43 * selection rectangle. If the Alt key was not hold, select only those objects
44 * completly within (e.g. for ways mean: only if all nodes of the way are
45 * within).
46 *
47 * If Shift key was hold, the objects are added to the current selection. If
48 * Shift key wasn't hold, the current selection get replaced.
49 *
50 * If Ctrl key was hold, remove all objects under the current rectangle from
51 * the active selection (if there were any). Nothing is added to the current
52 * selection.
53 *
54 * Alt can be combined with Ctrl or Shift. Ctrl and Shift cannot be combined.
55 * If both are pressed, nothing happens when releasing the mouse button.
56 *
57 * The user can also only click on the map. All total movements of 2 or less
58 * pixel are considered "only click". If that happens, the nearest Node will
59 * be selected if there is any within 10 pixel range. If there is no Node within
60 * 10 pixel, the nearest Segment (or Street, if user hold down the Alt-Key)
61 * within 10 pixel range is selected. If there is no Segment within 10 pixel
62 * and the user clicked in or 10 pixel away from an area, this area is selected.
63 * If there is even no area, nothing is selected. Shift and Ctrl key applies to
64 * this as usual. For more, @see MapView#getNearest(Point, boolean)
65 *
66 * @author imi
67 */
68public class SuperSelectionAction extends MapMode implements SelectionEnded {
69
70 enum Mode {select, straight}
71 private final Mode mode;
72
73 public static class Group extends GroupAction {
74 public Group(MapFrame mf) {
75 super(KeyEvent.VK_S,0);
76 putValue("help", "Action/Selection");
77 actions.add(new SuperSelectionAction(mf, tr("Selection"), Mode.select, tr("Select objects by dragging or clicking.")));
78 actions.add(new SuperSelectionAction(mf, tr("Straight line"), Mode.straight, tr("Select objects in a straight line.")));
79 setCurrent(0);
80// System.out.println( "In SuperSelectionAction!!!" );
81 }
82 }
83
84
85 /**
86 * The SelectionManager that manages the selection rectangle.
87 */
88 private SelectionManager selectionManager;
89
90 private Node straightStart = null;
91 private Node lastEnd = null;
92 private Collection<OsmPrimitive> oldSelection = null;
93
94 //TODO: Implement reverse references into data objects and remove this
95 private final Map<Node, Collection<Segment>> reverseSegmentMap = new HashMap<Node, Collection<Segment>>();
96
97 /**
98 * Create a new SelectionAction in the given frame.
99 * @param mapFrame The frame this action belongs to
100 */
101 public SuperSelectionAction(MapFrame mapFrame, String name, Mode mode, String desc) {
102 super(name, "selection/"+mode, desc, mapFrame, ImageProvider.getCursor("normal", "selection"));
103 this.mode = mode;
104 putValue("help", "Action/Selection/"+Character.toUpperCase(mode.toString().charAt(0))+mode.toString().substring(1));
105 this.selectionManager = new SelectionManager(this, false, mapFrame.mapView);
106 }
107
108 @Override public void enterMode() {
109 super.enterMode();
110// System.out.println( "enterMode()" );
111 if (mode == Mode.select)
112 {
113 selectionManager.register(Main.map.mapView);
114 Main.map.mapView.addMouseListener(this);
115 }
116 else {
117 Main.map.mapView.addMouseMotionListener(this);
118 Main.map.mapView.addMouseListener(this);
119 for (Segment s : Main.ds.segments) {
120 addBackReference(s.from, s);
121 addBackReference(s.to, s);
122 }
123 }
124 }
125
126 private void addBackReference(Node n, Segment s) {
127 Collection<Segment> c = reverseSegmentMap.get(n);
128 if (c == null) {
129 c = new HashSet<Segment>();
130 reverseSegmentMap.put(n, c);
131 }
132 c.add(s);
133 }
134
135 @Override public void exitMode() {
136 super.exitMode();
137// System.out.println( "exitMode()" );
138 if (mode == Mode.select)
139 {
140 selectionManager.unregister(Main.map.mapView);
141 Main.map.mapView.removeMouseListener(this);
142 }
143 else {
144 Main.map.mapView.removeMouseMotionListener(this);
145 Main.map.mapView.removeMouseListener(this);
146 reverseSegmentMap.clear();
147 }
148 }
149
150
151 /**
152 * Check the state of the keys and buttons and set the selection accordingly.
153 */
154 public void selectionEnded(Rectangle r, boolean alt, boolean shift, boolean ctrl) {
155// System.out.println( "selectionEnded() r="+r );
156 selectEverythingInRectangle(selectionManager, r, alt, shift, ctrl);
157 }
158
159 public static void selectEverythingInRectangle(SelectionManager selectionManager, Rectangle r, boolean alt, boolean shift, boolean ctrl) {
160 if (shift && ctrl)
161 return; // not allowed together
162
163 Collection<OsmPrimitive> curSel;
164 if (!ctrl && !shift)
165 curSel = new LinkedList<OsmPrimitive>(); // new selection will replace the old.
166 else
167 curSel = Main.ds.getSelected();
168
169 Collection<OsmPrimitive> selectionList = selectionManager.getObjectsInRectangle(r,alt);
170 for (OsmPrimitive osm : selectionList)
171 if (ctrl)
172 curSel.remove(osm);
173 else
174 curSel.add(osm);
175 Main.ds.setSelected(curSel);
176 Main.map.mapView.repaint();
177 }
178
179 @Override public void mouseDragged(MouseEvent e) {
180 if( mode == Mode.select )
181 return;
182// System.out.println( "mouseDragged()" );
183 Node old = lastEnd;
184 lastEnd = Main.map.mapView.getNearestNode(e.getPoint());
185 if (straightStart == null)
186 straightStart = lastEnd;
187 if (straightStart != null && lastEnd != null && straightStart != lastEnd && old != lastEnd) {
188 Collection<OsmPrimitive> path = new HashSet<OsmPrimitive>();
189 Collection<OsmPrimitive> sel = new HashSet<OsmPrimitive>();
190 path.add(straightStart);
191 calculateShortestPath(path, straightStart, lastEnd);
192 if ((e.getModifiers() & MouseEvent.CTRL_MASK) != 0) {
193 sel.addAll(oldSelection);
194 sel.removeAll(path);
195 } else if ((e.getModifiers() & MouseEvent.SHIFT_MASK) != 0) {
196 sel = path;
197 sel.addAll(oldSelection);
198 } else
199 sel = path;
200 Main.ds.setSelected(sel);
201 }
202 }
203
204 @Override public void mousePressed(MouseEvent e) {
205 if( mode == Mode.select )
206 return;
207 straightStart = Main.map.mapView.getNearestNode(e.getPoint());
208 lastEnd = null;
209 oldSelection = Main.ds.getSelected();
210// System.out.println( "mousePressed: count="+e.getClickCount() );
211 }
212
213 @Override public void mouseReleased(MouseEvent e) {
214 if( mode == Mode.select )
215 {
216 if( e.getClickCount() < 2 )
217 return;
218
219// System.out.println( "Got doubleclick!!" );
220 // We want all of the matching type under the mouse
221 // right now. The algorithm is fairly simple: we use
222 // getNearest to get the nearest object that the
223 // user would normally expect to be selected. The
224 // we do a getAllNearest which does almost what we
225 // want. We just toss out everything that's not the
226 // same type as what was selected.
227
228 NavigatableComponent nc = Main.map.mapView;
229
230 Point center = e.getPoint();
231 OsmPrimitive osm = nc.getNearest(center, (e.getModifiersEx() & MouseEvent.ALT_DOWN_MASK) != 0 );
232 Collection<OsmPrimitive> set = nc.getAllNearest(center);
233 Collection<OsmPrimitive> sel = new HashSet<OsmPrimitive>();
234 Collection<OsmPrimitive> oldsel = Main.ds.getSelected();
235
236 for (OsmPrimitive o : set )
237 {
238 if( osm.getClass() == o.getClass() )
239 sel.add( o );
240 }
241 if( (e.getModifiersEx() & MouseEvent.SHIFT_DOWN_MASK) != 0 )
242 oldsel.addAll( sel );
243 else if( (e.getModifiersEx() & MouseEvent.CTRL_DOWN_MASK) != 0 )
244 oldsel.removeAll( sel );
245 else
246 oldsel = sel;
247 Main.ds.setSelected( oldsel );
248 return;
249 }
250 straightStart = null;
251 lastEnd = null;
252 oldSelection = null;
253 }
254
255 /**
256 * Get the shortest path by stepping through the node with a common segment with start
257 * and nearest to the end (greedy algorithm).
258 */
259 private void calculateShortestPath(Collection<OsmPrimitive> path, Node start, Node end) {
260 for (Node pivot = start; pivot != null;)
261 pivot = addNearest(path, pivot, end);
262 }
263
264 private Node addNearest(Collection<OsmPrimitive> path, Node start, Node end) {
265 Collection<Segment> c = reverseSegmentMap.get(start);
266 if (c == null)
267 return null; // start may be a waypoint without segments
268 double min = Double.MAX_VALUE;
269 Node next = null;
270 Segment seg = null;
271 for (Segment s : c) {
272 Node other = s.from == start ? s.to : s.from;
273 if (other == end) {
274 next = other;
275 seg = s;
276 min = 0;
277 break;
278 }
279 double distance = other.eastNorth.distance(end.eastNorth);
280 if (distance < min) {
281 min = distance;
282 next = other;
283 seg = s;
284 }
285 }
286 if (min < start.eastNorth.distance(end.eastNorth) && next != null) {
287 path.add(next);
288 path.add(seg);
289 return next;
290 }
291 return null;
292 }
293}
Note: See TracBrowser for help on using the repository browser.