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

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

see #15229 - deprecate Main.parent and Main itself

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