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