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