source: josm/trunk/src/org/openstreetmap/josm/actions/AutoScaleAction.java@ 10007

Last change on this file since 10007 was 9795, checked in by Don-vip, 8 years ago

findbugs

  • Property svn:eol-style set to native
File size: 15.6 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.actions;
3
4import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
5import static org.openstreetmap.josm.tools.I18n.marktr;
6import static org.openstreetmap.josm.tools.I18n.tr;
7
8import java.awt.event.ActionEvent;
9import java.awt.event.KeyEvent;
10import java.util.ArrayList;
11import java.util.Arrays;
12import java.util.Collection;
13import java.util.Collections;
14import java.util.HashSet;
15import java.util.List;
16
17import javax.swing.JOptionPane;
18import javax.swing.event.ListSelectionEvent;
19import javax.swing.event.ListSelectionListener;
20import javax.swing.event.TreeSelectionEvent;
21import javax.swing.event.TreeSelectionListener;
22
23import org.openstreetmap.josm.Main;
24import org.openstreetmap.josm.data.Bounds;
25import org.openstreetmap.josm.data.DataSource;
26import org.openstreetmap.josm.data.conflict.Conflict;
27import org.openstreetmap.josm.data.osm.DataSet;
28import org.openstreetmap.josm.data.osm.OsmPrimitive;
29import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
30import org.openstreetmap.josm.data.validation.TestError;
31import org.openstreetmap.josm.gui.MapFrame;
32import org.openstreetmap.josm.gui.MapFrameListener;
33import org.openstreetmap.josm.gui.MapView;
34import org.openstreetmap.josm.gui.dialogs.LayerListDialog;
35import org.openstreetmap.josm.gui.dialogs.ValidatorDialog.ValidatorBoundingXYVisitor;
36import org.openstreetmap.josm.gui.layer.Layer;
37import org.openstreetmap.josm.tools.Shortcut;
38
39/**
40 * Toggles the autoScale feature of the mapView
41 * @author imi
42 */
43public class AutoScaleAction extends JosmAction {
44
45 /**
46 * A list of things we can zoom to. The zoom target is given depending on the mode.
47 */
48 public static final Collection<String> MODES = Collections.unmodifiableList(Arrays.asList(
49 marktr(/* ICON(dialogs/autoscale/) */ "data"),
50 marktr(/* ICON(dialogs/autoscale/) */ "layer"),
51 marktr(/* ICON(dialogs/autoscale/) */ "selection"),
52 marktr(/* ICON(dialogs/autoscale/) */ "conflict"),
53 marktr(/* ICON(dialogs/autoscale/) */ "download"),
54 marktr(/* ICON(dialogs/autoscale/) */ "problem"),
55 marktr(/* ICON(dialogs/autoscale/) */ "previous"),
56 marktr(/* ICON(dialogs/autoscale/) */ "next")));
57
58 /**
59 * One of {@link #MODES}. Defines what we are zooming to.
60 */
61 private final String mode;
62
63 /** Time of last zoom to bounds action */
64 protected long lastZoomTime = -1;
65 /** Last zommed bounds */
66 protected int lastZoomArea = -1;
67
68 /**
69 * Zooms the current map view to the currently selected primitives.
70 * Does nothing if there either isn't a current map view or if there isn't a current data
71 * layer.
72 *
73 */
74 public static void zoomToSelection() {
75 if (Main.main == null || !Main.main.hasEditLayer())
76 return;
77 Collection<OsmPrimitive> sel = Main.main.getEditLayer().data.getSelected();
78 if (sel.isEmpty()) {
79 JOptionPane.showMessageDialog(
80 Main.parent,
81 tr("Nothing selected to zoom to."),
82 tr("Information"),
83 JOptionPane.INFORMATION_MESSAGE);
84 return;
85 }
86 zoomTo(sel);
87 }
88
89 /**
90 * Zooms the view to display the given set of primitives.
91 * @param sel The primitives to zoom to, e.g. the current selection.
92 */
93 public static void zoomTo(Collection<OsmPrimitive> sel) {
94 BoundingXYVisitor bboxCalculator = new BoundingXYVisitor();
95 bboxCalculator.computeBoundingBox(sel);
96 // increase bbox. This is required
97 // especially if the bbox contains one single node, but helpful
98 // in most other cases as well.
99 bboxCalculator.enlargeBoundingBox();
100 if (bboxCalculator.getBounds() != null) {
101 Main.map.mapView.zoomTo(bboxCalculator);
102 }
103 }
104
105 /**
106 * Performs the auto scale operation of the given mode without the need to create a new action.
107 * @param mode One of {@link #MODES}.
108 */
109 public static void autoScale(String mode) {
110 new AutoScaleAction(mode, false).autoScale();
111 }
112
113 private static int getModeShortcut(String mode) {
114 int shortcut = -1;
115
116 // TODO: convert this to switch/case and make sure the parsing still works
117 // CHECKSTYLE.OFF: LeftCurly
118 // CHECKSTYLE.OFF: RightCurly
119 /* leave as single line for shortcut overview parsing! */
120 if (mode.equals("data")) { shortcut = KeyEvent.VK_1; }
121 else if (mode.equals("layer")) { shortcut = KeyEvent.VK_2; }
122 else if (mode.equals("selection")) { shortcut = KeyEvent.VK_3; }
123 else if (mode.equals("conflict")) { shortcut = KeyEvent.VK_4; }
124 else if (mode.equals("download")) { shortcut = KeyEvent.VK_5; }
125 else if (mode.equals("problem")) { shortcut = KeyEvent.VK_6; }
126 else if (mode.equals("previous")) { shortcut = KeyEvent.VK_8; }
127 else if (mode.equals("next")) { shortcut = KeyEvent.VK_9; }
128 // CHECKSTYLE.ON: LeftCurly
129 // CHECKSTYLE.ON: RightCurly
130
131 return shortcut;
132 }
133
134 /**
135 * Constructs a new {@code AutoScaleAction}.
136 * @param mode The autoscale mode (one of {@link AutoScaleAction#MODES})
137 * @param marker Used only to differentiate from default constructor
138 */
139 private AutoScaleAction(String mode, boolean marker) {
140 super(false);
141 this.mode = mode;
142 }
143
144 /**
145 * Constructs a new {@code AutoScaleAction}.
146 * @param mode The autoscale mode (one of {@link AutoScaleAction#MODES})
147 */
148 public AutoScaleAction(final String mode) {
149 super(tr("Zoom to {0}", tr(mode)), "dialogs/autoscale/" + mode, tr("Zoom the view to {0}.", tr(mode)),
150 Shortcut.registerShortcut("view:zoom" + mode, tr("View: {0}", tr("Zoom to {0}", tr(mode))),
151 getModeShortcut(mode), Shortcut.DIRECT), true, null, false);
152 String modeHelp = Character.toUpperCase(mode.charAt(0)) + mode.substring(1);
153 putValue("help", "Action/AutoScale/" + modeHelp);
154 this.mode = mode;
155 switch (mode) {
156 case "data":
157 putValue("help", ht("/Action/ZoomToData"));
158 break;
159 case "layer":
160 putValue("help", ht("/Action/ZoomToLayer"));
161 break;
162 case "selection":
163 putValue("help", ht("/Action/ZoomToSelection"));
164 break;
165 case "conflict":
166 putValue("help", ht("/Action/ZoomToConflict"));
167 break;
168 case "problem":
169 putValue("help", ht("/Action/ZoomToProblem"));
170 break;
171 case "download":
172 putValue("help", ht("/Action/ZoomToDownload"));
173 break;
174 case "previous":
175 putValue("help", ht("/Action/ZoomToPrevious"));
176 break;
177 case "next":
178 putValue("help", ht("/Action/ZoomToNext"));
179 break;
180 default:
181 throw new IllegalArgumentException("Unknown mode: " + mode);
182 }
183 installAdapters();
184 }
185
186 /**
187 * Performs this auto scale operation for the mode this action is in.
188 */
189 public void autoScale() {
190 if (Main.isDisplayingMapView()) {
191 switch (mode) {
192 case "previous":
193 Main.map.mapView.zoomPrevious();
194 break;
195 case "next":
196 Main.map.mapView.zoomNext();
197 break;
198 default:
199 BoundingXYVisitor bbox = getBoundingBox();
200 if (bbox != null && bbox.getBounds() != null) {
201 Main.map.mapView.zoomTo(bbox);
202 }
203 }
204 }
205 putValue("active", Boolean.TRUE);
206 }
207
208 @Override
209 public void actionPerformed(ActionEvent e) {
210 autoScale();
211 }
212
213 /**
214 * Replies the first selected layer in the layer list dialog. null, if no
215 * such layer exists, either because the layer list dialog is not yet created
216 * or because no layer is selected.
217 *
218 * @return the first selected layer in the layer list dialog
219 */
220 protected Layer getFirstSelectedLayer() {
221 if (Main.main.getActiveLayer() == null) {
222 return null;
223 }
224 List<Layer> layers = LayerListDialog.getInstance().getModel().getSelectedLayers();
225 if (layers.isEmpty())
226 return null;
227 return layers.get(0);
228 }
229
230 private BoundingXYVisitor getBoundingBox() {
231 BoundingXYVisitor v = "problem".equals(mode) ? new ValidatorBoundingXYVisitor() : new BoundingXYVisitor();
232
233 switch (mode) {
234 case "problem":
235 TestError error = Main.map.validatorDialog.getSelectedError();
236 if (error == null)
237 return null;
238 ((ValidatorBoundingXYVisitor) v).visit(error);
239 if (v.getBounds() == null)
240 return null;
241 v.enlargeBoundingBox(Main.pref.getDouble("validator.zoom-enlarge-bbox", 0.0002));
242 break;
243 case "data":
244 for (Layer l : Main.map.mapView.getAllLayers()) {
245 l.visitBoundingBox(v);
246 }
247 break;
248 case "layer":
249 // try to zoom to the first selected layer
250 Layer l = getFirstSelectedLayer();
251 if (l == null)
252 return null;
253 l.visitBoundingBox(v);
254 break;
255 case "selection":
256 case "conflict":
257 Collection<OsmPrimitive> sel = new HashSet<>();
258 if ("selection".equals(mode)) {
259 sel = getCurrentDataSet().getSelected();
260 } else {
261 Conflict<? extends OsmPrimitive> c = Main.map.conflictDialog.getSelectedConflict();
262 if (c != null) {
263 sel.add(c.getMy());
264 } else if (Main.map.conflictDialog.getConflicts() != null) {
265 sel = Main.map.conflictDialog.getConflicts().getMyConflictParties();
266 }
267 }
268 if (sel.isEmpty()) {
269 JOptionPane.showMessageDialog(
270 Main.parent,
271 "selection".equals(mode) ? tr("Nothing selected to zoom to.") : tr("No conflicts to zoom to"),
272 tr("Information"),
273 JOptionPane.INFORMATION_MESSAGE);
274 return null;
275 }
276 for (OsmPrimitive osm : sel) {
277 osm.accept(v);
278 }
279
280 // Increase the bounding box by up to 100% to give more context.
281 v.enlargeBoundingBoxLogarithmically(100);
282 // Make the bounding box at least 100 meter wide to
283 // ensure reasonable zoom level when zooming onto single nodes.
284 v.enlargeToMinSize(Main.pref.getDouble("zoom_to_selection_min_size_in_meter", 100));
285 break;
286 case "download":
287
288 if (lastZoomTime > 0 && System.currentTimeMillis() - lastZoomTime > Main.pref.getLong("zoom.bounds.reset.time", 10*1000)) {
289 lastZoomTime = -1;
290 }
291 final DataSet dataset = getCurrentDataSet();
292 if (dataset != null) {
293 List<DataSource> dataSources = new ArrayList<>(dataset.getDataSources());
294 int s = dataSources.size();
295 if (s > 0) {
296 if (lastZoomTime == -1 || lastZoomArea == -1 || lastZoomArea > s) {
297 lastZoomArea = s-1;
298 v.visit(dataSources.get(lastZoomArea).bounds);
299 } else if (lastZoomArea > 0) {
300 lastZoomArea -= 1;
301 v.visit(dataSources.get(lastZoomArea).bounds);
302 } else {
303 lastZoomArea = -1;
304 v.visit(new Bounds(Main.main.getCurrentDataSet().getDataSourceArea().getBounds2D()));
305 }
306 lastZoomTime = System.currentTimeMillis();
307 } else {
308 lastZoomTime = -1;
309 lastZoomArea = -1;
310 }
311 }
312 break;
313 }
314 return v;
315 }
316
317 @Override
318 protected void updateEnabledState() {
319 switch (mode) {
320 case "selection":
321 setEnabled(getCurrentDataSet() != null && !getCurrentDataSet().getSelected().isEmpty());
322 break;
323 case "layer":
324 setEnabled(getFirstSelectedLayer() != null);
325 break;
326 case "conflict":
327 setEnabled(Main.map != null && Main.map.conflictDialog.getSelectedConflict() != null);
328 break;
329 case "download":
330 setEnabled(getCurrentDataSet() != null && !getCurrentDataSet().getDataSources().isEmpty());
331 break;
332 case "problem":
333 setEnabled(Main.map != null && Main.map.validatorDialog.getSelectedError() != null);
334 break;
335 case "previous":
336 setEnabled(Main.isDisplayingMapView() && Main.map.mapView.hasZoomUndoEntries());
337 break;
338 case "next":
339 setEnabled(Main.isDisplayingMapView() && Main.map.mapView.hasZoomRedoEntries());
340 break;
341 default:
342 setEnabled(Main.isDisplayingMapView() && Main.map.mapView.hasLayers());
343 }
344 }
345
346 @Override
347 protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) {
348 if ("selection".equals(mode)) {
349 setEnabled(selection != null && !selection.isEmpty());
350 }
351 }
352
353 @Override
354 protected final void installAdapters() {
355 super.installAdapters();
356 // make this action listen to zoom and mapframe change events
357 //
358 MapView.addZoomChangeListener(new ZoomChangeAdapter());
359 Main.addMapFrameListener(new MapFrameAdapter());
360 initEnabledState();
361 }
362
363 /**
364 * Adapter for zoom change events
365 */
366 private class ZoomChangeAdapter implements MapView.ZoomChangeListener {
367 @Override
368 public void zoomChanged() {
369 updateEnabledState();
370 }
371 }
372
373 /**
374 * Adapter for MapFrame change events
375 */
376 private class MapFrameAdapter implements MapFrameListener {
377 private ListSelectionListener conflictSelectionListener;
378 private TreeSelectionListener validatorSelectionListener;
379
380 MapFrameAdapter() {
381 if ("conflict".equals(mode)) {
382 conflictSelectionListener = new ListSelectionListener() {
383 @Override
384 public void valueChanged(ListSelectionEvent e) {
385 updateEnabledState();
386 }
387 };
388 } else if ("problem".equals(mode)) {
389 validatorSelectionListener = new TreeSelectionListener() {
390 @Override
391 public void valueChanged(TreeSelectionEvent e) {
392 updateEnabledState();
393 }
394 };
395 }
396 }
397
398 @Override
399 public void mapFrameInitialized(MapFrame oldFrame, MapFrame newFrame) {
400 if (conflictSelectionListener != null) {
401 if (newFrame != null) {
402 newFrame.conflictDialog.addListSelectionListener(conflictSelectionListener);
403 } else if (oldFrame != null) {
404 oldFrame.conflictDialog.removeListSelectionListener(conflictSelectionListener);
405 }
406 } else if (validatorSelectionListener != null) {
407 if (newFrame != null) {
408 newFrame.validatorDialog.addTreeSelectionListener(validatorSelectionListener);
409 } else if (oldFrame != null) {
410 oldFrame.validatorDialog.removeTreeSelectionListener(validatorSelectionListener);
411 }
412 }
413 updateEnabledState();
414 }
415 }
416}
Note: See TracBrowser for help on using the repository browser.