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

Last change on this file since 14119 was 13955, checked in by Don-vip, 8 years ago

use IPrimitive in AutoScaleAction

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