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

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

fix #14602 - allow both dot and comma decimal separator everywhere possible for user-entered values

  • Property svn:eol-style set to native
File size: 13.7 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.layer.imagery.TileSourceDisplaySettings;
36import org.openstreetmap.josm.gui.util.WindowGeometry;
37import org.openstreetmap.josm.gui.widgets.JMultilineLabel;
38import org.openstreetmap.josm.gui.widgets.JosmTextField;
39import org.openstreetmap.josm.tools.GBC;
40import org.openstreetmap.josm.tools.ImageProvider;
41import org.openstreetmap.josm.tools.JosmDecimalFormatSymbolsProvider;
42import org.openstreetmap.josm.tools.Logging;
43
44/**
45 * Adjust the position of an imagery layer.
46 * @since 3715
47 */
48public class ImageryAdjustAction extends MapMode implements AWTEventListener {
49 private static volatile ImageryOffsetDialog offsetDialog;
50 private static Cursor cursor = ImageProvider.getCursor("normal", "move");
51
52 private OffsetBookmark old;
53 private OffsetBookmark tempOffset;
54 private EastNorth prevEastNorth;
55 private transient AbstractTileSourceLayer<?> layer;
56 private MapMode oldMapMode;
57
58 /**
59 * Constructs a new {@code ImageryAdjustAction} for the given layer.
60 * @param layer The imagery layer
61 */
62 public ImageryAdjustAction(AbstractTileSourceLayer<?> layer) {
63 super(tr("New offset"), "adjustimg", tr("Adjust the position of this imagery layer"), cursor);
64 putValue("toolbar", Boolean.FALSE);
65 this.layer = layer;
66 }
67
68 @Override
69 public void enterMode() {
70 super.enterMode();
71 if (layer == null)
72 return;
73 if (!layer.isVisible()) {
74 layer.setVisible(true);
75 }
76 old = layer.getDisplaySettings().getOffsetBookmark();
77 EastNorth curOff = old == null ? EastNorth.ZERO : old.getDisplacement(Main.getProjection());
78 LatLon center;
79 if (MainApplication.isDisplayingMapView()) {
80 center = Main.getProjection().eastNorth2latlon(MainApplication.getMap().mapView.getCenter());
81 } else {
82 center = LatLon.ZERO;
83 }
84 tempOffset = new OffsetBookmark(
85 Main.getProjection().toCode(),
86 layer.getInfo().getName(),
87 null,
88 curOff.east(), curOff.north(), center.lon(), center.lat());
89 layer.getDisplaySettings().setOffsetBookmark(tempOffset);
90 addListeners();
91 showOffsetDialog(new ImageryOffsetDialog());
92 }
93
94 private static void showOffsetDialog(ImageryOffsetDialog dlg) {
95 offsetDialog = dlg;
96 offsetDialog.setVisible(true);
97 }
98
99 private static void hideOffsetDialog() {
100 offsetDialog.setVisible(false);
101 offsetDialog = null;
102 }
103
104 protected void addListeners() {
105 MapView mapView = MainApplication.getMap().mapView;
106 mapView.addMouseListener(this);
107 mapView.addMouseMotionListener(this);
108 try {
109 Toolkit.getDefaultToolkit().addAWTEventListener(this, AWTEvent.KEY_EVENT_MASK);
110 } catch (SecurityException ex) {
111 Logging.error(ex);
112 }
113 }
114
115 @Override
116 public void exitMode() {
117 super.exitMode();
118 if (offsetDialog != null) {
119 if (layer != null) {
120 layer.getDisplaySettings().setOffsetBookmark(old);
121 }
122 hideOffsetDialog();
123 // do not restore old mode here - this is called when the new mode is already known.
124 }
125 removeListeners();
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 default: // Do nothing
158 }
159 if (dx != 0 || dy != 0) {
160 double ppd = layer.getPPD();
161 EastNorth d = tempOffset.getDisplacement().add(new EastNorth(dx / ppd, dy / ppd));
162 tempOffset.setDisplacement(d);
163 layer.getDisplaySettings().setOffsetBookmark(tempOffset);
164 if (offsetDialog != null) {
165 offsetDialog.updateOffset();
166 }
167 if (Logging.isDebugEnabled()) {
168 Logging.debug("{0} consuming event {1}", getClass().getName(), kev);
169 }
170 kev.consume();
171 }
172 }
173
174 @Override
175 public void mousePressed(MouseEvent e) {
176 if (e.getButton() != MouseEvent.BUTTON1)
177 return;
178
179 if (layer.isVisible()) {
180 requestFocusInMapView();
181 MapView mapView = MainApplication.getMap().mapView;
182 prevEastNorth = mapView.getEastNorth(e.getX(), e.getY());
183 mapView.setNewCursor(Cursor.MOVE_CURSOR, this);
184 }
185 }
186
187 @Override
188 public void mouseDragged(MouseEvent e) {
189 if (layer == null || prevEastNorth == null) return;
190 EastNorth eastNorth = MainApplication.getMap().mapView.getEastNorth(e.getX(), e.getY());
191 EastNorth d = tempOffset.getDisplacement().add(eastNorth).subtract(prevEastNorth);
192 tempOffset.setDisplacement(d);
193 layer.getDisplaySettings().setOffsetBookmark(tempOffset);
194 if (offsetDialog != null) {
195 offsetDialog.updateOffset();
196 }
197 prevEastNorth = eastNorth;
198 }
199
200 @Override
201 public void mouseReleased(MouseEvent e) {
202 MapView mapView = MainApplication.getMap().mapView;
203 mapView.repaint();
204 mapView.resetCursor(this);
205 prevEastNorth = null;
206 }
207
208 @Override
209 public void actionPerformed(ActionEvent e) {
210 MapFrame map = MainApplication.getMap();
211 if (offsetDialog != null || layer == null || map == null)
212 return;
213 oldMapMode = map.mapMode;
214 super.actionPerformed(e);
215 }
216
217 private class ImageryOffsetDialog extends ExtendedDialog implements FocusListener {
218 private final JosmTextField tOffset = new JosmTextField();
219 private final JosmTextField tBookmarkName = new JosmTextField();
220 private boolean ignoreListener;
221
222 /**
223 * Constructs a new {@code ImageryOffsetDialog}.
224 */
225 ImageryOffsetDialog() {
226 super(Main.parent,
227 tr("Adjust imagery offset"),
228 new String[] {tr("OK"), tr("Cancel")},
229 false);
230 setButtonIcons("ok", "cancel");
231 contentInsets = new Insets(10, 15, 5, 15);
232 JPanel pnl = new JPanel(new GridBagLayout());
233 pnl.add(new JMultilineLabel(tr("Use arrow keys or drag the imagery layer with mouse to adjust the imagery offset.\n" +
234 "You can also enter east and north offset in the {0} coordinates.\n" +
235 "If you want to save the offset as bookmark, enter the bookmark name below",
236 Main.getProjection().toString())), GBC.eop());
237 pnl.add(new JLabel(tr("Offset: ")), GBC.std());
238 pnl.add(tOffset, GBC.eol().fill(GBC.HORIZONTAL).insets(0, 0, 0, 5));
239 pnl.add(new JLabel(tr("Bookmark name: ")), GBC.std());
240 pnl.add(tBookmarkName, GBC.eol().fill(GBC.HORIZONTAL));
241 tOffset.setColumns(16);
242 updateOffsetIntl();
243 tOffset.addFocusListener(this);
244 setContent(pnl);
245 setupDialog();
246 addWindowListener(new WindowEventHandler());
247 setRememberWindowGeometry(getClass().getName() + ".geometry", WindowGeometry.centerInWindow(Main.parent, getSize()));
248 }
249
250 private boolean areFieldsInFocus() {
251 return tOffset.hasFocus();
252 }
253
254 @Override
255 public void focusGained(FocusEvent e) {
256 // Do nothing
257 }
258
259 @Override
260 public void focusLost(FocusEvent e) {
261 if (ignoreListener) return;
262 String ostr = tOffset.getText();
263 int semicolon = ostr.indexOf(';');
264 if (layer != null && semicolon >= 0 && semicolon + 1 < ostr.length()) {
265 try {
266 String easting = ostr.substring(0, semicolon).trim();
267 String northing = ostr.substring(semicolon + 1).trim();
268 double dx = JosmDecimalFormatSymbolsProvider.parseDouble(easting);
269 double dy = JosmDecimalFormatSymbolsProvider.parseDouble(northing);
270 tempOffset.setDisplacement(new EastNorth(dx, dy));
271 layer.getDisplaySettings().setOffsetBookmark(tempOffset);
272 } catch (NumberFormatException nfe) {
273 // we repaint offset numbers in any case
274 Logging.trace(nfe);
275 }
276 }
277 updateOffsetIntl();
278 if (MainApplication.isDisplayingMapView()) {
279 MainApplication.getMap().repaint();
280 }
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 TileSourceDisplaySettings ds = layer.getDisplaySettings();
296 tOffset.setText(us.format(new StringBuilder()
297 .append("%1.").append(precision).append("f; %1.").append(precision).append('f').toString(),
298 ds.getDx(), ds.getDy()).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 restoreMapModeState();
327 }
328
329 @Override
330 public void setVisible(boolean visible) {
331 super.setVisible(visible);
332 if (visible)
333 return;
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 }
344
345 private void restoreMapModeState() {
346 MapFrame map = MainApplication.getMap();
347 if (map == null)
348 return;
349 if (oldMapMode != null) {
350 map.selectMapMode(oldMapMode);
351 oldMapMode = null;
352 } else {
353 map.selectSelectTool(false);
354 }
355 }
356
357 class WindowEventHandler extends WindowAdapter {
358 @Override
359 public void windowClosing(WindowEvent e) {
360 setVisible(false);
361 restoreMapModeState();
362 }
363 }
364 }
365
366 @Override
367 public void destroy() {
368 super.destroy();
369 removeListeners();
370 this.layer = null;
371 this.oldMapMode = null;
372 }
373}
Note: See TracBrowser for help on using the repository browser.