[3719] | 1 | // License: GPL. For details, see LICENSE file.
|
---|
[3715] | 2 | package org.openstreetmap.josm.actions;
|
---|
| 3 |
|
---|
| 4 | import static org.openstreetmap.josm.tools.I18n.tr;
|
---|
| 5 |
|
---|
[3722] | 6 | import java.awt.AWTEvent;
|
---|
[3715] | 7 | import java.awt.Cursor;
|
---|
| 8 | import java.awt.GridBagLayout;
|
---|
| 9 | import java.awt.Insets;
|
---|
[3722] | 10 | import java.awt.Toolkit;
|
---|
| 11 | import java.awt.event.AWTEventListener;
|
---|
[3715] | 12 | import java.awt.event.ActionEvent;
|
---|
[3948] | 13 | import java.awt.event.FocusEvent;
|
---|
| 14 | import java.awt.event.FocusListener;
|
---|
[3722] | 15 | import java.awt.event.KeyEvent;
|
---|
[3715] | 16 | import java.awt.event.MouseEvent;
|
---|
[8152] | 17 | import java.awt.event.WindowAdapter;
|
---|
| 18 | import java.awt.event.WindowEvent;
|
---|
[7033] | 19 | import java.util.Formatter;
|
---|
| 20 | import java.util.Locale;
|
---|
[3715] | 21 |
|
---|
| 22 | import javax.swing.JLabel;
|
---|
| 23 | import javax.swing.JPanel;
|
---|
| 24 |
|
---|
| 25 | import org.openstreetmap.josm.Main;
|
---|
| 26 | import org.openstreetmap.josm.actions.mapmode.MapMode;
|
---|
| 27 | import org.openstreetmap.josm.data.coor.EastNorth;
|
---|
[12093] | 28 | import org.openstreetmap.josm.data.coor.LatLon;
|
---|
[3715] | 29 | import org.openstreetmap.josm.data.imagery.OffsetBookmark;
|
---|
| 30 | import org.openstreetmap.josm.gui.ExtendedDialog;
|
---|
[10571] | 31 | import org.openstreetmap.josm.gui.layer.AbstractTileSourceLayer;
|
---|
| 32 | import org.openstreetmap.josm.gui.layer.imagery.TileSourceDisplaySettings;
|
---|
[6792] | 33 | import org.openstreetmap.josm.gui.widgets.JMultilineLabel;
|
---|
| 34 | import org.openstreetmap.josm.gui.widgets.JosmTextField;
|
---|
[3715] | 35 | import org.openstreetmap.josm.tools.GBC;
|
---|
| 36 | import org.openstreetmap.josm.tools.ImageProvider;
|
---|
| 37 |
|
---|
[7859] | 38 | /**
|
---|
| 39 | * Adjust the position of an imagery layer.
|
---|
| 40 | * @since 3715
|
---|
| 41 | */
|
---|
[10152] | 42 | public 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 | }
|
---|