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    }