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