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

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

sonar - squid:S2184 - Math operands should be cast before assignment

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