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