// License: GPL. For details, see LICENSE file. package org.openstreetmap.josm.actions; import static org.openstreetmap.josm.gui.help.HelpUtil.ht; import static org.openstreetmap.josm.tools.I18n.marktr; import static org.openstreetmap.josm.tools.I18n.tr; import java.awt.event.ActionEvent; import java.awt.event.KeyEvent; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; import javax.swing.JOptionPane; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import javax.swing.event.TreeSelectionEvent; import javax.swing.event.TreeSelectionListener; import org.openstreetmap.josm.Main; import org.openstreetmap.josm.data.Bounds; import org.openstreetmap.josm.data.conflict.Conflict; import org.openstreetmap.josm.data.osm.OsmPrimitive; import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor; import org.openstreetmap.josm.data.validation.TestError; import org.openstreetmap.josm.gui.MapFrame; import org.openstreetmap.josm.gui.MapFrameListener; import org.openstreetmap.josm.gui.MapView; import org.openstreetmap.josm.gui.dialogs.LayerListDialog; import org.openstreetmap.josm.gui.dialogs.ValidatorDialog.ValidatorBoundingXYVisitor; import org.openstreetmap.josm.gui.download.DownloadDialog; import org.openstreetmap.josm.gui.layer.Layer; import org.openstreetmap.josm.tools.Shortcut; /** * Toggles the autoScale feature of the mapView * @author imi */ public class AutoScaleAction extends JosmAction { public static final Collection MODES = Collections.unmodifiableList(Arrays.asList( marktr("data"), marktr("layer"), marktr("selection"), marktr("conflict"), marktr("download"), marktr("problem"), marktr("previous"), marktr("next"))); private final String mode; protected ZoomChangeAdapter zoomChangeAdapter; protected MapFrameAdapter mapFrameAdapter; /** * Zooms the current map view to the currently selected primitives. * Does nothing if there either isn't a current map view or if there isn't a current data * layer. * */ public static void zoomToSelection() { if (Main.main == null || !Main.main.hasEditLayer()) return; Collection sel = Main.main.getEditLayer().data.getSelected(); if (sel.isEmpty()) { JOptionPane.showMessageDialog( Main.parent, tr("Nothing selected to zoom to."), tr("Information"), JOptionPane.INFORMATION_MESSAGE ); return; } zoomTo(sel); } public static void zoomTo(Collection sel) { BoundingXYVisitor bboxCalculator = new BoundingXYVisitor(); bboxCalculator.computeBoundingBox(sel); // increase bbox by 0.001 degrees on each side. this is required // especially if the bbox contains one single node, but helpful // in most other cases as well. bboxCalculator.enlargeBoundingBox(); if (bboxCalculator.getBounds() != null) { Main.map.mapView.recalculateCenterScale(bboxCalculator); } } public static void autoScale(String mode) { new AutoScaleAction(mode, false).autoScale(); } private static int getModeShortcut(String mode) { int shortcut = -1; // TODO: convert this to switch/case and make sure the parsing still works /* leave as single line for shortcut overview parsing! */ if (mode.equals("data")) { shortcut = KeyEvent.VK_1; } else if (mode.equals("layer")) { shortcut = KeyEvent.VK_2; } else if (mode.equals("selection")) { shortcut = KeyEvent.VK_3; } else if (mode.equals("conflict")) { shortcut = KeyEvent.VK_4; } else if (mode.equals("download")) { shortcut = KeyEvent.VK_5; } else if (mode.equals("problem")) { shortcut = KeyEvent.VK_6; } else if (mode.equals("previous")) { shortcut = KeyEvent.VK_8; } else if (mode.equals("next")) { shortcut = KeyEvent.VK_9; } return shortcut; } /** * Constructs a new {@code AutoScaleAction}. * @param mode The autoscale mode (one of {@link AutoScaleAction#MODES}) * @param marker Used only to differentiate from default constructor */ private AutoScaleAction(String mode, boolean marker) { super(false); this.mode = mode; } /** * Constructs a new {@code AutoScaleAction}. * @param mode The autoscale mode (one of {@link AutoScaleAction#MODES}) */ public AutoScaleAction(final String mode) { super(tr("Zoom to {0}", tr(mode)), "dialogs/autoscale/" + mode, tr("Zoom the view to {0}.", tr(mode)), Shortcut.registerShortcut("view:zoom"+mode, tr("View: {0}", tr("Zoom to {0}", tr(mode))), getModeShortcut(mode), Shortcut.DIRECT), true, null, false); String modeHelp = Character.toUpperCase(mode.charAt(0)) + mode.substring(1); putValue("help", "Action/AutoScale/" + modeHelp); this.mode = mode; switch (mode) { case "data": putValue("help", ht("/Action/ZoomToData")); break; case "layer": putValue("help", ht("/Action/ZoomToLayer")); break; case "selection": putValue("help", ht("/Action/ZoomToSelection")); break; case "conflict": putValue("help", ht("/Action/ZoomToConflict")); break; case "problem": putValue("help", ht("/Action/ZoomToProblem")); break; case "download": putValue("help", ht("/Action/ZoomToDownload")); break; case "previous": putValue("help", ht("/Action/ZoomToPrevious")); break; case "next": putValue("help", ht("/Action/ZoomToNext")); break; default: throw new IllegalArgumentException("Unknown mode: "+mode); } installAdapters(); } public void autoScale() { if (Main.isDisplayingMapView()) { switch(mode) { case "previous": Main.map.mapView.zoomPrevious(); break; case "next": Main.map.mapView.zoomNext(); break; default: BoundingXYVisitor bbox = getBoundingBox(); if (bbox != null && bbox.getBounds() != null) { Main.map.mapView.recalculateCenterScale(bbox); } } } putValue("active", true); } @Override public void actionPerformed(ActionEvent e) { autoScale(); } /** * Replies the first selected layer in the layer list dialog. null, if no * such layer exists, either because the layer list dialog is not yet created * or because no layer is selected. * * @return the first selected layer in the layer list dialog */ protected Layer getFirstSelectedLayer() { List layers = LayerListDialog.getInstance().getModel().getSelectedLayers(); if (layers.isEmpty()) return null; return layers.get(0); } private BoundingXYVisitor getBoundingBox() { BoundingXYVisitor v = "problem".equals(mode) ? new ValidatorBoundingXYVisitor() : new BoundingXYVisitor(); switch(mode) { case "problem": TestError error = Main.map.validatorDialog.getSelectedError(); if (error == null) return null; ((ValidatorBoundingXYVisitor) v).visit(error); if (v.getBounds() == null) return null; v.enlargeBoundingBox(Main.pref.getDouble("validator.zoom-enlarge-bbox", 0.0002)); break; case "data": for (Layer l : Main.map.mapView.getAllLayers()) { l.visitBoundingBox(v); } break; case "layer": if (Main.main.getActiveLayer() == null) return null; // try to zoom to the first selected layer Layer l = getFirstSelectedLayer(); if (l == null) return null; l.visitBoundingBox(v); break; case "selection": case "conflict": Collection sel = new HashSet<>(); if ("selection".equals(mode)) { sel = getCurrentDataSet().getSelected(); } else { Conflict c = Main.map.conflictDialog.getSelectedConflict(); if (c != null) { sel.add(c.getMy()); } else if (Main.map.conflictDialog.getConflicts() != null) { sel = Main.map.conflictDialog.getConflicts().getMyConflictParties(); } } if (sel.isEmpty()) { JOptionPane.showMessageDialog( Main.parent, ("selection".equals(mode) ? tr("Nothing selected to zoom to.") : tr("No conflicts to zoom to")), tr("Information"), JOptionPane.INFORMATION_MESSAGE ); return null; } for (OsmPrimitive osm : sel) { osm.accept(v); } // Increase the bounding box by up to 100% to give more context. v.enlargeBoundingBoxLogarithmically(100); // Make the bounding box at least 100 meter wide to // ensure reasonable zoom level when zooming onto single nodes. v.enlargeToMinSize(Main.pref.getDouble("zoom_to_selection_min_size_in_meter", 100)); break; case "download": Bounds bounds = DownloadDialog.getSavedDownloadBounds(); if (bounds != null) { try { v.visit(bounds); } catch (Exception e) { Main.warn(e); } } break; } return v; } @Override protected void updateEnabledState() { switch(mode) { case "selection": setEnabled(getCurrentDataSet() != null && ! getCurrentDataSet().getSelected().isEmpty()); break; case "layer": if (!Main.isDisplayingMapView() || Main.map.mapView.getAllLayersAsList().isEmpty()) { setEnabled(false); } else { // FIXME: should also check for whether a layer is selected in the layer list dialog setEnabled(true); } break; case "conflict": setEnabled(Main.map != null && Main.map.conflictDialog.getSelectedConflict() != null); break; case "problem": setEnabled(Main.map != null && Main.map.validatorDialog.getSelectedError() != null); break; case "previous": setEnabled(Main.isDisplayingMapView() && Main.map.mapView.hasZoomUndoEntries()); break; case "next": setEnabled(Main.isDisplayingMapView() && Main.map.mapView.hasZoomRedoEntries()); break; default: setEnabled(Main.isDisplayingMapView() && Main.map.mapView.hasLayers() ); } } @Override protected void updateEnabledState(Collection selection) { if ("selection".equals(mode)) { setEnabled(selection != null && !selection.isEmpty()); } } @Override protected final void installAdapters() { super.installAdapters(); // make this action listen to zoom and mapframe change events // MapView.addZoomChangeListener(zoomChangeAdapter = new ZoomChangeAdapter()); Main.addMapFrameListener(mapFrameAdapter = new MapFrameAdapter()); initEnabledState(); } /** * Adapter for zoom change events */ private class ZoomChangeAdapter implements MapView.ZoomChangeListener { @Override public void zoomChanged() { updateEnabledState(); } } /** * Adapter for MapFrame change events */ private class MapFrameAdapter implements MapFrameListener { private ListSelectionListener conflictSelectionListener; private TreeSelectionListener validatorSelectionListener; public MapFrameAdapter() { if ("conflict".equals(mode)) { conflictSelectionListener = new ListSelectionListener() { @Override public void valueChanged(ListSelectionEvent e) { updateEnabledState(); } }; } else if ("problem".equals(mode)) { validatorSelectionListener = new TreeSelectionListener() { @Override public void valueChanged(TreeSelectionEvent e) { updateEnabledState(); } }; } } @Override public void mapFrameInitialized(MapFrame oldFrame, MapFrame newFrame) { if (conflictSelectionListener != null) { if (newFrame != null) { newFrame.conflictDialog.addListSelectionListener(conflictSelectionListener); } else if (oldFrame != null) { oldFrame.conflictDialog.removeListSelectionListener(conflictSelectionListener); } } else if (validatorSelectionListener != null) { if (newFrame != null) { newFrame.validatorDialog.addTreeSelectionListener(validatorSelectionListener); } else if (oldFrame != null) { oldFrame.validatorDialog.removeTreeSelectionListener(validatorSelectionListener); } } } } }