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

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

see #15182 - deprecate shortcut handling and mapframe listener methods in Main. Replacement: same methods in gui.MainApplication

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