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

Last change on this file since 12093 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
RevLine 
[3719]1// License: GPL. For details, see LICENSE file.
[3715]2package org.openstreetmap.josm.actions;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
[3722]6import java.awt.AWTEvent;
[3715]7import java.awt.Cursor;
8import java.awt.GridBagLayout;
9import java.awt.Insets;
[3722]10import java.awt.Toolkit;
11import java.awt.event.AWTEventListener;
[3715]12import java.awt.event.ActionEvent;
[3948]13import java.awt.event.FocusEvent;
14import java.awt.event.FocusListener;
[3722]15import java.awt.event.KeyEvent;
[3715]16import java.awt.event.MouseEvent;
[8152]17import java.awt.event.WindowAdapter;
18import java.awt.event.WindowEvent;
[7033]19import java.util.Formatter;
20import java.util.Locale;
[3715]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;
[12093]28import org.openstreetmap.josm.data.coor.LatLon;
[3715]29import org.openstreetmap.josm.data.imagery.OffsetBookmark;
30import org.openstreetmap.josm.gui.ExtendedDialog;
[10571]31import org.openstreetmap.josm.gui.layer.AbstractTileSourceLayer;
32import org.openstreetmap.josm.gui.layer.imagery.TileSourceDisplaySettings;
[6792]33import org.openstreetmap.josm.gui.widgets.JMultilineLabel;
34import org.openstreetmap.josm.gui.widgets.JosmTextField;
[3715]35import org.openstreetmap.josm.tools.GBC;
36import org.openstreetmap.josm.tools.ImageProvider;
37
[7859]38/**
39 * Adjust the position of an imagery layer.
40 * @since 3715
41 */
[10152]42public class ImageryAdjustAction extends MapMode implements AWTEventListener {
[8126]43 private static volatile ImageryOffsetDialog offsetDialog;
[7859]44 private static Cursor cursor = ImageProvider.getCursor("normal", "move");
[3715]45
[12093]46 private OffsetBookmark old;
47 private OffsetBookmark tempOffset;
[7859]48 private EastNorth prevEastNorth;
[10571]49 private transient AbstractTileSourceLayer<?> layer;
[3715]50 private MapMode oldMapMode;
51
[6792]52 /**
53 * Constructs a new {@code ImageryAdjustAction} for the given layer.
54 * @param layer The imagery layer
55 */
[10571]56 public ImageryAdjustAction(AbstractTileSourceLayer<?> layer) {
[11713]57 super(tr("New offset"), "adjustimg", tr("Adjust the position of this imagery layer"), cursor);
[8377]58 putValue("toolbar", Boolean.FALSE);
[3715]59 this.layer = layer;
60 }
61
[6792]62 @Override
63 public void enterMode() {
[3715]64 super.enterMode();
65 if (layer == null)
66 return;
67 if (!layer.isVisible()) {
68 layer.setVisible(true);
69 }
[12093]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);
[6107]84 addListeners();
[11652]85 showOffsetDialog(new ImageryOffsetDialog());
86 }
87
88 private static void showOffsetDialog(ImageryOffsetDialog dlg) {
89 offsetDialog = dlg;
[6107]90 offsetDialog.setVisible(true);
91 }
[6792]92
[11652]93 private static void hideOffsetDialog() {
94 offsetDialog.setVisible(false);
95 offsetDialog = null;
96 }
97
[6107]98 protected void addListeners() {
[3715]99 Main.map.mapView.addMouseListener(this);
100 Main.map.mapView.addMouseMotionListener(this);
[3722]101 try {
102 Toolkit.getDefaultToolkit().addAWTEventListener(this, AWTEvent.KEY_EVENT_MASK);
103 } catch (SecurityException ex) {
[6792]104 Main.error(ex);
[3722]105 }
[3715]106 }
107
[6792]108 @Override
109 public void exitMode() {
[3715]110 super.exitMode();
111 if (offsetDialog != null) {
[7350]112 if (layer != null) {
[12093]113 layer.getDisplaySettings().setOffsetBookmark(old);
[7350]114 }
[11652]115 hideOffsetDialog();
[10930]116 // do not restore old mode here - this is called when the new mode is already known.
[3715]117 }
[6107]118 removeListeners();
119 }
[6792]120
[6107]121 protected void removeListeners() {
[3722]122 try {
123 Toolkit.getDefaultToolkit().removeAWTEventListener(this);
124 } catch (SecurityException ex) {
[6792]125 Main.error(ex);
[3722]126 }
[6107]127 if (Main.isDisplayingMapView()) {
128 Main.map.mapView.removeMouseMotionListener(this);
129 Main.map.mapView.removeMouseListener(this);
130 }
[3715]131 }
132
[3722]133 @Override
134 public void eventDispatched(AWTEvent event) {
[7859]135 if (!(event instanceof KeyEvent)
136 || (event.getID() != KeyEvent.KEY_PRESSED)
137 || (layer == null)
138 || (offsetDialog != null && offsetDialog.areFieldsInFocus())) {
139 return;
140 }
[8510]141 KeyEvent kev = (KeyEvent) event;
[10216]142 int dx = 0;
143 int dy = 0;
[3722]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;
[10216]149 default: // Do nothing
[3722]150 }
151 if (dx != 0 || dy != 0) {
152 double ppd = layer.getPPD();
[12093]153 EastNorth d = tempOffset.getDisplacement().add(new EastNorth(dx / ppd, dy / ppd));
154 tempOffset.setDisplacement(d);
155 layer.getDisplaySettings().setOffsetBookmark(tempOffset);
[3722]156 if (offsetDialog != null) {
157 offsetDialog.updateOffset();
158 }
[8441]159 if (Main.isDebugEnabled()) {
160 Main.debug(getClass().getName()+" consuming event "+kev);
161 }
[3722]162 kev.consume();
163 }
164 }
165
[6792]166 @Override
167 public void mousePressed(MouseEvent e) {
[3715]168 if (e.getButton() != MouseEvent.BUTTON1)
169 return;
170
171 if (layer.isVisible()) {
[5962]172 requestFocusInMapView();
[8510]173 prevEastNorth = Main.map.mapView.getEastNorth(e.getX(), e.getY());
[3919]174 Main.map.mapView.setNewCursor(Cursor.MOVE_CURSOR, this);
[3715]175 }
176 }
177
[6792]178 @Override
179 public void mouseDragged(MouseEvent e) {
[3715]180 if (layer == null || prevEastNorth == null) return;
[10571]181 EastNorth eastNorth = Main.map.mapView.getEastNorth(e.getX(), e.getY());
[12093]182 EastNorth d = tempOffset.getDisplacement().add(eastNorth).subtract(prevEastNorth);
183 tempOffset.setDisplacement(d);
184 layer.getDisplaySettings().setOffsetBookmark(tempOffset);
[3715]185 if (offsetDialog != null) {
186 offsetDialog.updateOffset();
187 }
188 prevEastNorth = eastNorth;
189 }
190
[6792]191 @Override
192 public void mouseReleased(MouseEvent e) {
[3715]193 Main.map.mapView.repaint();
[3919]194 Main.map.mapView.resetCursor(this);
[3715]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
[7859]206 private class ImageryOffsetDialog extends ExtendedDialog implements FocusListener {
207 private final JosmTextField tOffset = new JosmTextField();
208 private final JosmTextField tBookmarkName = new JosmTextField();
[3715]209 private boolean ignoreListener;
[7859]210
211 /**
212 * Constructs a new {@code ImageryOffsetDialog}.
213 */
[8836]214 ImageryOffsetDialog() {
[3715]215 super(Main.parent,
216 tr("Adjust imagery offset"),
[8510]217 new String[] {tr("OK"), tr("Cancel")},
[3715]218 false);
[8443]219 setButtonIcons(new String[] {"ok", "cancel"});
[3722]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" +
[7859]224 "If you want to save the offset as bookmark, enter the bookmark name below",
225 Main.getProjection().toString())), GBC.eop());
[8510]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));
[4001]230 tOffset.setColumns(16);
231 updateOffsetIntl();
232 tOffset.addFocusListener(this);
[3715]233 setContent(pnl);
234 setupDialog();
[8152]235 addWindowListener(new WindowEventHandler());
[3715]236 }
237
[7859]238 private boolean areFieldsInFocus() {
[4001]239 return tOffset.hasFocus();
240 }
241
[3715]242 @Override
[3948]243 public void focusGained(FocusEvent e) {
[7859]244 // Do nothing
[3948]245 }
246
247 @Override
248 public void focusLost(FocusEvent e) {
[3715]249 if (ignoreListener) return;
[4001]250 String ostr = tOffset.getText();
251 int semicolon = ostr.indexOf(';');
[10664]252 if (layer != null && semicolon >= 0 && semicolon + 1 < ostr.length()) {
[4001]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);
[12093]259 tempOffset.setDisplacement(new EastNorth(dx, dy));
260 layer.getDisplaySettings().setOffsetBookmark(tempOffset);
[4001]261 } catch (NumberFormatException nfe) {
262 // we repaint offset numbers in any case
[10626]263 Main.trace(nfe);
[4001]264 }
[3948]265 }
[4001]266 updateOffsetIntl();
[5460]267 if (Main.isDisplayingMapView()) {
[4845]268 Main.map.repaint();
269 }
[3715]270 }
271
[8512]272 private void updateOffset() {
[3715]273 ignoreListener = true;
[4001]274 updateOffsetIntl();
[3715]275 ignoreListener = false;
276 }
277
[8512]278 private void updateOffsetIntl() {
[11186]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 }
[7033]289 }
[4001]290 }
291
[3734]292 private boolean confirmOverwriteBookmark() {
293 ExtendedDialog dialog = new ExtendedDialog(
294 Main.parent,
295 tr("Overwrite"),
296 new String[] {tr("Overwrite"), tr("Cancel")}
[8510]297 ) { {
[3734]298 contentInsets = new Insets(10, 15, 10, 15);
[8510]299 } };
[3734]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
[3715]307 @Override
308 protected void buttonAction(int buttonIndex, ActionEvent evt) {
[6087]309 if (buttonIndex == 0 && tBookmarkName.getText() != null && !tBookmarkName.getText().isEmpty() &&
[7859]310 OffsetBookmark.getBookmarkByName(layer, tBookmarkName.getText()) != null &&
311 !confirmOverwriteBookmark()) {
312 return;
[3734]313 }
[3715]314 super.buttonAction(buttonIndex, evt);
[11063]315 restoreMapModeState();
[3780]316 }
317
318 @Override
319 public void setVisible(boolean visible) {
320 super.setVisible(visible);
[10150]321 if (visible)
322 return;
[3715]323 offsetDialog = null;
[10150]324 if (layer != null) {
325 if (getValue() != 1) {
[12093]326 layer.getDisplaySettings().setOffsetBookmark(old);
[10150]327 } else if (tBookmarkName.getText() != null && !tBookmarkName.getText().isEmpty()) {
328 OffsetBookmark.bookmarkOffset(tBookmarkName.getText(), layer);
329 }
[3715]330 }
[3737]331 Main.main.menu.imageryMenu.refreshOffsetMenu();
[10930]332 }
333
[11088]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
[8152]345 class WindowEventHandler extends WindowAdapter {
346 @Override
347 public void windowClosing(WindowEvent e) {
348 setVisible(false);
[10930]349 restoreMapModeState();
[8152]350 }
351 }
[3715]352 }
[6107]353
354 @Override
355 public void destroy() {
356 super.destroy();
357 removeListeners();
358 this.layer = null;
359 this.oldMapMode = null;
360 }
[3715]361}
Note: See TracBrowser for help on using the repository browser.