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

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

add AbstractOsmDataLayer, MainLayerManager.getActiveData, Main.getInProgressISelection

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