001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.download; 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.Color; 009import java.awt.Component; 010import java.awt.Dimension; 011import java.awt.FlowLayout; 012import java.awt.Font; 013import java.awt.Graphics; 014import java.awt.GridBagLayout; 015import java.awt.event.ActionEvent; 016import java.awt.event.ActionListener; 017import java.awt.event.InputEvent; 018import java.awt.event.KeyEvent; 019import java.awt.event.WindowAdapter; 020import java.awt.event.WindowEvent; 021import java.util.ArrayList; 022import java.util.List; 023 024import javax.swing.AbstractAction; 025import javax.swing.JCheckBox; 026import javax.swing.JComponent; 027import javax.swing.JDialog; 028import javax.swing.JLabel; 029import javax.swing.JOptionPane; 030import javax.swing.JPanel; 031import javax.swing.JTabbedPane; 032import javax.swing.KeyStroke; 033 034import org.openstreetmap.josm.Main; 035import org.openstreetmap.josm.actions.ExpertToggleAction; 036import org.openstreetmap.josm.data.Bounds; 037import org.openstreetmap.josm.gui.MapView; 038import org.openstreetmap.josm.gui.SideButton; 039import org.openstreetmap.josm.gui.help.ContextSensitiveHelpAction; 040import org.openstreetmap.josm.gui.help.HelpUtil; 041import org.openstreetmap.josm.io.OnlineResource; 042import org.openstreetmap.josm.plugins.PluginHandler; 043import org.openstreetmap.josm.tools.GBC; 044import org.openstreetmap.josm.tools.ImageProvider; 045import org.openstreetmap.josm.tools.InputMapUtils; 046import org.openstreetmap.josm.tools.OsmUrlToBounds; 047import org.openstreetmap.josm.tools.Utils; 048import org.openstreetmap.josm.tools.WindowGeometry; 049 050/** 051 * Dialog displayed to download OSM and/or GPS data from OSM server. 052 */ 053public class DownloadDialog extends JDialog { 054 /** the unique instance of the download dialog */ 055 private static DownloadDialog instance; 056 057 /** 058 * Replies the unique instance of the download dialog 059 * 060 * @return the unique instance of the download dialog 061 */ 062 public static DownloadDialog getInstance() { 063 if (instance == null) { 064 instance = new DownloadDialog(Main.parent); 065 } 066 return instance; 067 } 068 069 protected SlippyMapChooser slippyMapChooser; 070 protected final List<DownloadSelection> downloadSelections = new ArrayList<>(); 071 protected final JTabbedPane tpDownloadAreaSelectors = new JTabbedPane(); 072 protected JCheckBox cbNewLayer; 073 protected JCheckBox cbStartup; 074 protected final JLabel sizeCheck = new JLabel(); 075 protected Bounds currentBounds = null; 076 protected boolean canceled; 077 078 protected JCheckBox cbDownloadOsmData; 079 protected JCheckBox cbDownloadGpxData; 080 /** the download action and button */ 081 private DownloadAction actDownload; 082 protected SideButton btnDownload; 083 084 private void makeCheckBoxRespondToEnter(JCheckBox cb) { 085 cb.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER,0), "doDownload"); 086 cb.getActionMap().put("doDownload", actDownload); 087 } 088 089 protected final JPanel buildMainPanel() { 090 JPanel pnl = new JPanel(); 091 pnl.setLayout(new GridBagLayout()); 092 093 // adding the download tasks 094 pnl.add(new JLabel(tr("Data Sources and Types:")), GBC.std().insets(5,5,1,5)); 095 cbDownloadOsmData = new JCheckBox(tr("OpenStreetMap data"), true); 096 cbDownloadOsmData.setToolTipText(tr("Select to download OSM data in the selected download area.")); 097 pnl.add(cbDownloadOsmData, GBC.std().insets(1,5,1,5)); 098 cbDownloadGpxData = new JCheckBox(tr("Raw GPS data")); 099 cbDownloadGpxData.setToolTipText(tr("Select to download GPS traces in the selected download area.")); 100 pnl.add(cbDownloadGpxData, GBC.eol().insets(5,5,1,5)); 101 102 // hook for subclasses 103 buildMainPanelAboveDownloadSelections(pnl); 104 105 slippyMapChooser = new SlippyMapChooser(); 106 107 // predefined download selections 108 downloadSelections.add(slippyMapChooser); 109 downloadSelections.add(new BookmarkSelection()); 110 downloadSelections.add(new BoundingBoxSelection()); 111 downloadSelections.add(new PlaceSelection()); 112 downloadSelections.add(new TileSelection()); 113 114 // add selections from plugins 115 PluginHandler.addDownloadSelection(downloadSelections); 116 117 // now everybody may add their tab to the tabbed pane 118 // (not done right away to allow plugins to remove one of 119 // the default selectors!) 120 for (DownloadSelection s : downloadSelections) { 121 s.addGui(this); 122 } 123 124 pnl.add(tpDownloadAreaSelectors, GBC.eol().fill()); 125 126 try { 127 tpDownloadAreaSelectors.setSelectedIndex(Main.pref.getInteger("download.tab", 0)); 128 } catch (Exception ex) { 129 Main.pref.putInteger("download.tab", 0); 130 } 131 132 Font labelFont = sizeCheck.getFont(); 133 sizeCheck.setFont(labelFont.deriveFont(Font.PLAIN, labelFont.getSize())); 134 135 cbNewLayer = new JCheckBox(tr("Download as new layer")); 136 cbNewLayer.setToolTipText(tr("<html>Select to download data into a new data layer.<br>" 137 +"Unselect to download into the currently active data layer.</html>")); 138 139 cbStartup = new JCheckBox(tr("Open this dialog on startup")); 140 cbStartup.setToolTipText(tr("<html>Autostart ''Download from OSM'' dialog every time JOSM is started.<br>You can open it manually from File menu or toolbar.</html>")); 141 cbStartup.addActionListener(new ActionListener() { 142 @Override 143 public void actionPerformed(ActionEvent e) { 144 Main.pref.put("download.autorun", cbStartup.isSelected()); 145 }}); 146 147 pnl.add(cbNewLayer, GBC.std().anchor(GBC.WEST).insets(5,5,5,5)); 148 pnl.add(cbStartup, GBC.std().anchor(GBC.WEST).insets(15,5,5,5)); 149 150 pnl.add(sizeCheck, GBC.eol().anchor(GBC.EAST).insets(5,5,5,2)); 151 152 if (!ExpertToggleAction.isExpert()) { 153 JLabel infoLabel = new JLabel(tr("Use left click&drag to select area, arrows or right mouse button to scroll map, wheel or +/- to zoom.")); 154 pnl.add(infoLabel,GBC.eol().anchor(GBC.SOUTH).insets(0,0,0,0)); 155 } 156 return pnl; 157 } 158 159 /* This should not be necessary, but if not here, repaint is not always correct in SlippyMap! */ 160 @Override 161 public void paint(Graphics g) { 162 tpDownloadAreaSelectors.getSelectedComponent().paint(g); 163 super.paint(g); 164 } 165 166 protected final JPanel buildButtonPanel() { 167 JPanel pnl = new JPanel(); 168 pnl.setLayout(new FlowLayout()); 169 170 // -- download button 171 pnl.add(btnDownload = new SideButton(actDownload = new DownloadAction())); 172 InputMapUtils.enableEnter(btnDownload); 173 174 makeCheckBoxRespondToEnter(cbDownloadGpxData); 175 makeCheckBoxRespondToEnter(cbDownloadOsmData); 176 makeCheckBoxRespondToEnter(cbNewLayer); 177 178 // -- cancel button 179 SideButton btnCancel; 180 CancelAction actCancel = new CancelAction(); 181 pnl.add(btnCancel = new SideButton(actCancel)); 182 InputMapUtils.enableEnter(btnCancel); 183 184 // -- cancel on ESC 185 getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE,0), "cancel"); 186 getRootPane().getActionMap().put("cancel", actCancel); 187 188 // -- help button 189 SideButton btnHelp; 190 pnl.add(btnHelp = new SideButton(new ContextSensitiveHelpAction(ht("/Action/Download")))); 191 InputMapUtils.enableEnter(btnHelp); 192 193 return pnl; 194 } 195 196 public DownloadDialog(Component parent) { 197 super(JOptionPane.getFrameForComponent(parent),tr("Download"), ModalityType.DOCUMENT_MODAL); 198 getContentPane().setLayout(new BorderLayout()); 199 getContentPane().add(buildMainPanel(), BorderLayout.CENTER); 200 getContentPane().add(buildButtonPanel(), BorderLayout.SOUTH); 201 202 getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put( 203 KeyStroke.getKeyStroke(KeyEvent.VK_V, InputEvent.CTRL_MASK), "checkClipboardContents"); 204 205 getRootPane().getActionMap().put("checkClipboardContents", new AbstractAction() { 206 @Override 207 public void actionPerformed(ActionEvent e) { 208 String clip = Utils.getClipboardContent(); 209 if (clip == null) { 210 return; 211 } 212 Bounds b = OsmUrlToBounds.parse(clip); 213 if (b != null) { 214 boundingBoxChanged(new Bounds(b), null); 215 } 216 } 217 }); 218 HelpUtil.setHelpContext(getRootPane(), ht("/Action/Download")); 219 addWindowListener(new WindowEventHandler()); 220 restoreSettings(); 221 } 222 223 private void updateSizeCheck() { 224 if (currentBounds == null) { 225 sizeCheck.setText(tr("No area selected yet")); 226 sizeCheck.setForeground(Color.darkGray); 227 } else if (currentBounds.getArea() > Main.pref.getDouble("osm-server.max-request-area", 0.25)) { 228 sizeCheck.setText(tr("Download area too large; will probably be rejected by server")); 229 sizeCheck.setForeground(Color.red); 230 } else { 231 sizeCheck.setText(tr("Download area ok, size probably acceptable to server")); 232 sizeCheck.setForeground(Color.darkGray); 233 } 234 } 235 236 /** 237 * Distributes a "bounding box changed" from one DownloadSelection 238 * object to the others, so they may update or clear their input 239 * fields. 240 * 241 * @param eventSource - the DownloadSelection object that fired this notification. 242 */ 243 public void boundingBoxChanged(Bounds b, DownloadSelection eventSource) { 244 this.currentBounds = b; 245 for (DownloadSelection s : downloadSelections) { 246 if (s != eventSource) { 247 s.setDownloadArea(currentBounds); 248 } 249 } 250 updateSizeCheck(); 251 } 252 253 /** 254 * Invoked by 255 * @param b 256 */ 257 public void startDownload(Bounds b) { 258 this.currentBounds = b; 259 actDownload.run(); 260 } 261 262 /** 263 * Replies true if the user selected to download OSM data 264 * 265 * @return true if the user selected to download OSM data 266 */ 267 public boolean isDownloadOsmData() { 268 return cbDownloadOsmData.isSelected(); 269 } 270 271 /** 272 * Replies true if the user selected to download GPX data 273 * 274 * @return true if the user selected to download GPX data 275 */ 276 public boolean isDownloadGpxData() { 277 return cbDownloadGpxData.isSelected(); 278 } 279 280 /** 281 * Replies true if the user requires to download into a new layer 282 * 283 * @return true if the user requires to download into a new layer 284 */ 285 public boolean isNewLayerRequired() { 286 return cbNewLayer.isSelected(); 287 } 288 289 /** 290 * Adds a new download area selector to the download dialog 291 * 292 * @param selector the download are selector 293 * @param displayName the display name of the selector 294 */ 295 public void addDownloadAreaSelector(JPanel selector, String displayName) { 296 tpDownloadAreaSelectors.add(displayName, selector); 297 } 298 299 /** 300 * Refreshes the tile sources 301 * @since 6364 302 */ 303 public final void refreshTileSources() { 304 if (slippyMapChooser != null) { 305 slippyMapChooser.refreshTileSources(); 306 } 307 } 308 309 /** 310 * Remembers the current settings in the download dialog. 311 */ 312 public void rememberSettings() { 313 Main.pref.put("download.tab", Integer.toString(tpDownloadAreaSelectors.getSelectedIndex())); 314 Main.pref.put("download.osm", cbDownloadOsmData.isSelected()); 315 Main.pref.put("download.gps", cbDownloadGpxData.isSelected()); 316 Main.pref.put("download.newlayer", cbNewLayer.isSelected()); 317 if (currentBounds != null) { 318 Main.pref.put("osm-download.bounds", currentBounds.encodeAsString(";")); 319 } 320 } 321 322 /** 323 * Restores the previous settings in the download dialog. 324 */ 325 public void restoreSettings() { 326 cbDownloadOsmData.setSelected(Main.pref.getBoolean("download.osm", true)); 327 cbDownloadGpxData.setSelected(Main.pref.getBoolean("download.gps", false)); 328 cbNewLayer.setSelected(Main.pref.getBoolean("download.newlayer", false)); 329 cbStartup.setSelected( isAutorunEnabled() ); 330 int idx = Main.pref.getInteger("download.tab", 0); 331 if (idx < 0 || idx > tpDownloadAreaSelectors.getTabCount()) { 332 idx = 0; 333 } 334 tpDownloadAreaSelectors.setSelectedIndex(idx); 335 336 if (Main.isDisplayingMapView()) { 337 MapView mv = Main.map.mapView; 338 currentBounds = new Bounds( 339 mv.getLatLon(0, mv.getHeight()), 340 mv.getLatLon(mv.getWidth(), 0) 341 ); 342 boundingBoxChanged(currentBounds,null); 343 } 344 else { 345 Bounds bounds = getSavedDownloadBounds(); 346 if (bounds != null) { 347 currentBounds = bounds; 348 boundingBoxChanged(currentBounds, null); 349 } 350 } 351 } 352 353 /** 354 * Returns the previously saved bounding box from preferences. 355 * @return The bounding box saved in preferences if any, {@code null} otherwise 356 * @since 6509 357 */ 358 public static Bounds getSavedDownloadBounds() { 359 String value = Main.pref.get("osm-download.bounds"); 360 if (!value.isEmpty()) { 361 try { 362 return new Bounds(value, ";"); 363 } catch (IllegalArgumentException e) { 364 Main.warn(e); 365 } 366 } 367 return null; 368 } 369 370 /** 371 * Determines if the dialog autorun is enabled in preferences. 372 * @return {@code true} if the download dialog must be open at startup, {@code false} otherwise 373 */ 374 public static boolean isAutorunEnabled() { 375 return Main.pref.getBoolean("download.autorun",false); 376 } 377 378 public static void autostartIfNeeded() { 379 if (isAutorunEnabled()) { 380 Main.main.menu.download.actionPerformed(null); 381 } 382 } 383 384 /** 385 * Replies the currently selected download area. 386 * @return the currently selected download area. May be {@code null}, if no download area is selected yet. 387 */ 388 public Bounds getSelectedDownloadArea() { 389 return currentBounds; 390 } 391 392 @Override 393 public void setVisible(boolean visible) { 394 if (visible) { 395 new WindowGeometry( 396 getClass().getName() + ".geometry", 397 WindowGeometry.centerInWindow( 398 getParent(), 399 new Dimension(1000,600) 400 ) 401 ).applySafe(this); 402 } else if (isShowing()) { // Avoid IllegalComponentStateException like in #8775 403 new WindowGeometry(this).remember(getClass().getName() + ".geometry"); 404 } 405 super.setVisible(visible); 406 } 407 408 /** 409 * Replies true if the dialog was canceled 410 * 411 * @return true if the dialog was canceled 412 */ 413 public boolean isCanceled() { 414 return canceled; 415 } 416 417 protected void setCanceled(boolean canceled) { 418 this.canceled = canceled; 419 } 420 421 protected void buildMainPanelAboveDownloadSelections(JPanel pnl) { 422 } 423 424 class CancelAction extends AbstractAction { 425 public CancelAction() { 426 putValue(NAME, tr("Cancel")); 427 putValue(SMALL_ICON, ImageProvider.get("cancel")); 428 putValue(SHORT_DESCRIPTION, tr("Click to close the dialog and to abort downloading")); 429 } 430 431 public void run() { 432 setCanceled(true); 433 setVisible(false); 434 } 435 436 @Override 437 public void actionPerformed(ActionEvent e) { 438 run(); 439 } 440 } 441 442 class DownloadAction extends AbstractAction { 443 public DownloadAction() { 444 putValue(NAME, tr("Download")); 445 putValue(SMALL_ICON, ImageProvider.get("download")); 446 putValue(SHORT_DESCRIPTION, tr("Click to download the currently selected area")); 447 setEnabled(!Main.isOffline(OnlineResource.OSM_API)); 448 } 449 450 public void run() { 451 if (currentBounds == null) { 452 JOptionPane.showMessageDialog( 453 DownloadDialog.this, 454 tr("Please select a download area first."), 455 tr("Error"), 456 JOptionPane.ERROR_MESSAGE 457 ); 458 return; 459 } 460 if (!isDownloadOsmData() && !isDownloadGpxData()) { 461 JOptionPane.showMessageDialog( 462 DownloadDialog.this, 463 tr("<html>Neither <strong>{0}</strong> nor <strong>{1}</strong> is enabled.<br>" 464 + "Please choose to either download OSM data, or GPX data, or both.</html>", 465 cbDownloadOsmData.getText(), 466 cbDownloadGpxData.getText() 467 ), 468 tr("Error"), 469 JOptionPane.ERROR_MESSAGE 470 ); 471 return; 472 } 473 setCanceled(false); 474 setVisible(false); 475 } 476 477 @Override 478 public void actionPerformed(ActionEvent e) { 479 run(); 480 } 481 } 482 483 class WindowEventHandler extends WindowAdapter { 484 @Override 485 public void windowClosing(WindowEvent e) { 486 new CancelAction().run(); 487 } 488 489 @Override 490 public void windowActivated(WindowEvent e) { 491 btnDownload.requestFocusInWindow(); 492 } 493 } 494}