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

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

fix #16302 - IAE when switching map mode while offset dialog is open

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