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

Last change on this file since 12574 was 12574, checked in by michael2402, 4 months ago

Apply #15057: Improve the over pass turbo dialog

Adds the ability to add favorites and a new wizard dialog with examples.

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