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

Last change on this file since 13243 was 13243, checked in by michael2402, 6 years ago

See #15310: Replace use of dx/dy in bookmark loading/storing code.

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