001 // License: GPL. Copyright 2007 by Immanuel Scholz and others 002 package org.openstreetmap.josm.actions; 003 004 import static org.openstreetmap.josm.gui.help.HelpUtil.ht; 005 import static org.openstreetmap.josm.tools.I18n.marktr; 006 import static org.openstreetmap.josm.tools.I18n.tr; 007 008 import java.awt.event.ActionEvent; 009 import java.awt.event.KeyEvent; 010 import java.util.Collection; 011 import java.util.HashSet; 012 import java.util.List; 013 014 import javax.swing.JOptionPane; 015 016 import org.openstreetmap.josm.Main; 017 import org.openstreetmap.josm.data.Bounds; 018 import org.openstreetmap.josm.data.conflict.Conflict; 019 import org.openstreetmap.josm.data.osm.OsmPrimitive; 020 import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor; 021 import org.openstreetmap.josm.gui.MapView; 022 import org.openstreetmap.josm.gui.dialogs.LayerListDialog; 023 import org.openstreetmap.josm.gui.layer.Layer; 024 import org.openstreetmap.josm.tools.Shortcut; 025 026 /** 027 * Toggles the autoScale feature of the mapView 028 * @author imi 029 */ 030 public class AutoScaleAction extends JosmAction { 031 032 public static final String[] MODES = { 033 marktr("data"), 034 marktr("layer"), 035 marktr("selection"), 036 marktr("conflict"), 037 marktr("download"), 038 marktr("previous"), 039 marktr("next")}; 040 041 /** 042 * Zooms the current map view to the currently selected primitives. 043 * Does nothing if there either isn't a current map view or if there isn't a current data 044 * layer. 045 * 046 */ 047 public static void zoomToSelection() { 048 if (Main.main == null || Main.main.getEditLayer() == null) return; 049 if (Main.map == null || Main.map.mapView == null) return; 050 Collection<OsmPrimitive> sel = Main.main.getEditLayer().data.getSelected(); 051 if (sel.isEmpty()) { 052 JOptionPane.showMessageDialog( 053 Main.parent, 054 tr("Nothing selected to zoom to."), 055 tr("Information"), 056 JOptionPane.INFORMATION_MESSAGE 057 ); 058 return; 059 } 060 zoomTo(sel); 061 } 062 063 public static void zoomTo(Collection<OsmPrimitive> sel) { 064 BoundingXYVisitor bboxCalculator = new BoundingXYVisitor(); 065 bboxCalculator.computeBoundingBox(sel); 066 // increase bbox by 0.001 degrees on each side. this is required 067 // especially if the bbox contains one single node, but helpful 068 // in most other cases as well. 069 bboxCalculator.enlargeBoundingBox(); 070 if (bboxCalculator.getBounds() != null) { 071 Main.map.mapView.recalculateCenterScale(bboxCalculator); 072 } 073 } 074 075 public static void autoScale(String mode) { 076 new AutoScaleAction(mode, false).autoScale(); 077 } 078 079 private final String mode; 080 081 private static int getModeShortcut(String mode) { 082 int shortcut = -1; 083 084 /* leave as single line for shortcut overview parsing! */ 085 if (mode.equals("data")) { shortcut = KeyEvent.VK_1; } 086 else if (mode.equals("layer")) { shortcut = KeyEvent.VK_2; } 087 else if (mode.equals("selection")) { shortcut = KeyEvent.VK_3; } 088 else if (mode.equals("conflict")) { shortcut = KeyEvent.VK_4; } 089 else if (mode.equals("download")) { shortcut = KeyEvent.VK_5; } 090 else if (mode.equals("previous")) { shortcut = KeyEvent.VK_8; } 091 else if (mode.equals("next")) { shortcut = KeyEvent.VK_9; } 092 093 return shortcut; 094 } 095 096 /** 097 * 098 * @param mode 099 * @param marker Used only to differentiate from default constructor 100 */ 101 private AutoScaleAction(String mode, boolean marker) { 102 super(false); 103 this.mode = mode; 104 } 105 106 107 public AutoScaleAction(String mode) { 108 super(tr("Zoom to {0}", tr(mode)), "dialogs/autoscale/" + mode, tr("Zoom the view to {0}.", tr(mode)), 109 Shortcut.registerShortcut("view:zoom"+mode, tr("View: {0}", tr("Zoom to {0}", tr(mode))), getModeShortcut(mode), Shortcut.DIRECT), true); 110 String modeHelp = Character.toUpperCase(mode.charAt(0)) + mode.substring(1); 111 putValue("help", "Action/AutoScale/" + modeHelp); 112 this.mode = mode; 113 if (mode.equals("data")) { 114 putValue("help", ht("/Action/ZoomToData")); 115 } else if (mode.equals("layer")) { 116 putValue("help", ht("/Action/ZoomToLayer")); 117 } else if (mode.equals("selection")) { 118 putValue("help", ht("/Action/ZoomToSelection")); 119 } else if (mode.equals("conflict")) { 120 putValue("help", ht("/Action/ZoomToConflict")); 121 } else if (mode.equals("download")) { 122 putValue("help", ht("/Action/ZoomToDownload")); 123 } else if (mode.equals("previous")) { 124 putValue("help", ht("/Action/ZoomToPrevious")); 125 } else if (mode.equals("next")) { 126 putValue("help", ht("/Action/ZoomToNext")); 127 } 128 } 129 130 public void autoScale() { 131 if (Main.isDisplayingMapView()) { 132 if (mode.equals("previous")) { 133 Main.map.mapView.zoomPrevious(); 134 } else if (mode.equals("next")) { 135 Main.map.mapView.zoomNext(); 136 } else { 137 BoundingXYVisitor bbox = getBoundingBox(); 138 if (bbox != null && bbox.getBounds() != null) { 139 Main.map.mapView.recalculateCenterScale(bbox); 140 } 141 } 142 } 143 putValue("active", true); 144 } 145 146 public void actionPerformed(ActionEvent e) { 147 autoScale(); 148 } 149 150 protected Layer getActiveLayer() { 151 try { 152 return Main.map.mapView.getActiveLayer(); 153 } catch(NullPointerException e) { 154 return null; 155 } 156 } 157 158 /** 159 * Replies the first selected layer in the layer list dialog. null, if no 160 * such layer exists, either because the layer list dialog is not yet created 161 * or because no layer is selected. 162 * 163 * @return the first selected layer in the layer list dialog 164 */ 165 protected Layer getFirstSelectedLayer() { 166 if (LayerListDialog.getInstance() == null) return null; 167 List<Layer> layers = LayerListDialog.getInstance().getModel().getSelectedLayers(); 168 if (layers.isEmpty()) return null; 169 return layers.get(0); 170 } 171 172 private BoundingXYVisitor getBoundingBox() { 173 BoundingXYVisitor v = new BoundingXYVisitor(); 174 if (mode.equals("data")) { 175 for (Layer l : Main.map.mapView.getAllLayers()) { 176 l.visitBoundingBox(v); 177 } 178 } else if (mode.equals("layer")) { 179 if (getActiveLayer() == null) 180 return null; 181 // try to zoom to the first selected layer 182 // 183 Layer l = getFirstSelectedLayer(); 184 if (l == null) return null; 185 l.visitBoundingBox(v); 186 } else if (mode.equals("selection") || mode.equals("conflict")) { 187 Collection<OsmPrimitive> sel = new HashSet<OsmPrimitive>(); 188 if (mode.equals("selection")) { 189 sel = getCurrentDataSet().getSelected(); 190 } else if (mode.equals("conflict")) { 191 Conflict<? extends OsmPrimitive> c = Main.map.conflictDialog.getSelectedConflict(); 192 if (c != null) { 193 sel.add(c.getMy()); 194 } else if (Main.map.conflictDialog.getConflicts() != null) { 195 sel = Main.map.conflictDialog.getConflicts().getMyConflictParties(); 196 } 197 } 198 if (sel.isEmpty()) { 199 JOptionPane.showMessageDialog( 200 Main.parent, 201 (mode.equals("selection") ? tr("Nothing selected to zoom to.") : tr("No conflicts to zoom to")), 202 tr("Information"), 203 JOptionPane.INFORMATION_MESSAGE 204 ); 205 return null; 206 } 207 for (OsmPrimitive osm : sel) { 208 osm.visit(v); 209 } 210 // increase bbox by 0.001 degrees on each side. this is required 211 // especially if the bbox contains one single node, but helpful 212 // in most other cases as well. 213 v.enlargeBoundingBox(); 214 } 215 else if (mode.equals("download")) { 216 if (!Main.pref.get("osm-download.bounds").isEmpty()) { 217 try { 218 v.visit(new Bounds(Main.pref.get("osm-download.bounds"), ";")); 219 } catch (Exception e) { 220 e.printStackTrace(); 221 } 222 } 223 } 224 return v; 225 } 226 227 @Override 228 protected void updateEnabledState() { 229 if ("selection".equals(mode)) { 230 setEnabled(getCurrentDataSet() != null && ! getCurrentDataSet().getSelected().isEmpty()); 231 } else if ("layer".equals(mode)) { 232 if (Main.map == null || Main.map.mapView == null || Main.map.mapView.getAllLayersAsList().isEmpty()) { 233 setEnabled(false); 234 } else { 235 // FIXME: should also check for whether a layer is selected in the layer list dialog 236 setEnabled(true); 237 } 238 } else if ("previous".equals(mode)) { 239 setEnabled(Main.isDisplayingMapView() && Main.map.mapView.hasZoomUndoEntries()); 240 } else if ("next".equals(mode)) { 241 setEnabled(Main.isDisplayingMapView() && Main.map.mapView.hasZoomRedoEntries()); 242 } else { 243 setEnabled( 244 Main.isDisplayingMapView() 245 && Main.map.mapView.hasLayers() 246 ); 247 } 248 } 249 250 @Override 251 protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) { 252 if ("selection".equals(mode)) { 253 setEnabled(selection != null && !selection.isEmpty()); 254 } 255 } 256 257 @Override 258 protected void installAdapters() { 259 super.installAdapters(); 260 // make this action listen to zoom change events 261 // 262 zoomChangeAdapter = new ZoomChangeAdapter(); 263 MapView.addZoomChangeListener(zoomChangeAdapter); 264 initEnabledState(); 265 } 266 267 /** 268 * Adapter for selection change events 269 * 270 */ 271 private class ZoomChangeAdapter implements MapView.ZoomChangeListener { 272 public void zoomChanged() { 273 updateEnabledState(); 274 } 275 } 276 277 private ZoomChangeAdapter zoomChangeAdapter; 278 }