// License: GPL. For details, see LICENSE file. package org.openstreetmap.josm.actions; import static org.openstreetmap.josm.tools.I18n.tr; import java.awt.AWTEvent; import java.awt.Cursor; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.Toolkit; import java.awt.event.AWTEventListener; import java.awt.event.ActionEvent; import java.awt.event.FocusEvent; import java.awt.event.FocusListener; import java.awt.event.KeyEvent; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.util.Formatter; import java.util.Locale; import javax.swing.JLabel; import javax.swing.JPanel; import org.openstreetmap.josm.Main; import org.openstreetmap.josm.actions.mapmode.MapMode; import org.openstreetmap.josm.data.coor.EastNorth; import org.openstreetmap.josm.data.imagery.OffsetBookmark; import org.openstreetmap.josm.gui.ExtendedDialog; import org.openstreetmap.josm.gui.layer.ImageryLayer; import org.openstreetmap.josm.gui.widgets.JMultilineLabel; import org.openstreetmap.josm.gui.widgets.JosmTextField; import org.openstreetmap.josm.tools.GBC; import org.openstreetmap.josm.tools.ImageProvider; /** * Adjust the position of an imagery layer. * @since 3715 */ public class ImageryAdjustAction extends MapMode implements MouseListener, MouseMotionListener, AWTEventListener{ private static volatile ImageryOffsetDialog offsetDialog; private static Cursor cursor = ImageProvider.getCursor("normal", "move"); private double oldDx, oldDy; private EastNorth prevEastNorth; private transient ImageryLayer layer; private MapMode oldMapMode; /** * Constructs a new {@code ImageryAdjustAction} for the given layer. * @param layer The imagery layer */ public ImageryAdjustAction(ImageryLayer layer) { super(tr("New offset"), "adjustimg", tr("Adjust the position of this imagery layer"), Main.map, cursor); putValue("toolbar", Boolean.FALSE); this.layer = layer; } @Override public void enterMode() { super.enterMode(); if (layer == null) return; if (!layer.isVisible()) { layer.setVisible(true); } oldDx = layer.getDx(); oldDy = layer.getDy(); addListeners(); offsetDialog = new ImageryOffsetDialog(); offsetDialog.setVisible(true); } protected void addListeners() { Main.map.mapView.addMouseListener(this); Main.map.mapView.addMouseMotionListener(this); try { Toolkit.getDefaultToolkit().addAWTEventListener(this, AWTEvent.KEY_EVENT_MASK); } catch (SecurityException ex) { Main.error(ex); } } @Override public void exitMode() { super.exitMode(); if (offsetDialog != null) { if (layer != null) { layer.setOffset(oldDx, oldDy); } offsetDialog.setVisible(false); offsetDialog = null; } removeListeners(); } protected void removeListeners() { try { Toolkit.getDefaultToolkit().removeAWTEventListener(this); } catch (SecurityException ex) { Main.error(ex); } if (Main.isDisplayingMapView()) { Main.map.mapView.removeMouseMotionListener(this); Main.map.mapView.removeMouseListener(this); } } @Override public void eventDispatched(AWTEvent event) { if (!(event instanceof KeyEvent) || (event.getID() != KeyEvent.KEY_PRESSED) || (layer == null) || (offsetDialog != null && offsetDialog.areFieldsInFocus())) { return; } KeyEvent kev = (KeyEvent)event; int dx = 0, dy = 0; switch (kev.getKeyCode()) { case KeyEvent.VK_UP : dy = +1; break; case KeyEvent.VK_DOWN : dy = -1; break; case KeyEvent.VK_LEFT : dx = -1; break; case KeyEvent.VK_RIGHT : dx = +1; break; } if (dx != 0 || dy != 0) { double ppd = layer.getPPD(); layer.displace(dx / ppd, dy / ppd); if (offsetDialog != null) { offsetDialog.updateOffset(); } kev.consume(); Main.map.repaint(); } } @Override public void mousePressed(MouseEvent e) { if (e.getButton() != MouseEvent.BUTTON1) return; if (layer.isVisible()) { requestFocusInMapView(); prevEastNorth=Main.map.mapView.getEastNorth(e.getX(),e.getY()); Main.map.mapView.setNewCursor(Cursor.MOVE_CURSOR, this); } } @Override public void mouseDragged(MouseEvent e) { if (layer == null || prevEastNorth == null) return; EastNorth eastNorth = Main.map.mapView.getEastNorth(e.getX(),e.getY()); double dx = layer.getDx()+eastNorth.east()-prevEastNorth.east(); double dy = layer.getDy()+eastNorth.north()-prevEastNorth.north(); layer.setOffset(dx, dy); if (offsetDialog != null) { offsetDialog.updateOffset(); } Main.map.repaint(); prevEastNorth = eastNorth; } @Override public void mouseReleased(MouseEvent e) { Main.map.mapView.repaint(); Main.map.mapView.resetCursor(this); prevEastNorth = null; } @Override public void actionPerformed(ActionEvent e) { if (offsetDialog != null || layer == null || Main.map == null) return; oldMapMode = Main.map.mapMode; super.actionPerformed(e); } private class ImageryOffsetDialog extends ExtendedDialog implements FocusListener { private final JosmTextField tOffset = new JosmTextField(); private final JosmTextField tBookmarkName = new JosmTextField(); private boolean ignoreListener; /** * Constructs a new {@code ImageryOffsetDialog}. */ public ImageryOffsetDialog() { super(Main.parent, tr("Adjust imagery offset"), new String[] { tr("OK"),tr("Cancel") }, false); setButtonIcons(new String[] { "ok", "cancel" }); contentInsets = new Insets(10, 15, 5, 15); JPanel pnl = new JPanel(new GridBagLayout()); pnl.add(new JMultilineLabel(tr("Use arrow keys or drag the imagery layer with mouse to adjust the imagery offset.\n" + "You can also enter east and north offset in the {0} coordinates.\n" + "If you want to save the offset as bookmark, enter the bookmark name below", Main.getProjection().toString())), GBC.eop()); pnl.add(new JLabel(tr("Offset: ")),GBC.std()); pnl.add(tOffset,GBC.eol().fill(GBC.HORIZONTAL).insets(0,0,0,5)); pnl.add(new JLabel(tr("Bookmark name: ")),GBC.std()); pnl.add(tBookmarkName,GBC.eol().fill(GBC.HORIZONTAL)); tOffset.setColumns(16); updateOffsetIntl(); tOffset.addFocusListener(this); setContent(pnl); setupDialog(); addWindowListener(new WindowEventHandler()); } private boolean areFieldsInFocus() { return tOffset.hasFocus(); } @Override public void focusGained(FocusEvent e) { // Do nothing } @Override public void focusLost(FocusEvent e) { if (ignoreListener) return; String ostr = tOffset.getText(); int semicolon = ostr.indexOf(';'); if( semicolon >= 0 && semicolon + 1 < ostr.length() ) { try { // here we assume that Double.parseDouble() needs '.' as a decimal separator String easting = ostr.substring(0, semicolon).trim().replace(',', '.'); String northing = ostr.substring(semicolon + 1).trim().replace(',', '.'); double dx = Double.parseDouble(easting); double dy = Double.parseDouble(northing); layer.setOffset(dx, dy); } catch (NumberFormatException nfe) { // we repaint offset numbers in any case } } updateOffsetIntl(); if (Main.isDisplayingMapView()) { Main.map.repaint(); } } private final void updateOffset() { ignoreListener = true; updateOffsetIntl(); ignoreListener = false; } private final void updateOffsetIntl() { // Support projections with very small numbers (e.g. 4326) int precision = Main.getProjection().getDefaultZoomInPPD() >= 1.0 ? 2 : 7; // US locale to force decimal separator to be '.' try (Formatter us = new Formatter(Locale.US)) { tOffset.setText(us.format( "%1." + precision + "f; %1." + precision + "f", layer.getDx(), layer.getDy()).toString()); } } private boolean confirmOverwriteBookmark() { ExtendedDialog dialog = new ExtendedDialog( Main.parent, tr("Overwrite"), new String[] {tr("Overwrite"), tr("Cancel")} ) {{ contentInsets = new Insets(10, 15, 10, 15); }}; dialog.setContent(tr("Offset bookmark already exists. Overwrite?")); dialog.setButtonIcons(new String[] {"ok.png", "cancel.png"}); dialog.setupDialog(); dialog.setVisible(true); return dialog.getValue() == 1; } @Override protected void buttonAction(int buttonIndex, ActionEvent evt) { if (buttonIndex == 0 && tBookmarkName.getText() != null && !tBookmarkName.getText().isEmpty() && OffsetBookmark.getBookmarkByName(layer, tBookmarkName.getText()) != null && !confirmOverwriteBookmark()) { return; } super.buttonAction(buttonIndex, evt); } @Override public void setVisible(boolean visible) { super.setVisible(visible); if (visible) return; offsetDialog = null; if (getValue() != 1) { layer.setOffset(oldDx, oldDy); } else if (tBookmarkName.getText() != null && !tBookmarkName.getText().isEmpty()) { OffsetBookmark.bookmarkOffset(tBookmarkName.getText(), layer); } Main.main.menu.imageryMenu.refreshOffsetMenu(); if (Main.map == null) return; if (oldMapMode != null) { Main.map.selectMapMode(oldMapMode); oldMapMode = null; } else { Main.map.selectSelectTool(false); } } class WindowEventHandler extends WindowAdapter { @Override public void windowClosing(WindowEvent e) { setVisible(false); } } } @Override public void destroy() { super.destroy(); removeListeners(); this.layer = null; this.oldMapMode = null; } }