source: josm/trunk/src/org/openstreetmap/josm/gui/download/DownloadDialog.java@ 10790

Last change on this file since 10790 was 10790, checked in by simon04, 8 years ago

see #13319 - Use InputMapUtils where applicable (VK_ENTER)

  • Property svn:eol-style set to native
File size: 19.8 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.download;
3
4import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
5import static org.openstreetmap.josm.tools.I18n.tr;
6
7import java.awt.BorderLayout;
8import java.awt.Color;
9import java.awt.Component;
10import java.awt.Dimension;
11import java.awt.FlowLayout;
12import java.awt.Font;
13import java.awt.Graphics;
14import java.awt.GridBagLayout;
15import java.awt.event.ActionEvent;
16import java.awt.event.InputEvent;
17import java.awt.event.KeyEvent;
18import java.awt.event.WindowAdapter;
19import java.awt.event.WindowEvent;
20import java.util.ArrayList;
21import java.util.List;
22
23import javax.swing.AbstractAction;
24import javax.swing.JButton;
25import javax.swing.JCheckBox;
26import javax.swing.JComponent;
27import javax.swing.JDialog;
28import javax.swing.JLabel;
29import javax.swing.JOptionPane;
30import javax.swing.JPanel;
31import javax.swing.JTabbedPane;
32import javax.swing.KeyStroke;
33import javax.swing.event.ChangeListener;
34
35import org.openstreetmap.josm.Main;
36import org.openstreetmap.josm.actions.ExpertToggleAction;
37import org.openstreetmap.josm.data.Bounds;
38import org.openstreetmap.josm.gui.MapView;
39import org.openstreetmap.josm.gui.datatransfer.ClipboardUtils;
40import org.openstreetmap.josm.gui.help.ContextSensitiveHelpAction;
41import org.openstreetmap.josm.gui.help.HelpUtil;
42import org.openstreetmap.josm.gui.util.GuiHelper;
43import org.openstreetmap.josm.io.OnlineResource;
44import org.openstreetmap.josm.plugins.PluginHandler;
45import org.openstreetmap.josm.tools.GBC;
46import org.openstreetmap.josm.tools.ImageProvider;
47import org.openstreetmap.josm.tools.InputMapUtils;
48import org.openstreetmap.josm.tools.OsmUrlToBounds;
49import org.openstreetmap.josm.tools.WindowGeometry;
50
51/**
52 * Dialog displayed to download OSM and/or GPS data from OSM server.
53 */
54public class DownloadDialog extends JDialog {
55 /** the unique instance of the download dialog */
56 private static DownloadDialog instance;
57
58 /**
59 * Replies the unique instance of the download dialog
60 *
61 * @return the unique instance of the download dialog
62 */
63 public static synchronized DownloadDialog getInstance() {
64 if (instance == null) {
65 instance = new DownloadDialog(Main.parent);
66 }
67 return instance;
68 }
69
70 protected SlippyMapChooser slippyMapChooser;
71 protected final transient List<DownloadSelection> downloadSelections = new ArrayList<>();
72 protected final JTabbedPane tpDownloadAreaSelectors = new JTabbedPane();
73 protected JCheckBox cbNewLayer;
74 protected JCheckBox cbStartup;
75 protected final JLabel sizeCheck = new JLabel();
76 protected transient Bounds currentBounds;
77 protected boolean canceled;
78
79 protected JCheckBox cbDownloadOsmData;
80 protected JCheckBox cbDownloadGpxData;
81 protected JCheckBox cbDownloadNotes;
82 /** the download action and button */
83 private final DownloadAction actDownload = new DownloadAction();
84 protected final JButton btnDownload = new JButton(actDownload);
85
86 protected final JPanel buildMainPanel() {
87 JPanel pnl = new JPanel(new GridBagLayout());
88
89 // size check depends on selected data source
90 final ChangeListener checkboxChangeListener = e -> updateSizeCheck();
91
92 // adding the download tasks
93 pnl.add(new JLabel(tr("Data Sources and Types:")), GBC.std().insets(5, 5, 1, 5));
94 cbDownloadOsmData = new JCheckBox(tr("OpenStreetMap data"), true);
95 cbDownloadOsmData.setToolTipText(tr("Select to download OSM data in the selected download area."));
96 cbDownloadOsmData.getModel().addChangeListener(checkboxChangeListener);
97 pnl.add(cbDownloadOsmData, GBC.std().insets(1, 5, 1, 5));
98 cbDownloadGpxData = new JCheckBox(tr("Raw GPS data"));
99 cbDownloadGpxData.setToolTipText(tr("Select to download GPS traces in the selected download area."));
100 cbDownloadGpxData.getModel().addChangeListener(checkboxChangeListener);
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 cbDownloadNotes.getModel().addChangeListener(checkboxChangeListener);
105 pnl.add(cbDownloadNotes, GBC.eol().insets(50, 5, 1, 5));
106
107 // must be created before hook
108 slippyMapChooser = new SlippyMapChooser();
109
110 // hook for subclasses
111 buildMainPanelAboveDownloadSelections(pnl);
112
113 // predefined download selections
114 downloadSelections.add(slippyMapChooser);
115 downloadSelections.add(new BookmarkSelection());
116 downloadSelections.add(new BoundingBoxSelection());
117 downloadSelections.add(new PlaceSelection());
118 downloadSelections.add(new TileSelection());
119
120 // add selections from plugins
121 PluginHandler.addDownloadSelection(downloadSelections);
122
123 // now everybody may add their tab to the tabbed pane
124 // (not done right away to allow plugins to remove one of
125 // the default selectors!)
126 for (DownloadSelection s : downloadSelections) {
127 s.addGui(this);
128 }
129
130 pnl.add(tpDownloadAreaSelectors, GBC.eol().fill());
131
132 try {
133 tpDownloadAreaSelectors.setSelectedIndex(Main.pref.getInteger("download.tab", 0));
134 } catch (IndexOutOfBoundsException ex) {
135 Main.trace(ex);
136 Main.pref.putInteger("download.tab", 0);
137 }
138
139 Font labelFont = sizeCheck.getFont();
140 sizeCheck.setFont(labelFont.deriveFont(Font.PLAIN, labelFont.getSize()));
141
142 cbNewLayer = new JCheckBox(tr("Download as new layer"));
143 cbNewLayer.setToolTipText(tr("<html>Select to download data into a new data layer.<br>"
144 +"Unselect to download into the currently active data layer.</html>"));
145
146 cbStartup = new JCheckBox(tr("Open this dialog on startup"));
147 cbStartup.setToolTipText(
148 tr("<html>Autostart ''Download from OSM'' dialog every time JOSM is started.<br>" +
149 "You can open it manually from File menu or toolbar.</html>"));
150 cbStartup.addActionListener(e -> Main.pref.put("download.autorun", cbStartup.isSelected()));
151
152 pnl.add(cbNewLayer, GBC.std().anchor(GBC.WEST).insets(5, 5, 5, 5));
153 pnl.add(cbStartup, GBC.std().anchor(GBC.WEST).insets(15, 5, 5, 5));
154
155 pnl.add(sizeCheck, GBC.eol().anchor(GBC.EAST).insets(5, 5, 5, 2));
156
157 if (!ExpertToggleAction.isExpert()) {
158 JLabel infoLabel = new JLabel(
159 tr("Use left click&drag to select area, arrows or right mouse button to scroll map, wheel or +/- to zoom."));
160 pnl.add(infoLabel, GBC.eol().anchor(GBC.SOUTH).insets(0, 0, 0, 0));
161 }
162 return pnl;
163 }
164
165 /* This should not be necessary, but if not here, repaint is not always correct in SlippyMap! */
166 @Override
167 public void paint(Graphics g) {
168 tpDownloadAreaSelectors.getSelectedComponent().paint(g);
169 super.paint(g);
170 }
171
172 protected final JPanel buildButtonPanel() {
173 JPanel pnl = new JPanel(new FlowLayout());
174
175 // -- download button
176 pnl.add(btnDownload);
177 InputMapUtils.enableEnter(btnDownload);
178
179 InputMapUtils.addEnterActionWhenAncestor(cbDownloadGpxData, actDownload);
180 InputMapUtils.addEnterActionWhenAncestor(cbDownloadOsmData, actDownload);
181 InputMapUtils.addEnterActionWhenAncestor(cbDownloadNotes, actDownload);
182 InputMapUtils.addEnterActionWhenAncestor(cbNewLayer, actDownload);
183
184 // -- cancel button
185 JButton btnCancel;
186 CancelAction actCancel = new CancelAction();
187 btnCancel = new JButton(actCancel);
188 pnl.add(btnCancel);
189 InputMapUtils.enableEnter(btnCancel);
190
191 // -- cancel on ESC
192 getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "cancel");
193 getRootPane().getActionMap().put("cancel", actCancel);
194
195 // -- help button
196 JButton btnHelp = new JButton(new ContextSensitiveHelpAction(getRootPane().getClientProperty("help").toString()));
197 pnl.add(btnHelp);
198 InputMapUtils.enableEnter(btnHelp);
199
200 return pnl;
201 }
202
203 /**
204 * Constructs a new {@code DownloadDialog}.
205 * @param parent the parent component
206 */
207 public DownloadDialog(Component parent) {
208 this(parent, ht("/Action/Download"));
209 }
210
211 /**
212 * Constructs a new {@code DownloadDialog}.
213 * @param parent the parent component
214 * @param helpTopic the help topic to assign
215 */
216 public DownloadDialog(Component parent, String helpTopic) {
217 super(GuiHelper.getFrameForComponent(parent), tr("Download"), ModalityType.DOCUMENT_MODAL);
218 HelpUtil.setHelpContext(getRootPane(), helpTopic);
219 getContentPane().setLayout(new BorderLayout());
220 getContentPane().add(buildMainPanel(), BorderLayout.CENTER);
221 getContentPane().add(buildButtonPanel(), BorderLayout.SOUTH);
222
223 getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
224 KeyStroke.getKeyStroke(KeyEvent.VK_V, InputEvent.CTRL_MASK), "checkClipboardContents");
225
226 getRootPane().getActionMap().put("checkClipboardContents", new AbstractAction() {
227 @Override
228 public void actionPerformed(ActionEvent e) {
229 String clip = ClipboardUtils.getClipboardStringContent();
230 if (clip == null) {
231 return;
232 }
233 Bounds b = OsmUrlToBounds.parse(clip);
234 if (b != null) {
235 boundingBoxChanged(new Bounds(b), null);
236 }
237 }
238 });
239 addWindowListener(new WindowEventHandler());
240 restoreSettings();
241 }
242
243 protected void updateSizeCheck() {
244 boolean isAreaTooLarge = false;
245 if (currentBounds == null) {
246 sizeCheck.setText(tr("No area selected yet"));
247 sizeCheck.setForeground(Color.darkGray);
248 } else if (isDownloadNotes() && !isDownloadOsmData() && !isDownloadGpxData()) {
249 // see max_note_request_area in https://github.com/openstreetmap/openstreetmap-website/blob/master/config/example.application.yml
250 isAreaTooLarge = currentBounds.getArea() > Main.pref.getDouble("osm-server.max-request-area-notes", 25);
251 } else {
252 // see max_request_area in https://github.com/openstreetmap/openstreetmap-website/blob/master/config/example.application.yml
253 isAreaTooLarge = currentBounds.getArea() > Main.pref.getDouble("osm-server.max-request-area", 0.25);
254 }
255 displaySizeCheckResult(isAreaTooLarge);
256 }
257
258 protected void displaySizeCheckResult(boolean isAreaTooLarge) {
259 if (isAreaTooLarge) {
260 sizeCheck.setText(tr("Download area too large; will probably be rejected by server"));
261 sizeCheck.setForeground(Color.red);
262 } else {
263 sizeCheck.setText(tr("Download area ok, size probably acceptable to server"));
264 sizeCheck.setForeground(Color.darkGray);
265 }
266 }
267
268 /**
269 * Distributes a "bounding box changed" from one DownloadSelection
270 * object to the others, so they may update or clear their input fields.
271 * @param b new current bounds
272 *
273 * @param eventSource - the DownloadSelection object that fired this notification.
274 */
275 public void boundingBoxChanged(Bounds b, DownloadSelection eventSource) {
276 this.currentBounds = b;
277 for (DownloadSelection s : downloadSelections) {
278 if (s != eventSource) {
279 s.setDownloadArea(currentBounds);
280 }
281 }
282 updateSizeCheck();
283 }
284
285 /**
286 * Starts download for the given bounding box
287 * @param b bounding box to download
288 */
289 public void startDownload(Bounds b) {
290 this.currentBounds = b;
291 actDownload.run();
292 }
293
294 /**
295 * Replies true if the user selected to download OSM data
296 *
297 * @return true if the user selected to download OSM data
298 */
299 public boolean isDownloadOsmData() {
300 return cbDownloadOsmData.isSelected();
301 }
302
303 /**
304 * Replies true if the user selected to download GPX data
305 *
306 * @return true if the user selected to download GPX data
307 */
308 public boolean isDownloadGpxData() {
309 return cbDownloadGpxData.isSelected();
310 }
311
312 /**
313 * Replies true if user selected to download notes
314 *
315 * @return true if user selected to download notes
316 */
317 public boolean isDownloadNotes() {
318 return cbDownloadNotes.isSelected();
319 }
320
321 /**
322 * Replies true if the user requires to download into a new layer
323 *
324 * @return true if the user requires to download into a new layer
325 */
326 public boolean isNewLayerRequired() {
327 return cbNewLayer.isSelected();
328 }
329
330 /**
331 * Adds a new download area selector to the download dialog
332 *
333 * @param selector the download are selector
334 * @param displayName the display name of the selector
335 */
336 public void addDownloadAreaSelector(JPanel selector, String displayName) {
337 tpDownloadAreaSelectors.add(displayName, selector);
338 }
339
340 /**
341 * Refreshes the tile sources
342 * @since 6364
343 */
344 public final void refreshTileSources() {
345 if (slippyMapChooser != null) {
346 slippyMapChooser.refreshTileSources();
347 }
348 }
349
350 /**
351 * Remembers the current settings in the download dialog.
352 */
353 public void rememberSettings() {
354 Main.pref.put("download.tab", Integer.toString(tpDownloadAreaSelectors.getSelectedIndex()));
355 Main.pref.put("download.osm", cbDownloadOsmData.isSelected());
356 Main.pref.put("download.gps", cbDownloadGpxData.isSelected());
357 Main.pref.put("download.notes", cbDownloadNotes.isSelected());
358 Main.pref.put("download.newlayer", cbNewLayer.isSelected());
359 if (currentBounds != null) {
360 Main.pref.put("osm-download.bounds", currentBounds.encodeAsString(";"));
361 }
362 }
363
364 /**
365 * Restores the previous settings in the download dialog.
366 */
367 public void restoreSettings() {
368 cbDownloadOsmData.setSelected(Main.pref.getBoolean("download.osm", true));
369 cbDownloadGpxData.setSelected(Main.pref.getBoolean("download.gps", false));
370 cbDownloadNotes.setSelected(Main.pref.getBoolean("download.notes", false));
371 cbNewLayer.setSelected(Main.pref.getBoolean("download.newlayer", false));
372 cbStartup.setSelected(isAutorunEnabled());
373 int idx = Main.pref.getInteger("download.tab", 0);
374 if (idx < 0 || idx > tpDownloadAreaSelectors.getTabCount()) {
375 idx = 0;
376 }
377 tpDownloadAreaSelectors.setSelectedIndex(idx);
378
379 if (Main.isDisplayingMapView()) {
380 MapView mv = Main.map.mapView;
381 currentBounds = new Bounds(
382 mv.getLatLon(0, mv.getHeight()),
383 mv.getLatLon(mv.getWidth(), 0)
384 );
385 boundingBoxChanged(currentBounds, null);
386 } else {
387 Bounds bounds = getSavedDownloadBounds();
388 if (bounds != null) {
389 currentBounds = bounds;
390 boundingBoxChanged(currentBounds, null);
391 }
392 }
393 }
394
395 /**
396 * Returns the previously saved bounding box from preferences.
397 * @return The bounding box saved in preferences if any, {@code null} otherwise
398 * @since 6509
399 */
400 public static Bounds getSavedDownloadBounds() {
401 String value = Main.pref.get("osm-download.bounds");
402 if (!value.isEmpty()) {
403 try {
404 return new Bounds(value, ";");
405 } catch (IllegalArgumentException e) {
406 Main.warn(e);
407 }
408 }
409 return null;
410 }
411
412 /**
413 * Determines if the dialog autorun is enabled in preferences.
414 * @return {@code true} if the download dialog must be open at startup, {@code false} otherwise
415 */
416 public static boolean isAutorunEnabled() {
417 return Main.pref.getBoolean("download.autorun", false);
418 }
419
420 /**
421 * Automatically opens the download dialog, if autorun is enabled.
422 * @see #isAutorunEnabled
423 */
424 public static void autostartIfNeeded() {
425 if (isAutorunEnabled()) {
426 Main.main.menu.download.actionPerformed(null);
427 }
428 }
429
430 /**
431 * Replies the currently selected download area.
432 * @return the currently selected download area. May be {@code null}, if no download area is selected yet.
433 */
434 public Bounds getSelectedDownloadArea() {
435 return currentBounds;
436 }
437
438 @Override
439 public void setVisible(boolean visible) {
440 if (visible) {
441 new WindowGeometry(
442 getClass().getName() + ".geometry",
443 WindowGeometry.centerInWindow(
444 getParent(),
445 new Dimension(1000, 600)
446 )
447 ).applySafe(this);
448 } else if (isShowing()) { // Avoid IllegalComponentStateException like in #8775
449 new WindowGeometry(this).remember(getClass().getName() + ".geometry");
450 }
451 super.setVisible(visible);
452 }
453
454 /**
455 * Replies true if the dialog was canceled
456 *
457 * @return true if the dialog was canceled
458 */
459 public boolean isCanceled() {
460 return canceled;
461 }
462
463 protected void setCanceled(boolean canceled) {
464 this.canceled = canceled;
465 }
466
467 protected void buildMainPanelAboveDownloadSelections(JPanel pnl) {
468 // Do nothing
469 }
470
471 class CancelAction extends AbstractAction {
472 CancelAction() {
473 putValue(NAME, tr("Cancel"));
474 new ImageProvider("cancel").getResource().attachImageIcon(this);
475 putValue(SHORT_DESCRIPTION, tr("Click to close the dialog and to abort downloading"));
476 }
477
478 public void run() {
479 setCanceled(true);
480 setVisible(false);
481 }
482
483 @Override
484 public void actionPerformed(ActionEvent e) {
485 run();
486 }
487 }
488
489 class DownloadAction extends AbstractAction {
490 DownloadAction() {
491 putValue(NAME, tr("Download"));
492 new ImageProvider("download").getResource().attachImageIcon(this);
493 putValue(SHORT_DESCRIPTION, tr("Click to download the currently selected area"));
494 setEnabled(!Main.isOffline(OnlineResource.OSM_API));
495 }
496
497 public void run() {
498 if (currentBounds == null) {
499 JOptionPane.showMessageDialog(
500 DownloadDialog.this,
501 tr("Please select a download area first."),
502 tr("Error"),
503 JOptionPane.ERROR_MESSAGE
504 );
505 return;
506 }
507 if (!isDownloadOsmData() && !isDownloadGpxData() && !isDownloadNotes()) {
508 JOptionPane.showMessageDialog(
509 DownloadDialog.this,
510 tr("<html>Neither <strong>{0}</strong> nor <strong>{1}</strong> nor <strong>{2}</strong> is enabled.<br>"
511 + "Please choose to either download OSM data, or GPX data, or Notes, or all.</html>",
512 cbDownloadOsmData.getText(),
513 cbDownloadGpxData.getText(),
514 cbDownloadNotes.getText()
515 ),
516 tr("Error"),
517 JOptionPane.ERROR_MESSAGE
518 );
519 return;
520 }
521 setCanceled(false);
522 setVisible(false);
523 }
524
525 @Override
526 public void actionPerformed(ActionEvent e) {
527 run();
528 }
529 }
530
531 class WindowEventHandler extends WindowAdapter {
532 @Override
533 public void windowClosing(WindowEvent e) {
534 new CancelAction().run();
535 }
536
537 @Override
538 public void windowActivated(WindowEvent e) {
539 btnDownload.requestFocusInWindow();
540 }
541 }
542}
Note: See TracBrowser for help on using the repository browser.