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