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

Last change on this file since 12643 was 12643, checked in by Don-vip, 4 months ago

see #15182 - deprecate Main.main.menu. Replacement: gui.MainApplication.getMenu()

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