001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.actions; 003 004import static org.openstreetmap.josm.gui.help.HelpUtil.ht; 005import static org.openstreetmap.josm.tools.I18n.tr; 006 007import java.awt.BorderLayout; 008import java.awt.GridBagLayout; 009import java.awt.event.ActionEvent; 010import java.awt.event.KeyEvent; 011import java.util.Optional; 012 013import javax.swing.JLabel; 014import javax.swing.JOptionPane; 015import javax.swing.JPanel; 016import javax.swing.event.DocumentEvent; 017import javax.swing.event.DocumentListener; 018 019import org.openstreetmap.josm.data.Bounds; 020import org.openstreetmap.josm.data.coor.LatLon; 021import org.openstreetmap.josm.data.coor.conversion.LatLonParser; 022import org.openstreetmap.josm.gui.ExtendedDialog; 023import org.openstreetmap.josm.gui.MainApplication; 024import org.openstreetmap.josm.gui.MapView; 025import org.openstreetmap.josm.gui.datatransfer.ClipboardUtils; 026import org.openstreetmap.josm.gui.widgets.JosmTextField; 027import org.openstreetmap.josm.gui.widgets.SelectAllOnFocusGainedDecorator; 028import org.openstreetmap.josm.spi.preferences.Config; 029import org.openstreetmap.josm.tools.GBC; 030import org.openstreetmap.josm.tools.ImageProvider; 031import org.openstreetmap.josm.tools.Logging; 032import org.openstreetmap.josm.tools.OsmUrlToBounds; 033import org.openstreetmap.josm.tools.Shortcut; 034 035/** 036 * Allows to jump to a specific location. 037 * @since 2575 038 */ 039public class JumpToAction extends JosmAction { 040 041 private final JosmTextField url = new JosmTextField(); 042 private final JosmTextField lat = new JosmTextField(); 043 private final JosmTextField lon = new JosmTextField(); 044 private final JosmTextField zm = new JosmTextField(); 045 046 /** 047 * Constructs a new {@code JumpToAction}. 048 */ 049 public JumpToAction() { 050 super(tr("Jump to Position"), (ImageProvider) null, tr("Opens a dialog that allows to jump to a specific location"), 051 Shortcut.registerShortcut("tools:jumpto", tr("Tool: {0}", tr("Jump to Position")), 052 KeyEvent.VK_J, Shortcut.CTRL), true, "action/jumpto", true); 053 setHelpId(ht("/Action/JumpToPosition")); 054 } 055 056 static class JumpToPositionDialog extends ExtendedDialog { 057 JumpToPositionDialog(String[] buttons, JPanel panel) { 058 super(MainApplication.getMainFrame(), tr("Jump to Position"), buttons); 059 setButtonIcons("ok", "cancel"); 060 configureContextsensitiveHelp(ht("/Action/JumpToPosition"), true); 061 setContent(panel); 062 setCancelButton(2); 063 } 064 } 065 066 class OsmURLListener implements DocumentListener { 067 @Override 068 public void changedUpdate(DocumentEvent e) { 069 parseURL(); 070 } 071 072 @Override 073 public void insertUpdate(DocumentEvent e) { 074 parseURL(); 075 } 076 077 @Override 078 public void removeUpdate(DocumentEvent e) { 079 parseURL(); 080 } 081 } 082 083 class OsmLonLatListener implements DocumentListener { 084 @Override 085 public void changedUpdate(DocumentEvent e) { 086 updateUrl(false); 087 } 088 089 @Override 090 public void insertUpdate(DocumentEvent e) { 091 updateUrl(false); 092 } 093 094 @Override 095 public void removeUpdate(DocumentEvent e) { 096 updateUrl(false); 097 } 098 } 099 100 /** 101 * Displays the "Jump to" dialog. 102 */ 103 public void showJumpToDialog() { 104 if (!MainApplication.isDisplayingMapView()) { 105 return; 106 } 107 MapView mv = MainApplication.getMap().mapView; 108 109 final Optional<Bounds> boundsFromClipboard = Optional 110 .ofNullable(ClipboardUtils.getClipboardStringContent()) 111 .map(OsmUrlToBounds::parse); 112 if (boundsFromClipboard.isPresent() && Config.getPref().getBoolean("jumpto.use.clipboard", true)) { 113 setBounds(boundsFromClipboard.get()); 114 } else { 115 setBounds(mv.getState().getViewArea().getCornerBounds()); 116 } 117 updateUrl(true); 118 119 JPanel panel = new JPanel(new BorderLayout()); 120 panel.add(new JLabel("<html>" 121 + tr("Enter Lat/Lon to jump to position.") 122 + "<br>" 123 + tr("You can also paste an URL from www.openstreetmap.org") 124 + "<br>" 125 + "</html>"), 126 BorderLayout.NORTH); 127 128 OsmLonLatListener x = new OsmLonLatListener(); 129 lat.getDocument().addDocumentListener(x); 130 lon.getDocument().addDocumentListener(x); 131 zm.getDocument().addDocumentListener(x); 132 url.getDocument().addDocumentListener(new OsmURLListener()); 133 134 SelectAllOnFocusGainedDecorator.decorate(lat); 135 SelectAllOnFocusGainedDecorator.decorate(lon); 136 SelectAllOnFocusGainedDecorator.decorate(zm); 137 SelectAllOnFocusGainedDecorator.decorate(url); 138 139 JPanel p = new JPanel(new GridBagLayout()); 140 panel.add(p, BorderLayout.NORTH); 141 142 p.add(new JLabel(tr("Latitude")), GBC.eol()); 143 p.add(lat, GBC.eol().fill(GBC.HORIZONTAL)); 144 145 p.add(new JLabel(tr("Longitude")), GBC.eol()); 146 p.add(lon, GBC.eol().fill(GBC.HORIZONTAL)); 147 148 p.add(new JLabel(tr("Zoom (in metres)")), GBC.eol()); 149 p.add(zm, GBC.eol().fill(GBC.HORIZONTAL)); 150 151 p.add(new JLabel(tr("URL")), GBC.eol()); 152 p.add(url, GBC.eol().fill(GBC.HORIZONTAL)); 153 154 String[] buttons = {tr("Jump there"), tr("Cancel")}; 155 LatLon ll = null; 156 double zoomLvl = 100; 157 while (ll == null) { 158 final int option = new JumpToPositionDialog(buttons, panel).showDialog().getValue(); 159 160 if (option != 1) return; 161 try { 162 zoomLvl = Double.parseDouble(zm.getText()); 163 ll = new LatLon(Double.parseDouble(lat.getText()), Double.parseDouble(lon.getText())); 164 } catch (NumberFormatException ex) { 165 try { 166 ll = LatLonParser.parse(lat.getText() + "; " + lon.getText()); 167 } catch (IllegalArgumentException ex2) { 168 JOptionPane.showMessageDialog(MainApplication.getMainFrame(), 169 tr("Could not parse Latitude, Longitude or Zoom. Please check."), 170 tr("Unable to parse Lon/Lat"), JOptionPane.ERROR_MESSAGE); 171 } 172 } 173 } 174 175 double zoomFactor = 1/ mv.getDist100Pixel(); 176 mv.zoomToFactor(mv.getProjection().latlon2eastNorth(ll), zoomFactor * zoomLvl); 177 } 178 179 private void parseURL() { 180 if (!url.hasFocus()) return; 181 String urlText = url.getText(); 182 Bounds b = OsmUrlToBounds.parse(urlText); 183 setBounds(b); 184 } 185 186 private void setBounds(Bounds b) { 187 if (b != null) { 188 final LatLon center = b.getCenter(); 189 lat.setText(Double.toString(center.lat())); 190 lon.setText(Double.toString(center.lon())); 191 zm.setText(Double.toString(OsmUrlToBounds.getZoom(b))); 192 } 193 } 194 195 private void updateUrl(boolean force) { 196 if (!lat.hasFocus() && !lon.hasFocus() && !zm.hasFocus() && !force) return; 197 try { 198 double dlat = Double.parseDouble(lat.getText()); 199 double dlon = Double.parseDouble(lon.getText()); 200 double zoomLvl = Double.parseDouble(zm.getText()); 201 url.setText(OsmUrlToBounds.getURL(dlat, dlon, (int) zoomLvl)); 202 } catch (NumberFormatException e) { 203 Logging.debug(e.getMessage()); 204 } 205 } 206 207 @Override 208 public void actionPerformed(ActionEvent e) { 209 showJumpToDialog(); 210 } 211 212 @Override 213 protected void updateEnabledState() { 214 setEnabled(MainApplication.isDisplayingMapView()); 215 } 216 217 @Override 218 protected void installAdapters() { 219 super.installAdapters(); 220 // make this action listen to mapframe change events 221 MainApplication.addMapFrameListener((o, n) -> updateEnabledState()); 222 } 223}