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