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

Last change on this file since 13686 was 13686, checked in by Don-vip, 6 years ago

fix #16133, see #16053 - fix imagery layer offset bug (regression of r13546)

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