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

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

fix #16053, see #15849 - make ImageryAdjustAction robust to change of map mode via shortcut

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