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

Last change on this file since 12161 was 12093, checked in by bastiK, 7 years ago

fixed #14734 - Handling imagery offsets when reprojecting

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