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

Last change on this file since 19307 was 19307, checked in by taylor.smock, 5 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.