source: josm/src/org/openstreetmap/josm/actions/mapmode/SelectionAction.java@ 151

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