source: josm/trunk/src/org/openstreetmap/josm/actions/ImageryAdjustAction.java @ 13433

Last change on this file since 13433 was 13433, checked in by Don-vip, 12 months ago

fix #15849, see #15716 - IAE when changing active layer while setting offset

  • Property svn:eol-style set to native
File size: 13.6 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.actions;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.AWTEvent;
7import java.awt.Cursor;
8import java.awt.GridBagLayout;
9import java.awt.Insets;
10import java.awt.Toolkit;
11import java.awt.event.AWTEventListener;
12import java.awt.event.ActionEvent;
13import java.awt.event.FocusEvent;
14import java.awt.event.FocusListener;
15import java.awt.event.KeyEvent;
16import java.awt.event.MouseEvent;
17import java.util.Formatter;
18import java.util.Locale;
19
20import javax.swing.JLabel;
21import javax.swing.JPanel;
22
23import org.openstreetmap.josm.Main;
24import org.openstreetmap.josm.actions.mapmode.MapMode;
25import org.openstreetmap.josm.data.coor.EastNorth;
26import org.openstreetmap.josm.data.coor.LatLon;
27import org.openstreetmap.josm.data.imagery.OffsetBookmark;
28import org.openstreetmap.josm.gui.ExtendedDialog;
29import org.openstreetmap.josm.gui.MainApplication;
30import org.openstreetmap.josm.gui.MapFrame;
31import org.openstreetmap.josm.gui.MapView;
32import org.openstreetmap.josm.gui.layer.AbstractTileSourceLayer;
33import org.openstreetmap.josm.gui.util.WindowGeometry;
34import org.openstreetmap.josm.gui.widgets.JMultilineLabel;
35import org.openstreetmap.josm.gui.widgets.JosmTextField;
36import org.openstreetmap.josm.tools.GBC;
37import org.openstreetmap.josm.tools.ImageProvider;
38import org.openstreetmap.josm.tools.JosmDecimalFormatSymbolsProvider;
39import org.openstreetmap.josm.tools.Logging;
40
41/**
42 * Adjust the position of an imagery layer.
43 * @since 3715
44 */
45public class ImageryAdjustAction extends MapMode implements AWTEventListener {
46    private static volatile ImageryOffsetDialog offsetDialog;
47    private static Cursor cursor = ImageProvider.getCursor("normal", "move");
48
49    private OffsetBookmark old;
50    private OffsetBookmark tempOffset;
51    private EastNorth prevEastNorth;
52    private transient AbstractTileSourceLayer<?> layer;
53    private MapMode oldMapMode;
54    private boolean exitingMode;
55
56    /**
57     * Constructs a new {@code ImageryAdjustAction} for the given layer.
58     * @param layer The imagery layer
59     */
60    public ImageryAdjustAction(AbstractTileSourceLayer<?> layer) {
61        super(tr("New offset"), "adjustimg", tr("Adjust the position of this imagery layer"), cursor);
62        putValue("toolbar", Boolean.FALSE);
63        this.layer = layer;
64    }
65
66    @Override
67    public void enterMode() {
68        super.enterMode();
69        if (layer == null)
70            return;
71        if (!layer.isVisible()) {
72            layer.setVisible(true);
73        }
74        old = layer.getDisplaySettings().getOffsetBookmark();
75        EastNorth curOff = old == null ? EastNorth.ZERO : old.getDisplacement(Main.getProjection());
76        LatLon center;
77        if (MainApplication.isDisplayingMapView()) {
78            center = Main.getProjection().eastNorth2latlon(MainApplication.getMap().mapView.getCenter());
79        } else {
80            center = LatLon.ZERO;
81        }
82        tempOffset = new OffsetBookmark(
83                Main.getProjection().toCode(),
84                layer.getInfo().getName(),
85                null,
86                curOff, center);
87        layer.getDisplaySettings().setOffsetBookmark(tempOffset);
88        addListeners();
89        showOffsetDialog(new ImageryOffsetDialog());
90    }
91
92    private static void showOffsetDialog(ImageryOffsetDialog dlg) {
93        offsetDialog = dlg;
94        offsetDialog.setVisible(true);
95    }
96
97    private static void hideOffsetDialog() {
98        offsetDialog.setVisible(false);
99        offsetDialog = null;
100    }
101
102    protected void addListeners() {
103        MapView mapView = MainApplication.getMap().mapView;
104        mapView.addMouseListener(this);
105        mapView.addMouseMotionListener(this);
106        try {
107            Toolkit.getDefaultToolkit().addAWTEventListener(this, AWTEvent.KEY_EVENT_MASK);
108        } catch (SecurityException ex) {
109            Logging.error(ex);
110        }
111    }
112
113    @Override
114    public void exitMode() {
115        exitingMode = true;
116        super.exitMode();
117        if (offsetDialog != null) {
118            if (layer != null) {
119                layer.getDisplaySettings().setOffsetBookmark(old);
120            }
121            hideOffsetDialog();
122            // do not restore old mode here - this is called when the new mode is already known.
123        }
124        removeListeners();
125        exitingMode = false;
126    }
127
128    protected void removeListeners() {
129        try {
130            Toolkit.getDefaultToolkit().removeAWTEventListener(this);
131        } catch (SecurityException ex) {
132            Logging.error(ex);
133        }
134        if (MainApplication.isDisplayingMapView()) {
135            MapFrame map = MainApplication.getMap();
136            map.mapView.removeMouseMotionListener(this);
137            map.mapView.removeMouseListener(this);
138        }
139    }
140
141    @Override
142    public void eventDispatched(AWTEvent event) {
143        if (!(event instanceof KeyEvent)
144          || (event.getID() != KeyEvent.KEY_PRESSED)
145          || (layer == null)
146          || (offsetDialog != null && offsetDialog.areFieldsInFocus())) {
147            return;
148        }
149        KeyEvent kev = (KeyEvent) event;
150        int dx = 0;
151        int dy = 0;
152        switch (kev.getKeyCode()) {
153        case KeyEvent.VK_UP : dy = +1; break;
154        case KeyEvent.VK_DOWN : dy = -1; break;
155        case KeyEvent.VK_LEFT : dx = -1; break;
156        case KeyEvent.VK_RIGHT : dx = +1; break;
157        case KeyEvent.VK_ESCAPE:
158            if (offsetDialog != null) {
159                offsetDialog.setVisible(false);
160                return;
161            }
162            break;
163        default: // Do nothing
164        }
165        if (dx != 0 || dy != 0) {
166            double ppd = layer.getPPD();
167            EastNorth d = tempOffset.getDisplacement().add(new EastNorth(dx / ppd, dy / ppd));
168            tempOffset.setDisplacement(d);
169            layer.getDisplaySettings().setOffsetBookmark(tempOffset);
170            if (offsetDialog != null) {
171                offsetDialog.updateOffset();
172            }
173            if (Logging.isDebugEnabled()) {
174                Logging.debug("{0} consuming event {1}", getClass().getName(), kev);
175            }
176            kev.consume();
177        }
178    }
179
180    @Override
181    public void mousePressed(MouseEvent e) {
182        if (e.getButton() != MouseEvent.BUTTON1)
183            return;
184
185        if (layer.isVisible()) {
186            requestFocusInMapView();
187            MapView mapView = MainApplication.getMap().mapView;
188            prevEastNorth = mapView.getEastNorth(e.getX(), e.getY());
189            mapView.setNewCursor(Cursor.MOVE_CURSOR, this);
190        }
191    }
192
193    @Override
194    public void mouseDragged(MouseEvent e) {
195        if (layer == null || prevEastNorth == null) return;
196        EastNorth eastNorth = MainApplication.getMap().mapView.getEastNorth(e.getX(), e.getY());
197        EastNorth d = tempOffset.getDisplacement().add(eastNorth).subtract(prevEastNorth);
198        tempOffset.setDisplacement(d);
199        layer.getDisplaySettings().setOffsetBookmark(tempOffset);
200        if (offsetDialog != null) {
201            offsetDialog.updateOffset();
202        }
203        prevEastNorth = eastNorth;
204    }
205
206    @Override
207    public void mouseReleased(MouseEvent e) {
208        MapView mapView = MainApplication.getMap().mapView;
209        mapView.repaint();
210        mapView.resetCursor(this);
211        prevEastNorth = null;
212    }
213
214    @Override
215    public void actionPerformed(ActionEvent e) {
216        MapFrame map = MainApplication.getMap();
217        if (offsetDialog != null || layer == null || map == null)
218            return;
219        oldMapMode = map.mapMode;
220        super.actionPerformed(e);
221    }
222
223    private class ImageryOffsetDialog extends ExtendedDialog implements FocusListener {
224        private final JosmTextField tOffset = new JosmTextField();
225        private final JosmTextField tBookmarkName = new JosmTextField();
226        private boolean ignoreListener;
227
228        /**
229         * Constructs a new {@code ImageryOffsetDialog}.
230         */
231        ImageryOffsetDialog() {
232            super(Main.parent,
233                    tr("Adjust imagery offset"),
234                    new String[] {tr("OK"), tr("Cancel")},
235                    false, false); // Do not dispose on close, so HIDE_ON_CLOSE remains the default behaviour and setVisible is called
236            setButtonIcons("ok", "cancel");
237            contentInsets = new Insets(10, 15, 5, 15);
238            JPanel pnl = new JPanel(new GridBagLayout());
239            pnl.add(new JMultilineLabel(tr("Use arrow keys or drag the imagery layer with mouse to adjust the imagery offset.\n" +
240                    "You can also enter east and north offset in the {0} coordinates.\n" +
241                    "If you want to save the offset as bookmark, enter the bookmark name below",
242                    Main.getProjection().toString())), GBC.eop());
243            pnl.add(new JLabel(tr("Offset: ")), GBC.std());
244            pnl.add(tOffset, GBC.eol().fill(GBC.HORIZONTAL).insets(0, 0, 0, 5));
245            pnl.add(new JLabel(tr("Bookmark name: ")), GBC.std());
246            pnl.add(tBookmarkName, GBC.eol().fill(GBC.HORIZONTAL));
247            tOffset.setColumns(16);
248            updateOffsetIntl();
249            tOffset.addFocusListener(this);
250            setContent(pnl);
251            setupDialog();
252            setRememberWindowGeometry(getClass().getName() + ".geometry", WindowGeometry.centerInWindow(Main.parent, getSize()));
253        }
254
255        private boolean areFieldsInFocus() {
256            return tOffset.hasFocus();
257        }
258
259        @Override
260        public void focusGained(FocusEvent e) {
261            // Do nothing
262        }
263
264        @Override
265        public void focusLost(FocusEvent e) {
266            if (ignoreListener) return;
267            String ostr = tOffset.getText();
268            int semicolon = ostr.indexOf(';');
269            if (layer != null && semicolon >= 0 && semicolon + 1 < ostr.length()) {
270                try {
271                    String easting = ostr.substring(0, semicolon).trim();
272                    String northing = ostr.substring(semicolon + 1).trim();
273                    double dx = JosmDecimalFormatSymbolsProvider.parseDouble(easting);
274                    double dy = JosmDecimalFormatSymbolsProvider.parseDouble(northing);
275                    tempOffset.setDisplacement(new EastNorth(dx, dy));
276                    layer.getDisplaySettings().setOffsetBookmark(tempOffset);
277                } catch (NumberFormatException nfe) {
278                    // we repaint offset numbers in any case
279                    Logging.trace(nfe);
280                }
281            }
282            updateOffsetIntl();
283            layer.invalidate();
284        }
285
286        private void updateOffset() {
287            ignoreListener = true;
288            updateOffsetIntl();
289            ignoreListener = false;
290        }
291
292        private void updateOffsetIntl() {
293            if (layer != null) {
294                // Support projections with very small numbers (e.g. 4326)
295                int precision = Main.getProjection().getDefaultZoomInPPD() >= 1.0 ? 2 : 7;
296                // US locale to force decimal separator to be '.'
297                try (Formatter us = new Formatter(Locale.US)) {
298                    EastNorth displacement = layer.getDisplaySettings().getDisplacement();
299                    tOffset.setText(us.format(new StringBuilder()
300                        .append("%1.").append(precision).append("f; %1.").append(precision).append('f').toString(),
301                        displacement.east(), displacement.north()).toString());
302                }
303            }
304        }
305
306        private boolean confirmOverwriteBookmark() {
307            ExtendedDialog dialog = new ExtendedDialog(
308                    Main.parent,
309                    tr("Overwrite"),
310                    tr("Overwrite"), tr("Cancel")
311            ) { {
312                contentInsets = new Insets(10, 15, 10, 15);
313            } };
314            dialog.setContent(tr("Offset bookmark already exists. Overwrite?"));
315            dialog.setButtonIcons("ok", "cancel");
316            dialog.setupDialog();
317            dialog.setVisible(true);
318            return dialog.getValue() == 1;
319        }
320
321        @Override
322        protected void buttonAction(int buttonIndex, ActionEvent evt) {
323            if (buttonIndex == 0 && tBookmarkName.getText() != null && !tBookmarkName.getText().isEmpty() &&
324                    OffsetBookmark.getBookmarkByName(layer, tBookmarkName.getText()) != null &&
325                    !confirmOverwriteBookmark()) {
326                return;
327            }
328            super.buttonAction(buttonIndex, evt);
329        }
330
331        @Override
332        public void setVisible(boolean visible) {
333            super.setVisible(visible);
334            if (visible)
335                return;
336            ignoreListener = true;
337            offsetDialog = null;
338            if (layer != null) {
339                if (getValue() != 1) {
340                    layer.getDisplaySettings().setOffsetBookmark(old);
341                } else if (tBookmarkName.getText() != null && !tBookmarkName.getText().isEmpty()) {
342                    OffsetBookmark.bookmarkOffset(tBookmarkName.getText(), layer);
343                }
344            }
345            MainApplication.getMenu().imageryMenu.refreshOffsetMenu();
346            restoreMapModeState();
347        }
348
349        private void restoreMapModeState() {
350            MapFrame map = MainApplication.getMap();
351            if (map == null)
352                return;
353            if (oldMapMode != null) {
354                map.selectMapMode(oldMapMode);
355                oldMapMode = null;
356            } else if (!exitingMode && !map.selectSelectTool(false)) {
357                exitMode();
358                map.mapMode = null;
359            }
360        }
361    }
362
363    @Override
364    public void destroy() {
365        super.destroy();
366        removeListeners();
367        this.layer = null;
368        this.oldMapMode = null;
369    }
370}
Note: See TracBrowser for help on using the repository browser.