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