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 }