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

Last change on this file since 12467 was 12235, checked in by Don-vip, 7 years ago

fix #14605 - catch IllegalStateException

  • Property svn:eol-style set to native
File size: 15.9 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.concurrent.TimeUnit;
18
19import javax.swing.JOptionPane;
20import javax.swing.event.ListSelectionListener;
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 DataSet dataSet = Main.getLayerManager().getEditDataSet();
76 if (dataSet == null) {
77 return;
78 }
79 Collection<OsmPrimitive> sel = dataSet.getSelected();
80 if (sel.isEmpty()) {
81 JOptionPane.showMessageDialog(
82 Main.parent,
83 tr("Nothing selected to zoom to."),
84 tr("Information"),
85 JOptionPane.INFORMATION_MESSAGE);
86 return;
87 }
88 zoomTo(sel);
89 }
90
91 /**
92 * Zooms the view to display the given set of primitives.
93 * @param sel The primitives to zoom to, e.g. the current selection.
94 */
95 public static void zoomTo(Collection<OsmPrimitive> sel) {
96 BoundingXYVisitor bboxCalculator = new BoundingXYVisitor();
97 bboxCalculator.computeBoundingBox(sel);
98 // increase bbox. This is required
99 // especially if the bbox contains one single node, but helpful
100 // in most other cases as well.
101 bboxCalculator.enlargeBoundingBox();
102 if (bboxCalculator.getBounds() != null) {
103 Main.map.mapView.zoomTo(bboxCalculator);
104 }
105 }
106
107 /**
108 * Performs the auto scale operation of the given mode without the need to create a new action.
109 * @param mode One of {@link #MODES}.
110 */
111 public static void autoScale(String mode) {
112 new AutoScaleAction(mode, false).autoScale();
113 }
114
115 private static int getModeShortcut(String mode) {
116 int shortcut = -1;
117
118 // TODO: convert this to switch/case and make sure the parsing still works
119 // CHECKSTYLE.OFF: LeftCurly
120 // CHECKSTYLE.OFF: RightCurly
121 /* leave as single line for shortcut overview parsing! */
122 if (mode.equals("data")) { shortcut = KeyEvent.VK_1; }
123 else if (mode.equals("layer")) { shortcut = KeyEvent.VK_2; }
124 else if (mode.equals("selection")) { shortcut = KeyEvent.VK_3; }
125 else if (mode.equals("conflict")) { shortcut = KeyEvent.VK_4; }
126 else if (mode.equals("download")) { shortcut = KeyEvent.VK_5; }
127 else if (mode.equals("problem")) { shortcut = KeyEvent.VK_6; }
128 else if (mode.equals("previous")) { shortcut = KeyEvent.VK_8; }
129 else if (mode.equals("next")) { shortcut = KeyEvent.VK_9; }
130 // CHECKSTYLE.ON: LeftCurly
131 // CHECKSTYLE.ON: RightCurly
132
133 return shortcut;
134 }
135
136 /**
137 * Constructs a new {@code AutoScaleAction}.
138 * @param mode The autoscale mode (one of {@link AutoScaleAction#MODES})
139 * @param marker Must be set to false. Used only to differentiate from default constructor
140 */
141 private AutoScaleAction(String mode, boolean marker) {
142 super(marker);
143 this.mode = mode;
144 }
145
146 /**
147 * Constructs a new {@code AutoScaleAction}.
148 * @param mode The autoscale mode (one of {@link AutoScaleAction#MODES})
149 */
150 public AutoScaleAction(final String mode) {
151 super(tr("Zoom to {0}", tr(mode)), "dialogs/autoscale/" + mode, tr("Zoom the view to {0}.", tr(mode)),
152 Shortcut.registerShortcut("view:zoom" + mode, tr("View: {0}", tr("Zoom to {0}", tr(mode))),
153 getModeShortcut(mode), Shortcut.DIRECT), true, null, false);
154 String modeHelp = Character.toUpperCase(mode.charAt(0)) + mode.substring(1);
155 putValue("help", "Action/AutoScale/" + modeHelp);
156 this.mode = mode;
157 switch (mode) {
158 case "data":
159 putValue("help", ht("/Action/ZoomToData"));
160 break;
161 case "layer":
162 putValue("help", ht("/Action/ZoomToLayer"));
163 break;
164 case "selection":
165 putValue("help", ht("/Action/ZoomToSelection"));
166 break;
167 case "conflict":
168 putValue("help", ht("/Action/ZoomToConflict"));
169 break;
170 case "problem":
171 putValue("help", ht("/Action/ZoomToProblem"));
172 break;
173 case "download":
174 putValue("help", ht("/Action/ZoomToDownload"));
175 break;
176 case "previous":
177 putValue("help", ht("/Action/ZoomToPrevious"));
178 break;
179 case "next":
180 putValue("help", ht("/Action/ZoomToNext"));
181 break;
182 default:
183 throw new IllegalArgumentException("Unknown mode: " + mode);
184 }
185 installAdapters();
186 }
187
188 /**
189 * Performs this auto scale operation for the mode this action is in.
190 */
191 public void autoScale() {
192 if (Main.isDisplayingMapView()) {
193 switch (mode) {
194 case "previous":
195 Main.map.mapView.zoomPrevious();
196 break;
197 case "next":
198 Main.map.mapView.zoomNext();
199 break;
200 default:
201 BoundingXYVisitor bbox = getBoundingBox();
202 if (bbox != null && bbox.getBounds() != null) {
203 Main.map.mapView.zoomTo(bbox);
204 }
205 }
206 }
207 putValue("active", Boolean.TRUE);
208 }
209
210 @Override
211 public void actionPerformed(ActionEvent e) {
212 autoScale();
213 }
214
215 /**
216 * Replies the first selected layer in the layer list dialog. null, if no
217 * such layer exists, either because the layer list dialog is not yet created
218 * or because no layer is selected.
219 *
220 * @return the first selected layer in the layer list dialog
221 */
222 protected Layer getFirstSelectedLayer() {
223 if (Main.getLayerManager().getActiveLayer() == null) {
224 return null;
225 }
226 try {
227 List<Layer> layers = LayerListDialog.getInstance().getModel().getSelectedLayers();
228 if (!layers.isEmpty())
229 return layers.get(0);
230 } catch (IllegalStateException e) {
231 Main.error(e);
232 }
233 return null;
234 }
235
236 private BoundingXYVisitor getBoundingBox() {
237 switch (mode) {
238 case "problem":
239 return modeProblem(new ValidatorBoundingXYVisitor());
240 case "data":
241 return modeData(new BoundingXYVisitor());
242 case "layer":
243 return modeLayer(new BoundingXYVisitor());
244 case "selection":
245 case "conflict":
246 return modeSelectionOrConflict(new BoundingXYVisitor());
247 case "download":
248 return modeDownload(new BoundingXYVisitor());
249 default:
250 return new BoundingXYVisitor();
251 }
252 }
253
254 private static BoundingXYVisitor modeProblem(ValidatorBoundingXYVisitor v) {
255 TestError error = Main.map.validatorDialog.getSelectedError();
256 if (error == null)
257 return null;
258 v.visit(error);
259 if (v.getBounds() == null)
260 return null;
261 v.enlargeBoundingBox(Main.pref.getDouble("validator.zoom-enlarge-bbox", 0.0002));
262 return v;
263 }
264
265 private static BoundingXYVisitor modeData(BoundingXYVisitor v) {
266 for (Layer l : Main.getLayerManager().getLayers()) {
267 l.visitBoundingBox(v);
268 }
269 return v;
270 }
271
272 private BoundingXYVisitor modeLayer(BoundingXYVisitor v) {
273 // try to zoom to the first selected layer
274 Layer l = getFirstSelectedLayer();
275 if (l == null)
276 return null;
277 l.visitBoundingBox(v);
278 return v;
279 }
280
281 private BoundingXYVisitor modeSelectionOrConflict(BoundingXYVisitor v) {
282 Collection<OsmPrimitive> sel = new HashSet<>();
283 if ("selection".equals(mode)) {
284 DataSet dataSet = getLayerManager().getEditDataSet();
285 if (dataSet != null) {
286 sel = dataSet.getSelected();
287 }
288 } else {
289 Conflict<? extends OsmPrimitive> c = Main.map.conflictDialog.getSelectedConflict();
290 if (c != null) {
291 sel.add(c.getMy());
292 } else if (Main.map.conflictDialog.getConflicts() != null) {
293 sel = Main.map.conflictDialog.getConflicts().getMyConflictParties();
294 }
295 }
296 if (sel.isEmpty()) {
297 JOptionPane.showMessageDialog(
298 Main.parent,
299 "selection".equals(mode) ? tr("Nothing selected to zoom to.") : tr("No conflicts to zoom to"),
300 tr("Information"),
301 JOptionPane.INFORMATION_MESSAGE);
302 return null;
303 }
304 for (OsmPrimitive osm : sel) {
305 osm.accept(v);
306 }
307
308 // Increase the bounding box by up to 100% to give more context.
309 v.enlargeBoundingBoxLogarithmically(100);
310 // Make the bounding box at least 100 meter wide to
311 // ensure reasonable zoom level when zooming onto single nodes.
312 v.enlargeToMinSize(Main.pref.getDouble("zoom_to_selection_min_size_in_meter", 100));
313 return v;
314 }
315
316 private BoundingXYVisitor modeDownload(BoundingXYVisitor v) {
317 if (lastZoomTime > 0 &&
318 System.currentTimeMillis() - lastZoomTime > Main.pref.getLong("zoom.bounds.reset.time", TimeUnit.SECONDS.toMillis(10))) {
319 lastZoomTime = -1;
320 }
321 final DataSet dataset = getLayerManager().getEditDataSet();
322 if (dataset != null) {
323 List<DataSource> dataSources = new ArrayList<>(dataset.getDataSources());
324 int s = dataSources.size();
325 if (s > 0) {
326 if (lastZoomTime == -1 || lastZoomArea == -1 || lastZoomArea > s) {
327 lastZoomArea = s-1;
328 v.visit(dataSources.get(lastZoomArea).bounds);
329 } else if (lastZoomArea > 0) {
330 lastZoomArea -= 1;
331 v.visit(dataSources.get(lastZoomArea).bounds);
332 } else {
333 lastZoomArea = -1;
334 Area sourceArea = Main.getLayerManager().getEditDataSet().getDataSourceArea();
335 if (sourceArea != null) {
336 v.visit(new Bounds(sourceArea.getBounds2D()));
337 }
338 }
339 lastZoomTime = System.currentTimeMillis();
340 } else {
341 lastZoomTime = -1;
342 lastZoomArea = -1;
343 }
344 }
345 return v;
346 }
347
348 @Override
349 protected void updateEnabledState() {
350 DataSet ds = getLayerManager().getEditDataSet();
351 switch (mode) {
352 case "selection":
353 setEnabled(ds != null && !ds.selectionEmpty());
354 break;
355 case "layer":
356 setEnabled(getFirstSelectedLayer() != null);
357 break;
358 case "conflict":
359 setEnabled(Main.map != null && Main.map.conflictDialog.getSelectedConflict() != null);
360 break;
361 case "download":
362 setEnabled(ds != null && !ds.getDataSources().isEmpty());
363 break;
364 case "problem":
365 setEnabled(Main.map != null && Main.map.validatorDialog.getSelectedError() != null);
366 break;
367 case "previous":
368 setEnabled(Main.isDisplayingMapView() && Main.map.mapView.hasZoomUndoEntries());
369 break;
370 case "next":
371 setEnabled(Main.isDisplayingMapView() && Main.map.mapView.hasZoomRedoEntries());
372 break;
373 default:
374 setEnabled(!getLayerManager().getLayers().isEmpty());
375 }
376 }
377
378 @Override
379 protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) {
380 if ("selection".equals(mode)) {
381 setEnabled(selection != null && !selection.isEmpty());
382 }
383 }
384
385 @Override
386 protected final void installAdapters() {
387 super.installAdapters();
388 // make this action listen to zoom and mapframe change events
389 //
390 MapView.addZoomChangeListener(new ZoomChangeAdapter());
391 Main.addMapFrameListener(new MapFrameAdapter());
392 initEnabledState();
393 }
394
395 /**
396 * Adapter for zoom change events
397 */
398 private class ZoomChangeAdapter implements MapView.ZoomChangeListener {
399 @Override
400 public void zoomChanged() {
401 updateEnabledState();
402 }
403 }
404
405 /**
406 * Adapter for MapFrame change events
407 */
408 private class MapFrameAdapter implements MapFrameListener {
409 private ListSelectionListener conflictSelectionListener;
410 private TreeSelectionListener validatorSelectionListener;
411
412 MapFrameAdapter() {
413 if ("conflict".equals(mode)) {
414 conflictSelectionListener = e -> updateEnabledState();
415 } else if ("problem".equals(mode)) {
416 validatorSelectionListener = e -> updateEnabledState();
417 }
418 }
419
420 @Override
421 public void mapFrameInitialized(MapFrame oldFrame, MapFrame newFrame) {
422 if (conflictSelectionListener != null) {
423 if (newFrame != null) {
424 newFrame.conflictDialog.addListSelectionListener(conflictSelectionListener);
425 } else if (oldFrame != null) {
426 oldFrame.conflictDialog.removeListSelectionListener(conflictSelectionListener);
427 }
428 } else if (validatorSelectionListener != null) {
429 if (newFrame != null) {
430 newFrame.validatorDialog.addTreeSelectionListener(validatorSelectionListener);
431 } else if (oldFrame != null) {
432 oldFrame.validatorDialog.removeTreeSelectionListener(validatorSelectionListener);
433 }
434 }
435 updateEnabledState();
436 }
437 }
438}
Note: See TracBrowser for help on using the repository browser.