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

Last change on this file since 8727 was 8513, checked in by Don-vip, 9 years ago

checkstyle: blocks

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