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

Last change on this file was 19307, checked in by taylor.smock, 14 months ago

Fix most new PMD issues

It would be better to use the newer switch syntax introduced in Java 14 (JEP 361),
but we currently target Java 11+. When we move to Java 17, this should be
reverted and the newer switch syntax should be used.

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