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

Last change on this file since 14734 was 14734, checked in by GerdP, 5 years ago

fix #16706 (16706-improve-v2.patch)

  • Property svn:eol-style set to native
File size: 19.0 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 if (bboxCalculator.getBounds() != null) {
167 MainApplication.getMap().mapView.zoomTo(bboxCalculator);
168 }
169 }
170
171 /**
172 * Performs the auto scale operation of the given mode without the need to create a new action.
173 * @param mode One of {@link #MODES}.
174 * @since 14221
175 */
176 public static void autoScale(AutoScaleMode mode) {
177 new AutoScaleAction(mode, false).autoScale();
178 }
179
180 /**
181 * Performs the auto scale operation of the given mode without the need to create a new action.
182 * @param mode One of {@link #MODES}.
183 * @deprecated Use {@link #autoScale(AutoScaleMode)} instead
184 */
185 @Deprecated
186 public static void autoScale(String mode) {
187 autoScale(AutoScaleMode.of(mode));
188 }
189
190 private static int getModeShortcut(String mode) {
191 int shortcut = -1;
192
193 // TODO: convert this to switch/case and make sure the parsing still works
194 // CHECKSTYLE.OFF: LeftCurly
195 // CHECKSTYLE.OFF: RightCurly
196 /* leave as single line for shortcut overview parsing! */
197 if (mode.equals("data")) { shortcut = KeyEvent.VK_1; }
198 else if (mode.equals("layer")) { shortcut = KeyEvent.VK_2; }
199 else if (mode.equals("selection")) { shortcut = KeyEvent.VK_3; }
200 else if (mode.equals("conflict")) { shortcut = KeyEvent.VK_4; }
201 else if (mode.equals("download")) { shortcut = KeyEvent.VK_5; }
202 else if (mode.equals("problem")) { shortcut = KeyEvent.VK_6; }
203 else if (mode.equals("previous")) { shortcut = KeyEvent.VK_8; }
204 else if (mode.equals("next")) { shortcut = KeyEvent.VK_9; }
205 // CHECKSTYLE.ON: LeftCurly
206 // CHECKSTYLE.ON: RightCurly
207
208 return shortcut;
209 }
210
211 /**
212 * Constructs a new {@code AutoScaleAction}.
213 * @param mode The autoscale mode (one of {@link AutoScaleMode})
214 * @param marker Must be set to false. Used only to differentiate from default constructor
215 */
216 private AutoScaleAction(AutoScaleMode mode, boolean marker) {
217 super(marker);
218 this.mode = mode;
219 }
220
221 /**
222 * Constructs a new {@code AutoScaleAction}.
223 * @param mode The autoscale mode (one of {@link AutoScaleAction#MODES})
224 * @deprecated Use {@link #AutoScaleAction(AutoScaleMode)} instead
225 */
226 @Deprecated
227 public AutoScaleAction(final String mode) {
228 this(AutoScaleMode.of(mode));
229 }
230
231 /**
232 * Constructs a new {@code AutoScaleAction}.
233 * @param mode The autoscale mode (one of {@link AutoScaleAction#MODES})
234 * @since 14221
235 */
236 public AutoScaleAction(final AutoScaleMode mode) {
237 super(tr("Zoom to {0}", mode.getLocalizedLabel()), "dialogs/autoscale/" + mode.getEnglishLabel(),
238 tr("Zoom the view to {0}.", mode.getLocalizedLabel()),
239 Shortcut.registerShortcut("view:zoom" + mode.getEnglishLabel(),
240 tr("View: {0}", tr("Zoom to {0}", mode.getLocalizedLabel())),
241 getModeShortcut(mode.getEnglishLabel()), Shortcut.DIRECT), true, null, false);
242 String label = mode.getEnglishLabel();
243 String modeHelp = Character.toUpperCase(label.charAt(0)) + label.substring(1);
244 setHelpId("Action/AutoScale/" + modeHelp);
245 this.mode = mode;
246 switch (mode) {
247 case DATA:
248 setHelpId(ht("/Action/ZoomToData"));
249 break;
250 case LAYER:
251 setHelpId(ht("/Action/ZoomToLayer"));
252 break;
253 case SELECTION:
254 setHelpId(ht("/Action/ZoomToSelection"));
255 break;
256 case CONFLICT:
257 setHelpId(ht("/Action/ZoomToConflict"));
258 break;
259 case PROBLEM:
260 setHelpId(ht("/Action/ZoomToProblem"));
261 break;
262 case DOWNLOAD:
263 setHelpId(ht("/Action/ZoomToDownload"));
264 break;
265 case PREVIOUS:
266 setHelpId(ht("/Action/ZoomToPrevious"));
267 break;
268 case NEXT:
269 setHelpId(ht("/Action/ZoomToNext"));
270 break;
271 default:
272 throw new IllegalArgumentException("Unknown mode: " + mode);
273 }
274 installAdapters();
275 }
276
277 /**
278 * Performs this auto scale operation for the mode this action is in.
279 */
280 public void autoScale() {
281 if (MainApplication.isDisplayingMapView()) {
282 MapView mapView = MainApplication.getMap().mapView;
283 switch (mode) {
284 case PREVIOUS:
285 mapView.zoomPrevious();
286 break;
287 case NEXT:
288 mapView.zoomNext();
289 break;
290 case PROBLEM:
291 modeProblem(new ValidatorBoundingXYVisitor());
292 break;
293 case DATA:
294 modeData(new BoundingXYVisitor());
295 break;
296 case LAYER:
297 modeLayer(new BoundingXYVisitor());
298 break;
299 case SELECTION:
300 case CONFLICT:
301 modeSelectionOrConflict(new BoundingXYVisitor());
302 break;
303 case DOWNLOAD:
304 modeDownload(new BoundingXYVisitor());
305 break;
306 }
307 putValue("active", Boolean.TRUE);
308 }
309 }
310
311 @Override
312 public void actionPerformed(ActionEvent e) {
313 autoScale();
314 }
315
316 /**
317 * Replies the first selected layer in the layer list dialog. null, if no
318 * such layer exists, either because the layer list dialog is not yet created
319 * or because no layer is selected.
320 *
321 * @return the first selected layer in the layer list dialog
322 */
323 protected Layer getFirstSelectedLayer() {
324 if (getLayerManager().getActiveLayer() == null) {
325 return null;
326 }
327 try {
328 List<Layer> layers = LayerListDialog.getInstance().getModel().getSelectedLayers();
329 if (!layers.isEmpty())
330 return layers.get(0);
331 } catch (IllegalStateException e) {
332 Logging.error(e);
333 }
334 return null;
335 }
336
337 private static void modeProblem(ValidatorBoundingXYVisitor v) {
338 TestError error = MainApplication.getMap().validatorDialog.getSelectedError();
339 if (error == null)
340 return;
341 v.visit(error);
342 if (v.getBounds() == null)
343 return;
344 MainApplication.getMap().mapView.zoomTo(v);
345 }
346
347 private static void modeData(BoundingXYVisitor v) {
348 for (Layer l : MainApplication.getLayerManager().getLayers()) {
349 l.visitBoundingBox(v);
350 }
351 MainApplication.getMap().mapView.zoomTo(v);
352 }
353
354 private void modeLayer(BoundingXYVisitor v) {
355 // try to zoom to the first selected layer
356 Layer l = getFirstSelectedLayer();
357 if (l == null)
358 return;
359 l.visitBoundingBox(v);
360 MainApplication.getMap().mapView.zoomTo(v);
361 }
362
363 private void modeSelectionOrConflict(BoundingXYVisitor v) {
364 Collection<IPrimitive> sel = new HashSet<>();
365 if (AutoScaleMode.SELECTION == mode) {
366 OsmData<?, ?, ?, ?> dataSet = getLayerManager().getActiveData();
367 if (dataSet != null) {
368 sel.addAll(dataSet.getSelected());
369 }
370 } else {
371 ConflictDialog conflictDialog = MainApplication.getMap().conflictDialog;
372 Conflict<? extends IPrimitive> c = conflictDialog.getSelectedConflict();
373 if (c != null) {
374 sel.add(c.getMy());
375 } else if (conflictDialog.getConflicts() != null) {
376 sel.addAll(conflictDialog.getConflicts().getMyConflictParties());
377 }
378 }
379 if (sel.isEmpty()) {
380 JOptionPane.showMessageDialog(
381 MainApplication.getMainFrame(),
382 AutoScaleMode.SELECTION == mode ? tr("Nothing selected to zoom to.") : tr("No conflicts to zoom to"),
383 tr("Information"),
384 JOptionPane.INFORMATION_MESSAGE);
385 return;
386 }
387 for (IPrimitive osm : sel) {
388 osm.accept(v);
389 }
390 if (v.getBounds() == null) {
391 return;
392 }
393
394 MainApplication.getMap().mapView.zoomTo(v);
395 }
396
397 private void modeDownload(BoundingXYVisitor v) {
398 if (lastZoomTime > 0 &&
399 System.currentTimeMillis() - lastZoomTime > Config.getPref().getLong("zoom.bounds.reset.time", TimeUnit.SECONDS.toMillis(10))) {
400 lastZoomTime = -1;
401 }
402 final DataSet dataset = getLayerManager().getActiveDataSet();
403 if (dataset != null) {
404 List<DataSource> dataSources = new ArrayList<>(dataset.getDataSources());
405 int s = dataSources.size();
406 if (s > 0) {
407 if (lastZoomTime == -1 || lastZoomArea == -1 || lastZoomArea > s) {
408 lastZoomArea = s-1;
409 v.visit(dataSources.get(lastZoomArea).bounds);
410 } else if (lastZoomArea > 0) {
411 lastZoomArea -= 1;
412 v.visit(dataSources.get(lastZoomArea).bounds);
413 } else {
414 lastZoomArea = -1;
415 Area sourceArea = getLayerManager().getActiveDataSet().getDataSourceArea();
416 if (sourceArea != null) {
417 v.visit(new Bounds(sourceArea.getBounds2D()));
418 }
419 }
420 lastZoomTime = System.currentTimeMillis();
421 } else {
422 lastZoomTime = -1;
423 lastZoomArea = -1;
424 }
425 }
426 MainApplication.getMap().mapView.zoomTo(v);
427 }
428
429 @Override
430 protected void updateEnabledState() {
431 OsmData<?, ?, ?, ?> ds = getLayerManager().getActiveData();
432 MapFrame map = MainApplication.getMap();
433 switch (mode) {
434 case SELECTION:
435 setEnabled(ds != null && !ds.selectionEmpty());
436 break;
437 case LAYER:
438 setEnabled(getFirstSelectedLayer() != null);
439 break;
440 case CONFLICT:
441 setEnabled(map != null && map.conflictDialog.getSelectedConflict() != null);
442 break;
443 case DOWNLOAD:
444 setEnabled(ds != null && !ds.getDataSources().isEmpty());
445 break;
446 case PROBLEM:
447 setEnabled(map != null && map.validatorDialog.getSelectedError() != null);
448 break;
449 case PREVIOUS:
450 setEnabled(MainApplication.isDisplayingMapView() && map.mapView.hasZoomUndoEntries());
451 break;
452 case NEXT:
453 setEnabled(MainApplication.isDisplayingMapView() && map.mapView.hasZoomRedoEntries());
454 break;
455 default:
456 setEnabled(!getLayerManager().getLayers().isEmpty());
457 }
458 }
459
460 @Override
461 protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) {
462 if (AutoScaleMode.SELECTION == mode) {
463 setEnabled(selection != null && !selection.isEmpty());
464 }
465 }
466
467 @Override
468 protected final void installAdapters() {
469 super.installAdapters();
470 // make this action listen to zoom and mapframe change events
471 //
472 MapView.addZoomChangeListener(new ZoomChangeAdapter());
473 MainApplication.addMapFrameListener(new MapFrameAdapter());
474 initEnabledState();
475 }
476
477 /**
478 * Adapter for zoom change events
479 */
480 private class ZoomChangeAdapter implements MapView.ZoomChangeListener {
481 @Override
482 public void zoomChanged() {
483 updateEnabledState();
484 }
485 }
486
487 /**
488 * Adapter for MapFrame change events
489 */
490 private class MapFrameAdapter implements MapFrameListener {
491 private ListSelectionListener conflictSelectionListener;
492 private TreeSelectionListener validatorSelectionListener;
493
494 MapFrameAdapter() {
495 if (AutoScaleMode.CONFLICT == mode) {
496 conflictSelectionListener = e -> updateEnabledState();
497 } else if (AutoScaleMode.PROBLEM == mode) {
498 validatorSelectionListener = e -> updateEnabledState();
499 }
500 }
501
502 @Override
503 public void mapFrameInitialized(MapFrame oldFrame, MapFrame newFrame) {
504 if (conflictSelectionListener != null) {
505 if (newFrame != null) {
506 newFrame.conflictDialog.addListSelectionListener(conflictSelectionListener);
507 } else if (oldFrame != null) {
508 oldFrame.conflictDialog.removeListSelectionListener(conflictSelectionListener);
509 }
510 } else if (validatorSelectionListener != null) {
511 if (newFrame != null) {
512 newFrame.validatorDialog.addTreeSelectionListener(validatorSelectionListener);
513 } else if (oldFrame != null) {
514 oldFrame.validatorDialog.removeTreeSelectionListener(validatorSelectionListener);
515 }
516 }
517 updateEnabledState();
518 }
519 }
520}
Note: See TracBrowser for help on using the repository browser.