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