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