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

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

see #8039, see #10456 - support read-only data layers

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