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

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

fix #15716 - proper exit of imagery offset mapmode

  • Property svn:eol-style set to native
File size: 13.3 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        default: // Do nothing
155        }
156        if (dx != 0 || dy != 0) {
157            double ppd = layer.getPPD();
158            EastNorth d = tempOffset.getDisplacement().add(new EastNorth(dx / ppd, dy / ppd));
159            tempOffset.setDisplacement(d);
160            layer.getDisplaySettings().setOffsetBookmark(tempOffset);
161            if (offsetDialog != null) {
162                offsetDialog.updateOffset();
163            }
164            if (Logging.isDebugEnabled()) {
165                Logging.debug("{0} consuming event {1}", getClass().getName(), kev);
166            }
167            kev.consume();
168        }
169    }
170
171    @Override
172    public void mousePressed(MouseEvent e) {
173        if (e.getButton() != MouseEvent.BUTTON1)
174            return;
175
176        if (layer.isVisible()) {
177            requestFocusInMapView();
178            MapView mapView = MainApplication.getMap().mapView;
179            prevEastNorth = mapView.getEastNorth(e.getX(), e.getY());
180            mapView.setNewCursor(Cursor.MOVE_CURSOR, this);
181        }
182    }
183
184    @Override
185    public void mouseDragged(MouseEvent e) {
186        if (layer == null || prevEastNorth == null) return;
187        EastNorth eastNorth = MainApplication.getMap().mapView.getEastNorth(e.getX(), e.getY());
188        EastNorth d = tempOffset.getDisplacement().add(eastNorth).subtract(prevEastNorth);
189        tempOffset.setDisplacement(d);
190        layer.getDisplaySettings().setOffsetBookmark(tempOffset);
191        if (offsetDialog != null) {
192            offsetDialog.updateOffset();
193        }
194        prevEastNorth = eastNorth;
195    }
196
197    @Override
198    public void mouseReleased(MouseEvent e) {
199        MapView mapView = MainApplication.getMap().mapView;
200        mapView.repaint();
201        mapView.resetCursor(this);
202        prevEastNorth = null;
203    }
204
205    @Override
206    public void actionPerformed(ActionEvent e) {
207        MapFrame map = MainApplication.getMap();
208        if (offsetDialog != null || layer == null || map == null)
209            return;
210        oldMapMode = map.mapMode;
211        super.actionPerformed(e);
212    }
213
214    private class ImageryOffsetDialog extends ExtendedDialog implements FocusListener {
215        private final JosmTextField tOffset = new JosmTextField();
216        private final JosmTextField tBookmarkName = new JosmTextField();
217        private boolean ignoreListener;
218
219        /**
220         * Constructs a new {@code ImageryOffsetDialog}.
221         */
222        ImageryOffsetDialog() {
223            super(Main.parent,
224                    tr("Adjust imagery offset"),
225                    new String[] {tr("OK"), tr("Cancel")},
226                    false, false); // Do not dispose on close, so HIDE_ON_CLOSE remains the default behaviour and setVisible is called
227            setButtonIcons("ok", "cancel");
228            contentInsets = new Insets(10, 15, 5, 15);
229            JPanel pnl = new JPanel(new GridBagLayout());
230            pnl.add(new JMultilineLabel(tr("Use arrow keys or drag the imagery layer with mouse to adjust the imagery offset.\n" +
231                    "You can also enter east and north offset in the {0} coordinates.\n" +
232                    "If you want to save the offset as bookmark, enter the bookmark name below",
233                    Main.getProjection().toString())), GBC.eop());
234            pnl.add(new JLabel(tr("Offset: ")), GBC.std());
235            pnl.add(tOffset, GBC.eol().fill(GBC.HORIZONTAL).insets(0, 0, 0, 5));
236            pnl.add(new JLabel(tr("Bookmark name: ")), GBC.std());
237            pnl.add(tBookmarkName, GBC.eol().fill(GBC.HORIZONTAL));
238            tOffset.setColumns(16);
239            updateOffsetIntl();
240            tOffset.addFocusListener(this);
241            setContent(pnl);
242            setupDialog();
243            setRememberWindowGeometry(getClass().getName() + ".geometry", WindowGeometry.centerInWindow(Main.parent, getSize()));
244        }
245
246        private boolean areFieldsInFocus() {
247            return tOffset.hasFocus();
248        }
249
250        @Override
251        public void focusGained(FocusEvent e) {
252            // Do nothing
253        }
254
255        @Override
256        public void focusLost(FocusEvent e) {
257            if (ignoreListener) return;
258            String ostr = tOffset.getText();
259            int semicolon = ostr.indexOf(';');
260            if (layer != null && semicolon >= 0 && semicolon + 1 < ostr.length()) {
261                try {
262                    String easting = ostr.substring(0, semicolon).trim();
263                    String northing = ostr.substring(semicolon + 1).trim();
264                    double dx = JosmDecimalFormatSymbolsProvider.parseDouble(easting);
265                    double dy = JosmDecimalFormatSymbolsProvider.parseDouble(northing);
266                    tempOffset.setDisplacement(new EastNorth(dx, dy));
267                    layer.getDisplaySettings().setOffsetBookmark(tempOffset);
268                } catch (NumberFormatException nfe) {
269                    // we repaint offset numbers in any case
270                    Logging.trace(nfe);
271                }
272            }
273            updateOffsetIntl();
274            layer.invalidate();
275        }
276
277        private void updateOffset() {
278            ignoreListener = true;
279            updateOffsetIntl();
280            ignoreListener = false;
281        }
282
283        private void updateOffsetIntl() {
284            if (layer != null) {
285                // Support projections with very small numbers (e.g. 4326)
286                int precision = Main.getProjection().getDefaultZoomInPPD() >= 1.0 ? 2 : 7;
287                // US locale to force decimal separator to be '.'
288                try (Formatter us = new Formatter(Locale.US)) {
289                    EastNorth displacement = layer.getDisplaySettings().getDisplacement();
290                    tOffset.setText(us.format(new StringBuilder()
291                        .append("%1.").append(precision).append("f; %1.").append(precision).append('f').toString(),
292                        displacement.east(), displacement.north()).toString());
293                }
294            }
295        }
296
297        private boolean confirmOverwriteBookmark() {
298            ExtendedDialog dialog = new ExtendedDialog(
299                    Main.parent,
300                    tr("Overwrite"),
301                    tr("Overwrite"), tr("Cancel")
302            ) { {
303                contentInsets = new Insets(10, 15, 10, 15);
304            } };
305            dialog.setContent(tr("Offset bookmark already exists. Overwrite?"));
306            dialog.setButtonIcons("ok", "cancel");
307            dialog.setupDialog();
308            dialog.setVisible(true);
309            return dialog.getValue() == 1;
310        }
311
312        @Override
313        protected void buttonAction(int buttonIndex, ActionEvent evt) {
314            if (buttonIndex == 0 && tBookmarkName.getText() != null && !tBookmarkName.getText().isEmpty() &&
315                    OffsetBookmark.getBookmarkByName(layer, tBookmarkName.getText()) != null &&
316                    !confirmOverwriteBookmark()) {
317                return;
318            }
319            super.buttonAction(buttonIndex, evt);
320        }
321
322        @Override
323        public void setVisible(boolean visible) {
324            super.setVisible(visible);
325            if (visible)
326                return;
327            offsetDialog = null;
328            if (layer != null) {
329                if (getValue() != 1) {
330                    layer.getDisplaySettings().setOffsetBookmark(old);
331                } else if (tBookmarkName.getText() != null && !tBookmarkName.getText().isEmpty()) {
332                    OffsetBookmark.bookmarkOffset(tBookmarkName.getText(), layer);
333                }
334            }
335            MainApplication.getMenu().imageryMenu.refreshOffsetMenu();
336            restoreMapModeState();
337        }
338
339        private void restoreMapModeState() {
340            MapFrame map = MainApplication.getMap();
341            if (map == null)
342                return;
343            if (oldMapMode != null) {
344                map.selectMapMode(oldMapMode);
345                oldMapMode = null;
346            } else if (!map.selectSelectTool(false)) {
347                exitMode();
348                map.mapMode = null;
349            }
350        }
351    }
352
353    @Override
354    public void destroy() {
355        super.destroy();
356        removeListeners();
357        this.layer = null;
358        this.oldMapMode = null;
359    }
360}
Note: See TracBrowser for help on using the repository browser.