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