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

Last change on this file since 4 was 4, checked in by imi, 20 years ago

improved selection

File size: 6.6 KB
Line 
1package org.openstreetmap.josm.actions.mapmode;
2
3import java.awt.Point;
4import java.awt.Rectangle;
5import java.awt.event.KeyEvent;
6import java.awt.event.MouseEvent;
7import java.awt.geom.Point2D;
8
9import org.openstreetmap.josm.data.osm.LineSegment;
10import org.openstreetmap.josm.data.osm.Node;
11import org.openstreetmap.josm.data.osm.OsmPrimitive;
12import org.openstreetmap.josm.data.osm.Track;
13import org.openstreetmap.josm.gui.MapFrame;
14import org.openstreetmap.josm.gui.MapView;
15import org.openstreetmap.josm.gui.SelectionManager;
16import org.openstreetmap.josm.gui.SelectionManager.SelectionEnded;
17
18/**
19 * This MapMode enables the user to easy make a selection of different objects.
20 *
21 * The selected objects are drawn in a different style.
22 *
23 * Holding and dragging the left mouse button draws an selection rectangle.
24 * When releasing the left mouse button, all objects within the rectangle get
25 * selected.
26 *
27 * When releasing the left mouse button while the right mouse button pressed,
28 * nothing happens (the selection rectangle will be cleared, however).
29 *
30 * When releasing the mouse button and one of the following keys was hold:
31 *
32 * If Alt key was hold, select all objects that are touched by the
33 * selection rectangle. If the Alt key was not hold, select only those objects
34 * completly within (e.g. for tracks mean: only if all nodes of the track are
35 * within).
36 *
37 * If Shift key was hold, the objects are added to the current selection. If
38 * Shift key wasn't hold, the current selection get replaced.
39 *
40 * If Ctrl key was hold, remove all objects under the current rectangle from
41 * the active selection (if there were any). Nothing is added to the current
42 * selection.
43 *
44 * Alt can be combined with Ctrl or Shift. Ctrl and Shift cannot be combined.
45 * If both are pressed, nothing happens when releasing the mouse button.
46 *
47 * The user can also only click on the map. All total movements of 2 or less
48 * pixel are considered "only click". If that happens, the nearest Node will
49 * be selected if there is any within 10 pixel range. If there is no Node within
50 * 10 pixel, the nearest LineSegment (or Street, if user hold down the Alt-Key)
51 * within 10 pixel range is selected. If there is no LineSegment within 10 pixel
52 * and the user clicked in or 10 pixel away from an area, this area is selected.
53 * If there is even no area, nothing is selected. Shift and Ctrl key applies to
54 * this as usual.
55 *
56 * @author imi
57 */
58public class SelectionAction extends MapMode implements SelectionEnded {
59
60 /**
61 * Shortcut for the MapView.
62 */
63 private MapView mv;
64 /**
65 * The SelectionManager that manages the selection rectangle.
66 */
67 private SelectionManager selectionManager;
68
69 /**
70 * Create a new SelectionAction in the given frame.
71 * @param mapFrame
72 */
73 public SelectionAction(MapFrame mapFrame) {
74 super("Selection", "selection", "Select objects by dragging or clicking", KeyEvent.VK_S, mapFrame);
75 this.mv = mapFrame.mapView;
76 this.selectionManager = new SelectionManager(this, false, mv);
77 }
78
79 @Override
80 public void registerListener(MapView mapView) {
81 selectionManager.register(mapView);
82 }
83
84 @Override
85 public void unregisterListener(MapView mapView) {
86 selectionManager.unregister(mapView);
87 }
88
89
90 /**
91 * Check the state of the keys and buttons and set the selection accordingly.
92 */
93 public void selectionEnded(Rectangle r, int modifiers) {
94 boolean shift = (modifiers & MouseEvent.SHIFT_DOWN_MASK) != 0;
95 boolean alt = (modifiers & MouseEvent.ALT_DOWN_MASK) != 0;
96 boolean ctrl = (modifiers & MouseEvent.CTRL_DOWN_MASK) != 0;
97 if (shift && ctrl)
98 return; // not allowed together
99
100 if (!ctrl && !shift) {
101 // remove the old selection. The new selection will replace the old.
102 mv.dataSet.clearSelection();
103 }
104
105 // now set the selection to this value
106 boolean selection = !ctrl;
107
108 // whether user only clicked, not dragged.
109 boolean clicked = r.width <= 2 && r.height <= 2;
110 Point2D.Double center = new Point2D.Double(r.getCenterX(), r.getCenterY());
111
112 try {
113 // nodes
114 double minDistanceSq = Double.MAX_VALUE;
115 OsmPrimitive minPrimitive = null;
116 for (Node n : mv.dataSet.allNodes) {
117 Point sp = mv.getScreenPoint(n.coor);
118 double dist = center.distanceSq(sp);
119 if (clicked && minDistanceSq > dist && dist < 100) {
120 minDistanceSq = center.distanceSq(sp);
121 minPrimitive = n;
122 } else if (r.contains(sp))
123 n.selected = selection;
124 }
125 if (minPrimitive != null) {
126 minPrimitive.selected = selection;
127 return;
128 }
129
130 // tracks
131 minDistanceSq = Double.MAX_VALUE;
132 for (Track t : mv.dataSet.tracks) {
133 boolean wholeTrackSelected = t.segments.size() > 0;
134 for (LineSegment ls : t.segments) {
135 if (clicked) {
136 Point A = mv.getScreenPoint(ls.start.coor);
137 Point B = mv.getScreenPoint(ls.end.coor);
138 double c = A.distanceSq(B);
139 double a = center.distanceSq(B);
140 double b = center.distanceSq(A);
141 double perDist = perpendicularDistSq(a,b,c);
142 if (perDist < 100 && minDistanceSq > perDist && a < c+100 && b < c+100) {
143 minDistanceSq = perDist;
144 if (alt)
145 minPrimitive = t;
146 else
147 minPrimitive = ls;
148 }
149 } else {
150 if (alt) {
151 Point p1 = mv.getScreenPoint(ls.start.coor);
152 Point p2 = mv.getScreenPoint(ls.end.coor);
153 if (r.intersectsLine(p1.x, p1.y, p2.x, p2.y))
154 ls.selected = selection;
155 else
156 wholeTrackSelected = false;
157 } else {
158 if (r.contains(mv.getScreenPoint(ls.start.coor))
159 && r.contains(mv.getScreenPoint(ls.end.coor)))
160 ls.selected = selection;
161 else
162 wholeTrackSelected = false;
163 }
164 }
165 }
166 if (wholeTrackSelected && !clicked)
167 t.selected = true;
168 }
169 if (minPrimitive != null) {
170 minPrimitive.selected = selection;
171 return;
172 }
173
174 // TODO arrays
175 } finally {
176 mv.repaint();
177 }
178 }
179
180 /**
181 * Calculates the squared perpendicular distance named "h" from a point C to the
182 * straight line going to the points A and B, where the distance to B is
183 * sqrt(a) and the distance to A is sqrt(b).
184 *
185 * Think of a, b and c as the squared line lengths of any ordinary triangle
186 * A,B,C. a = BC, b = AC and c = AB. The straight line goes through A and B
187 * and the desired return value is the perpendicular distance from C to c.
188 *
189 * @param a Squared distance from B to C.
190 * @param b Squared distance from A to C.
191 * @param c Squared distance from A to B.
192 * @return The perpendicular distance from C to c.
193 */
194 private double perpendicularDistSq(double a, double b, double c) {
195 // I did this on paper by myself, so I am surprised too, that it is that
196 // performant ;-)
197 return a-(a-b+c)*(a-b+c)/4/c;
198 }
199}
Note: See TracBrowser for help on using the repository browser.