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

Last change on this file since 8248 was 8152, checked in by stoecker, 9 years ago

fix #11230 - improper handling of imagery adjustemnet closing - patch by Markus Heidelberg

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