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

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

fix #15716 - listen to escape key outside of imagery adjust dialog, and restore correct offset when pressing it

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