001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.actions; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005 006import java.awt.AWTEvent; 007import java.awt.Cursor; 008import java.awt.GridBagLayout; 009import java.awt.Insets; 010import java.awt.Toolkit; 011import java.awt.event.AWTEventListener; 012import java.awt.event.ActionEvent; 013import java.awt.event.FocusEvent; 014import java.awt.event.FocusListener; 015import java.awt.event.KeyEvent; 016import java.awt.event.MouseEvent; 017import java.awt.event.MouseListener; 018import java.awt.event.MouseMotionListener; 019import java.util.Formatter; 020import java.util.Locale; 021 022import javax.swing.JLabel; 023import javax.swing.JPanel; 024 025import org.openstreetmap.josm.Main; 026import org.openstreetmap.josm.actions.mapmode.MapMode; 027import org.openstreetmap.josm.data.coor.EastNorth; 028import org.openstreetmap.josm.data.imagery.OffsetBookmark; 029import org.openstreetmap.josm.gui.ExtendedDialog; 030import org.openstreetmap.josm.gui.layer.ImageryLayer; 031import org.openstreetmap.josm.gui.widgets.JMultilineLabel; 032import org.openstreetmap.josm.gui.widgets.JosmTextField; 033import org.openstreetmap.josm.tools.GBC; 034import org.openstreetmap.josm.tools.ImageProvider; 035 036/** 037 * Adjust the position of an imagery layer. 038 * @since 3715 039 */ 040public class ImageryAdjustAction extends MapMode implements MouseListener, MouseMotionListener, AWTEventListener{ 041 private static ImageryOffsetDialog offsetDialog; 042 private static Cursor cursor = ImageProvider.getCursor("normal", "move"); 043 044 private double oldDx, oldDy; 045 private EastNorth prevEastNorth; 046 private ImageryLayer layer; 047 private MapMode oldMapMode; 048 049 /** 050 * Constructs a new {@code ImageryAdjustAction} for the given layer. 051 * @param layer The imagery layer 052 */ 053 public ImageryAdjustAction(ImageryLayer layer) { 054 super(tr("New offset"), "adjustimg", 055 tr("Adjust the position of this imagery layer"), Main.map, 056 cursor); 057 putValue("toolbar", false); 058 this.layer = layer; 059 } 060 061 @Override 062 public void enterMode() { 063 super.enterMode(); 064 if (layer == null) 065 return; 066 if (!layer.isVisible()) { 067 layer.setVisible(true); 068 } 069 oldDx = layer.getDx(); 070 oldDy = layer.getDy(); 071 addListeners(); 072 offsetDialog = new ImageryOffsetDialog(); 073 offsetDialog.setVisible(true); 074 } 075 076 protected void addListeners() { 077 Main.map.mapView.addMouseListener(this); 078 Main.map.mapView.addMouseMotionListener(this); 079 try { 080 Toolkit.getDefaultToolkit().addAWTEventListener(this, AWTEvent.KEY_EVENT_MASK); 081 } catch (SecurityException ex) { 082 Main.error(ex); 083 } 084 } 085 086 @Override 087 public void exitMode() { 088 super.exitMode(); 089 if (offsetDialog != null) { 090 if (layer != null) { 091 layer.setOffset(oldDx, oldDy); 092 } 093 offsetDialog.setVisible(false); 094 offsetDialog = null; 095 } 096 removeListeners(); 097 } 098 099 protected void removeListeners() { 100 try { 101 Toolkit.getDefaultToolkit().removeAWTEventListener(this); 102 } catch (SecurityException ex) { 103 Main.error(ex); 104 } 105 if (Main.isDisplayingMapView()) { 106 Main.map.mapView.removeMouseMotionListener(this); 107 Main.map.mapView.removeMouseListener(this); 108 } 109 } 110 111 @Override 112 public void eventDispatched(AWTEvent event) { 113 if (!(event instanceof KeyEvent) 114 || (event.getID() != KeyEvent.KEY_PRESSED) 115 || (layer == null) 116 || (offsetDialog != null && offsetDialog.areFieldsInFocus())) { 117 return; 118 } 119 KeyEvent kev = (KeyEvent)event; 120 int dx = 0, dy = 0; 121 switch (kev.getKeyCode()) { 122 case KeyEvent.VK_UP : dy = +1; break; 123 case KeyEvent.VK_DOWN : dy = -1; break; 124 case KeyEvent.VK_LEFT : dx = -1; break; 125 case KeyEvent.VK_RIGHT : dx = +1; break; 126 } 127 if (dx != 0 || dy != 0) { 128 double ppd = layer.getPPD(); 129 layer.displace(dx / ppd, dy / ppd); 130 if (offsetDialog != null) { 131 offsetDialog.updateOffset(); 132 } 133 kev.consume(); 134 Main.map.repaint(); 135 } 136 } 137 138 @Override 139 public void mousePressed(MouseEvent e) { 140 if (e.getButton() != MouseEvent.BUTTON1) 141 return; 142 143 if (layer.isVisible()) { 144 requestFocusInMapView(); 145 prevEastNorth=Main.map.mapView.getEastNorth(e.getX(),e.getY()); 146 Main.map.mapView.setNewCursor(Cursor.MOVE_CURSOR, this); 147 } 148 } 149 150 @Override 151 public void mouseDragged(MouseEvent e) { 152 if (layer == null || prevEastNorth == null) return; 153 EastNorth eastNorth = 154 Main.map.mapView.getEastNorth(e.getX(),e.getY()); 155 double dx = layer.getDx()+eastNorth.east()-prevEastNorth.east(); 156 double dy = layer.getDy()+eastNorth.north()-prevEastNorth.north(); 157 layer.setOffset(dx, dy); 158 if (offsetDialog != null) { 159 offsetDialog.updateOffset(); 160 } 161 Main.map.repaint(); 162 prevEastNorth = eastNorth; 163 } 164 165 @Override 166 public void mouseReleased(MouseEvent e) { 167 Main.map.mapView.repaint(); 168 Main.map.mapView.resetCursor(this); 169 prevEastNorth = null; 170 } 171 172 @Override 173 public void actionPerformed(ActionEvent e) { 174 if (offsetDialog != null || layer == null || Main.map == null) 175 return; 176 oldMapMode = Main.map.mapMode; 177 super.actionPerformed(e); 178 } 179 180 private class ImageryOffsetDialog extends ExtendedDialog implements FocusListener { 181 private final JosmTextField tOffset = new JosmTextField(); 182 private final JosmTextField tBookmarkName = new JosmTextField(); 183 private boolean ignoreListener; 184 185 /** 186 * Constructs a new {@code ImageryOffsetDialog}. 187 */ 188 public ImageryOffsetDialog() { 189 super(Main.parent, 190 tr("Adjust imagery offset"), 191 new String[] { tr("OK"),tr("Cancel") }, 192 false); 193 setButtonIcons(new String[] { "ok", "cancel" }); 194 contentInsets = new Insets(10, 15, 5, 15); 195 JPanel pnl = new JPanel(new GridBagLayout()); 196 pnl.add(new JMultilineLabel(tr("Use arrow keys or drag the imagery layer with mouse to adjust the imagery offset.\n" + 197 "You can also enter east and north offset in the {0} coordinates.\n" + 198 "If you want to save the offset as bookmark, enter the bookmark name below", 199 Main.getProjection().toString())), GBC.eop()); 200 pnl.add(new JLabel(tr("Offset: ")),GBC.std()); 201 pnl.add(tOffset,GBC.eol().fill(GBC.HORIZONTAL).insets(0,0,0,5)); 202 pnl.add(new JLabel(tr("Bookmark name: ")),GBC.std()); 203 pnl.add(tBookmarkName,GBC.eol().fill(GBC.HORIZONTAL)); 204 tOffset.setColumns(16); 205 updateOffsetIntl(); 206 tOffset.addFocusListener(this); 207 setContent(pnl); 208 setupDialog(); 209 } 210 211 private boolean areFieldsInFocus() { 212 return tOffset.hasFocus(); 213 } 214 215 @Override 216 public void focusGained(FocusEvent e) { 217 // Do nothing 218 } 219 220 @Override 221 public void focusLost(FocusEvent e) { 222 if (ignoreListener) return; 223 String ostr = tOffset.getText(); 224 int semicolon = ostr.indexOf(';'); 225 if( semicolon >= 0 && semicolon + 1 < ostr.length() ) { 226 try { 227 // here we assume that Double.parseDouble() needs '.' as a decimal separator 228 String easting = ostr.substring(0, semicolon).trim().replace(',', '.'); 229 String northing = ostr.substring(semicolon + 1).trim().replace(',', '.'); 230 double dx = Double.parseDouble(easting); 231 double dy = Double.parseDouble(northing); 232 layer.setOffset(dx, dy); 233 } catch (NumberFormatException nfe) { 234 // we repaint offset numbers in any case 235 } 236 } 237 updateOffsetIntl(); 238 if (Main.isDisplayingMapView()) { 239 Main.map.repaint(); 240 } 241 } 242 243 private final void updateOffset() { 244 ignoreListener = true; 245 updateOffsetIntl(); 246 ignoreListener = false; 247 } 248 249 private final void updateOffsetIntl() { 250 // Support projections with very small numbers (e.g. 4326) 251 int precision = Main.getProjection().getDefaultZoomInPPD() >= 1.0 ? 2 : 7; 252 // US locale to force decimal separator to be '.' 253 try (Formatter us = new Formatter(Locale.US)) { 254 tOffset.setText(us.format( 255 "%1." + precision + "f; %1." + precision + "f", 256 layer.getDx(), layer.getDy()).toString()); 257 } 258 } 259 260 private boolean confirmOverwriteBookmark() { 261 ExtendedDialog dialog = new ExtendedDialog( 262 Main.parent, 263 tr("Overwrite"), 264 new String[] {tr("Overwrite"), tr("Cancel")} 265 ) {{ 266 contentInsets = new Insets(10, 15, 10, 15); 267 }}; 268 dialog.setContent(tr("Offset bookmark already exists. Overwrite?")); 269 dialog.setButtonIcons(new String[] {"ok.png", "cancel.png"}); 270 dialog.setupDialog(); 271 dialog.setVisible(true); 272 return dialog.getValue() == 1; 273 } 274 275 @Override 276 protected void buttonAction(int buttonIndex, ActionEvent evt) { 277 if (buttonIndex == 0 && tBookmarkName.getText() != null && !tBookmarkName.getText().isEmpty() && 278 OffsetBookmark.getBookmarkByName(layer, tBookmarkName.getText()) != null && 279 !confirmOverwriteBookmark()) { 280 return; 281 } 282 super.buttonAction(buttonIndex, evt); 283 } 284 285 @Override 286 public void setVisible(boolean visible) { 287 super.setVisible(visible); 288 if (visible) return; 289 offsetDialog = null; 290 if (getValue() != 1) { 291 layer.setOffset(oldDx, oldDy); 292 } else if (tBookmarkName.getText() != null && !tBookmarkName.getText().isEmpty()) { 293 OffsetBookmark.bookmarkOffset(tBookmarkName.getText(), layer); 294 } 295 Main.main.menu.imageryMenu.refreshOffsetMenu(); 296 if (Main.map == null) return; 297 if (oldMapMode != null) { 298 Main.map.selectMapMode(oldMapMode); 299 oldMapMode = null; 300 } else { 301 Main.map.selectSelectTool(false); 302 } 303 } 304 } 305 306 @Override 307 public void destroy() { 308 super.destroy(); 309 removeListeners(); 310 this.layer = null; 311 this.oldMapMode = null; 312 } 313}