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