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

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

fix #15716 - listen to escape key outside of imagery adjust dialog, and restore correct offset when pressing it

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