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

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