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