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

Last change on this file since 14306 was 14221, checked in by Don-vip, 6 years ago

see #16706 - use documented enum for AutoScaleMode instead of undocumented string constants

  • Property svn:eol-style set to native
File size: 19.8 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.awt.geom.Area;
11import java.util.ArrayList;
12import java.util.Arrays;
13import java.util.Collection;
14import java.util.Collections;
15import java.util.HashSet;
16import java.util.List;
17import java.util.Objects;
18import java.util.concurrent.TimeUnit;
19
20import javax.swing.JOptionPane;
21import javax.swing.event.ListSelectionListener;
22import javax.swing.event.TreeSelectionListener;
23
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.IPrimitive;
29import org.openstreetmap.josm.data.osm.OsmData;
30import org.openstreetmap.josm.data.osm.OsmPrimitive;
31import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
32import org.openstreetmap.josm.data.validation.TestError;
33import org.openstreetmap.josm.gui.MainApplication;
34import org.openstreetmap.josm.gui.MapFrame;
35import org.openstreetmap.josm.gui.MapFrameListener;
36import org.openstreetmap.josm.gui.MapView;
37import org.openstreetmap.josm.gui.dialogs.ConflictDialog;
38import org.openstreetmap.josm.gui.dialogs.LayerListDialog;
39import org.openstreetmap.josm.gui.dialogs.ValidatorDialog.ValidatorBoundingXYVisitor;
40import org.openstreetmap.josm.gui.layer.Layer;
41import org.openstreetmap.josm.spi.preferences.Config;
42import org.openstreetmap.josm.tools.Logging;
43import org.openstreetmap.josm.tools.Shortcut;
44
45/**
46 * Toggles the autoScale feature of the mapView
47 * @author imi
48 * @since 17
49 */
50public class AutoScaleAction extends JosmAction {
51
52 /**
53 * A list of things we can zoom to. The zoom target is given depending on the mode.
54 * @since 14221
55 */
56 public enum AutoScaleMode {
57 /** Zoom the window so that all the data fills the window area */
58 DATA(marktr(/* ICON(dialogs/autoscale/) */ "data")),
59 /** Zoom the window so that all the data on the currently selected layer fills the window area */
60 LAYER(marktr(/* ICON(dialogs/autoscale/) */ "layer")),
61 /** Zoom the window so that only data which is currently selected fills the window area */
62 SELECTION(marktr(/* ICON(dialogs/autoscale/) */ "selection")),
63 /** Zoom to the first selected conflict */
64 CONFLICT(marktr(/* ICON(dialogs/autoscale/) */ "conflict")),
65 /** Zoom the view to last downloaded data */
66 DOWNLOAD(marktr(/* ICON(dialogs/autoscale/) */ "download")),
67 /** Zoom the view to problem */
68 PROBLEM(marktr(/* ICON(dialogs/autoscale/) */ "problem")),
69 /** Zoom to the previous zoomed to scale and location (zoom undo) */
70 PREVIOUS(marktr(/* ICON(dialogs/autoscale/) */ "previous")),
71 /** Zoom to the next zoomed to scale and location (zoom redo) */
72 NEXT(marktr(/* ICON(dialogs/autoscale/) */ "next"));
73
74 private final String label;
75
76 AutoScaleMode(String label) {
77 this.label = label;
78 }
79
80 /**
81 * Returns the English label. Used for retrieving icons.
82 * @return the English label
83 */
84 public String getEnglishLabel() {
85 return label;
86 }
87
88 /**
89 * Returns the localized label. Used for display
90 * @return the localized label
91 */
92 public String getLocalizedLabel() {
93 return tr(label);
94 }
95
96 /**
97 * Returns {@code AutoScaleMode} for a given English label
98 * @param englishLabel English label
99 * @return {@code AutoScaleMode} for given English label
100 * @throws IllegalArgumentException if Engligh label is unknown
101 */
102 public static AutoScaleMode of(String englishLabel) {
103 for (AutoScaleMode v : values()) {
104 if (Objects.equals(v.label, englishLabel)) {
105 return v;
106 }
107 }
108 throw new IllegalArgumentException(englishLabel);
109 }
110 }
111
112 /**
113 * A list of things we can zoom to. The zoom target is given depending on the mode.
114 * @deprecated Use {@link AutoScaleMode} enum instead
115 */
116 @Deprecated
117 public static final Collection<String> MODES = Collections.unmodifiableList(Arrays.asList(
118 marktr(/* ICON(dialogs/autoscale/) */ "data"),
119 marktr(/* ICON(dialogs/autoscale/) */ "layer"),
120 marktr(/* ICON(dialogs/autoscale/) */ "selection"),
121 marktr(/* ICON(dialogs/autoscale/) */ "conflict"),
122 marktr(/* ICON(dialogs/autoscale/) */ "download"),
123 marktr(/* ICON(dialogs/autoscale/) */ "problem"),
124 marktr(/* ICON(dialogs/autoscale/) */ "previous"),
125 marktr(/* ICON(dialogs/autoscale/) */ "next")));
126
127 /**
128 * One of {@link AutoScaleMode}. Defines what we are zooming to.
129 */
130 private final AutoScaleMode mode;
131
132 /** Time of last zoom to bounds action */
133 protected long lastZoomTime = -1;
134 /** Last zommed bounds */
135 protected int lastZoomArea = -1;
136
137 /**
138 * Zooms the current map view to the currently selected primitives.
139 * Does nothing if there either isn't a current map view or if there isn't a current data layer.
140 *
141 */
142 public static void zoomToSelection() {
143 OsmData<?, ?, ?, ?> dataSet = MainApplication.getLayerManager().getActiveData();
144 if (dataSet == null) {
145 return;
146 }
147 Collection<? extends IPrimitive> sel = dataSet.getSelected();
148 if (sel.isEmpty()) {
149 JOptionPane.showMessageDialog(
150 MainApplication.getMainFrame(),
151 tr("Nothing selected to zoom to."),
152 tr("Information"),
153 JOptionPane.INFORMATION_MESSAGE);
154 return;
155 }
156 zoomTo(sel);
157 }
158
159 /**
160 * Zooms the view to display the given set of primitives.
161 * @param sel The primitives to zoom to, e.g. the current selection.
162 */
163 public static void zoomTo(Collection<? extends IPrimitive> sel) {
164 BoundingXYVisitor bboxCalculator = new BoundingXYVisitor();
165 bboxCalculator.computeBoundingBox(sel);
166 // increase bbox. This is required
167 // especially if the bbox contains one single node, but helpful
168 // in most other cases as well.
169 bboxCalculator.enlargeBoundingBox();
170 if (bboxCalculator.getBounds() != null) {
171 MainApplication.getMap().mapView.zoomTo(bboxCalculator);
172 }
173 }
174
175 /**
176 * Performs the auto scale operation of the given mode without the need to create a new action.
177 * @param mode One of {@link #MODES}.
178 * @since 14221
179 */
180 public static void autoScale(AutoScaleMode mode) {
181 new AutoScaleAction(mode, false).autoScale();
182 }
183
184 /**
185 * Performs the auto scale operation of the given mode without the need to create a new action.
186 * @param mode One of {@link #MODES}.
187 * @deprecated Use {@link #autoScale(AutoScaleMode)} instead
188 */
189 @Deprecated
190 public static void autoScale(String mode) {
191 autoScale(AutoScaleMode.of(mode));
192 }
193
194 private static int getModeShortcut(String mode) {
195 int shortcut = -1;
196
197 // TODO: convert this to switch/case and make sure the parsing still works
198 // CHECKSTYLE.OFF: LeftCurly
199 // CHECKSTYLE.OFF: RightCurly
200 /* leave as single line for shortcut overview parsing! */
201 if (mode.equals("data")) { shortcut = KeyEvent.VK_1; }
202 else if (mode.equals("layer")) { shortcut = KeyEvent.VK_2; }
203 else if (mode.equals("selection")) { shortcut = KeyEvent.VK_3; }
204 else if (mode.equals("conflict")) { shortcut = KeyEvent.VK_4; }
205 else if (mode.equals("download")) { shortcut = KeyEvent.VK_5; }
206 else if (mode.equals("problem")) { shortcut = KeyEvent.VK_6; }
207 else if (mode.equals("previous")) { shortcut = KeyEvent.VK_8; }
208 else if (mode.equals("next")) { shortcut = KeyEvent.VK_9; }
209 // CHECKSTYLE.ON: LeftCurly
210 // CHECKSTYLE.ON: RightCurly
211
212 return shortcut;
213 }
214
215 /**
216 * Constructs a new {@code AutoScaleAction}.
217 * @param mode The autoscale mode (one of {@link AutoScaleMode})
218 * @param marker Must be set to false. Used only to differentiate from default constructor
219 */
220 private AutoScaleAction(AutoScaleMode mode, boolean marker) {
221 super(marker);
222 this.mode = mode;
223 }
224
225 /**
226 * Constructs a new {@code AutoScaleAction}.
227 * @param mode The autoscale mode (one of {@link AutoScaleAction#MODES})
228 * @deprecated Use {@link #AutoScaleAction(AutoScaleMode)} instead
229 */
230 @Deprecated
231 public AutoScaleAction(final String mode) {
232 this(AutoScaleMode.of(mode));
233 }
234
235 /**
236 * Constructs a new {@code AutoScaleAction}.
237 * @param mode The autoscale mode (one of {@link AutoScaleAction#MODES})
238 * @since 14221
239 */
240 public AutoScaleAction(final AutoScaleMode mode) {
241 super(tr("Zoom to {0}", mode.getLocalizedLabel()), "dialogs/autoscale/" + mode.getEnglishLabel(),
242 tr("Zoom the view to {0}.", mode.getLocalizedLabel()),
243 Shortcut.registerShortcut("view:zoom" + mode.getEnglishLabel(),
244 tr("View: {0}", tr("Zoom to {0}", mode.getLocalizedLabel())),
245 getModeShortcut(mode.getEnglishLabel()), Shortcut.DIRECT), true, null, false);
246 String label = mode.getEnglishLabel();
247 String modeHelp = Character.toUpperCase(label.charAt(0)) + label.substring(1);
248 putValue("help", "Action/AutoScale/" + modeHelp);
249 this.mode = mode;
250 switch (mode) {
251 case DATA:
252 putValue("help", ht("/Action/ZoomToData"));
253 break;
254 case LAYER:
255 putValue("help", ht("/Action/ZoomToLayer"));
256 break;
257 case SELECTION:
258 putValue("help", ht("/Action/ZoomToSelection"));
259 break;
260 case CONFLICT:
261 putValue("help", ht("/Action/ZoomToConflict"));
262 break;
263 case PROBLEM:
264 putValue("help", ht("/Action/ZoomToProblem"));
265 break;
266 case DOWNLOAD:
267 putValue("help", ht("/Action/ZoomToDownload"));
268 break;
269 case PREVIOUS:
270 putValue("help", ht("/Action/ZoomToPrevious"));
271 break;
272 case NEXT:
273 putValue("help", ht("/Action/ZoomToNext"));
274 break;
275 default:
276 throw new IllegalArgumentException("Unknown mode: " + mode);
277 }
278 installAdapters();
279 }
280
281 /**
282 * Performs this auto scale operation for the mode this action is in.
283 */
284 public void autoScale() {
285 if (MainApplication.isDisplayingMapView()) {
286 MapView mapView = MainApplication.getMap().mapView;
287 switch (mode) {
288 case PREVIOUS:
289 mapView.zoomPrevious();
290 break;
291 case NEXT:
292 mapView.zoomNext();
293 break;
294 default:
295 BoundingXYVisitor bbox = getBoundingBox();
296 if (bbox != null && bbox.getBounds() != null) {
297 mapView.zoomTo(bbox);
298 }
299 }
300 }
301 putValue("active", Boolean.TRUE);
302 }
303
304 @Override
305 public void actionPerformed(ActionEvent e) {
306 autoScale();
307 }
308
309 /**
310 * Replies the first selected layer in the layer list dialog. null, if no
311 * such layer exists, either because the layer list dialog is not yet created
312 * or because no layer is selected.
313 *
314 * @return the first selected layer in the layer list dialog
315 */
316 protected Layer getFirstSelectedLayer() {
317 if (getLayerManager().getActiveLayer() == null) {
318 return null;
319 }
320 try {
321 List<Layer> layers = LayerListDialog.getInstance().getModel().getSelectedLayers();
322 if (!layers.isEmpty())
323 return layers.get(0);
324 } catch (IllegalStateException e) {
325 Logging.error(e);
326 }
327 return null;
328 }
329
330 private BoundingXYVisitor getBoundingBox() {
331 switch (mode) {
332 case PROBLEM:
333 return modeProblem(new ValidatorBoundingXYVisitor());
334 case DATA:
335 return modeData(new BoundingXYVisitor());
336 case LAYER:
337 return modeLayer(new BoundingXYVisitor());
338 case SELECTION:
339 case CONFLICT:
340 return modeSelectionOrConflict(new BoundingXYVisitor());
341 case DOWNLOAD:
342 return modeDownload(new BoundingXYVisitor());
343 default:
344 return new BoundingXYVisitor();
345 }
346 }
347
348 private static BoundingXYVisitor modeProblem(ValidatorBoundingXYVisitor v) {
349 TestError error = MainApplication.getMap().validatorDialog.getSelectedError();
350 if (error == null)
351 return null;
352 v.visit(error);
353 if (v.getBounds() == null)
354 return null;
355 v.enlargeBoundingBox(Config.getPref().getDouble("validator.zoom-enlarge-bbox", 0.0002));
356 return v;
357 }
358
359 private static BoundingXYVisitor modeData(BoundingXYVisitor v) {
360 for (Layer l : MainApplication.getLayerManager().getLayers()) {
361 l.visitBoundingBox(v);
362 }
363 return v;
364 }
365
366 private BoundingXYVisitor modeLayer(BoundingXYVisitor v) {
367 // try to zoom to the first selected layer
368 Layer l = getFirstSelectedLayer();
369 if (l == null)
370 return null;
371 l.visitBoundingBox(v);
372 return v;
373 }
374
375 private BoundingXYVisitor modeSelectionOrConflict(BoundingXYVisitor v) {
376 Collection<IPrimitive> sel = new HashSet<>();
377 if (AutoScaleMode.SELECTION == mode) {
378 OsmData<?, ?, ?, ?> dataSet = getLayerManager().getActiveData();
379 if (dataSet != null) {
380 sel.addAll(dataSet.getSelected());
381 }
382 } else {
383 ConflictDialog conflictDialog = MainApplication.getMap().conflictDialog;
384 Conflict<? extends IPrimitive> c = conflictDialog.getSelectedConflict();
385 if (c != null) {
386 sel.add(c.getMy());
387 } else if (conflictDialog.getConflicts() != null) {
388 sel.addAll(conflictDialog.getConflicts().getMyConflictParties());
389 }
390 }
391 if (sel.isEmpty()) {
392 JOptionPane.showMessageDialog(
393 MainApplication.getMainFrame(),
394 AutoScaleMode.SELECTION == mode ? tr("Nothing selected to zoom to.") : tr("No conflicts to zoom to"),
395 tr("Information"),
396 JOptionPane.INFORMATION_MESSAGE);
397 return null;
398 }
399 for (IPrimitive osm : sel) {
400 osm.accept(v);
401 }
402
403 // Increase the bounding box by up to 100% to give more context.
404 v.enlargeBoundingBoxLogarithmically(100);
405 // Make the bounding box at least 100 meter wide to
406 // ensure reasonable zoom level when zooming onto single nodes.
407 v.enlargeToMinSize(Config.getPref().getDouble("zoom_to_selection_min_size_in_meter", 100));
408 return v;
409 }
410
411 private BoundingXYVisitor modeDownload(BoundingXYVisitor v) {
412 if (lastZoomTime > 0 &&
413 System.currentTimeMillis() - lastZoomTime > Config.getPref().getLong("zoom.bounds.reset.time", TimeUnit.SECONDS.toMillis(10))) {
414 lastZoomTime = -1;
415 }
416 final DataSet dataset = getLayerManager().getActiveDataSet();
417 if (dataset != null) {
418 List<DataSource> dataSources = new ArrayList<>(dataset.getDataSources());
419 int s = dataSources.size();
420 if (s > 0) {
421 if (lastZoomTime == -1 || lastZoomArea == -1 || lastZoomArea > s) {
422 lastZoomArea = s-1;
423 v.visit(dataSources.get(lastZoomArea).bounds);
424 } else if (lastZoomArea > 0) {
425 lastZoomArea -= 1;
426 v.visit(dataSources.get(lastZoomArea).bounds);
427 } else {
428 lastZoomArea = -1;
429 Area sourceArea = getLayerManager().getActiveDataSet().getDataSourceArea();
430 if (sourceArea != null) {
431 v.visit(new Bounds(sourceArea.getBounds2D()));
432 }
433 }
434 lastZoomTime = System.currentTimeMillis();
435 } else {
436 lastZoomTime = -1;
437 lastZoomArea = -1;
438 }
439 }
440 return v;
441 }
442
443 @Override
444 protected void updateEnabledState() {
445 OsmData<?, ?, ?, ?> ds = getLayerManager().getActiveData();
446 MapFrame map = MainApplication.getMap();
447 switch (mode) {
448 case SELECTION:
449 setEnabled(ds != null && !ds.selectionEmpty());
450 break;
451 case LAYER:
452 setEnabled(getFirstSelectedLayer() != null);
453 break;
454 case CONFLICT:
455 setEnabled(map != null && map.conflictDialog.getSelectedConflict() != null);
456 break;
457 case DOWNLOAD:
458 setEnabled(ds != null && !ds.getDataSources().isEmpty());
459 break;
460 case PROBLEM:
461 setEnabled(map != null && map.validatorDialog.getSelectedError() != null);
462 break;
463 case PREVIOUS:
464 setEnabled(MainApplication.isDisplayingMapView() && map.mapView.hasZoomUndoEntries());
465 break;
466 case NEXT:
467 setEnabled(MainApplication.isDisplayingMapView() && map.mapView.hasZoomRedoEntries());
468 break;
469 default:
470 setEnabled(!getLayerManager().getLayers().isEmpty());
471 }
472 }
473
474 @Override
475 protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) {
476 if (AutoScaleMode.SELECTION == mode) {
477 setEnabled(selection != null && !selection.isEmpty());
478 }
479 }
480
481 @Override
482 protected final void installAdapters() {
483 super.installAdapters();
484 // make this action listen to zoom and mapframe change events
485 //
486 MapView.addZoomChangeListener(new ZoomChangeAdapter());
487 MainApplication.addMapFrameListener(new MapFrameAdapter());
488 initEnabledState();
489 }
490
491 /**
492 * Adapter for zoom change events
493 */
494 private class ZoomChangeAdapter implements MapView.ZoomChangeListener {
495 @Override
496 public void zoomChanged() {
497 updateEnabledState();
498 }
499 }
500
501 /**
502 * Adapter for MapFrame change events
503 */
504 private class MapFrameAdapter implements MapFrameListener {
505 private ListSelectionListener conflictSelectionListener;
506 private TreeSelectionListener validatorSelectionListener;
507
508 MapFrameAdapter() {
509 if (AutoScaleMode.CONFLICT == mode) {
510 conflictSelectionListener = e -> updateEnabledState();
511 } else if (AutoScaleMode.PROBLEM == mode) {
512 validatorSelectionListener = e -> updateEnabledState();
513 }
514 }
515
516 @Override
517 public void mapFrameInitialized(MapFrame oldFrame, MapFrame newFrame) {
518 if (conflictSelectionListener != null) {
519 if (newFrame != null) {
520 newFrame.conflictDialog.addListSelectionListener(conflictSelectionListener);
521 } else if (oldFrame != null) {
522 oldFrame.conflictDialog.removeListSelectionListener(conflictSelectionListener);
523 }
524 } else if (validatorSelectionListener != null) {
525 if (newFrame != null) {
526 newFrame.validatorDialog.addTreeSelectionListener(validatorSelectionListener);
527 } else if (oldFrame != null) {
528 oldFrame.validatorDialog.removeTreeSelectionListener(validatorSelectionListener);
529 }
530 }
531 updateEnabledState();
532 }
533 }
534}
Note: See TracBrowser for help on using the repository browser.