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