Ticket #15167: merge-download-dialog-v2.patch

File merge-download-dialog-v2.patch, 83.9 KB (added by bafonins, 8 years ago)

added label to check download area

  • src/org/openstreetmap/josm/actions/DownloadAction.java

     
    66
    77import java.awt.event.ActionEvent;
    88import java.awt.event.KeyEvent;
    9 import java.util.ArrayList;
    10 import java.util.List;
    11 import java.util.Optional;
    12 import java.util.concurrent.ExecutionException;
    13 import java.util.concurrent.Future;
    149
    15 import javax.swing.JOptionPane;
    16 
    17 import org.openstreetmap.josm.Main;
    18 import org.openstreetmap.josm.actions.downloadtasks.AbstractDownloadTask;
    19 import org.openstreetmap.josm.actions.downloadtasks.DownloadGpsTask;
    20 import org.openstreetmap.josm.actions.downloadtasks.DownloadNotesTask;
    21 import org.openstreetmap.josm.actions.downloadtasks.DownloadOsmTask;
    22 import org.openstreetmap.josm.actions.downloadtasks.PostDownloadHandler;
    23 import org.openstreetmap.josm.data.Bounds;
    24 import org.openstreetmap.josm.data.ProjectionBounds;
    25 import org.openstreetmap.josm.data.ViewportData;
    2610import org.openstreetmap.josm.gui.download.DownloadDialog;
    27 import org.openstreetmap.josm.gui.util.GuiHelper;
    28 import org.openstreetmap.josm.tools.Pair;
    2911import org.openstreetmap.josm.tools.Shortcut;
    3012
    3113/**
     
    4224     * Constructs a new {@code DownloadAction}.
    4325     */
    4426    public DownloadAction() {
    45         super(tr("Download from OSM..."), "download", tr("Download map data from the OSM server."),
    46               Shortcut.registerShortcut("file:download", tr("File: {0}", tr("Download from OSM...")), KeyEvent.VK_DOWN, Shortcut.CTRL_SHIFT),
     27        super(tr("Download data"), "download", tr("Download map data from a server of your choice"),
     28              Shortcut.registerShortcut("file:download", tr("File: {0}", tr("Download data")), KeyEvent.VK_DOWN, Shortcut.CTRL_SHIFT),
    4729              true);
    4830        putValue("help", ht("/Action/Download"));
    4931    }
     
    5335        DownloadDialog dialog = DownloadDialog.getInstance();
    5436        dialog.restoreSettings();
    5537        dialog.setVisible(true);
    56 
    57         if (dialog.isCanceled()) {
    58             return;
    59         }
    60 
    61         dialog.rememberSettings();
    62 
    63         Optional<Bounds> selectedArea = dialog.getSelectedDownloadArea();
    64         if (!selectedArea.isPresent()) {
    65             JOptionPane.showMessageDialog(
    66                     dialog,
    67                     tr("Please select a download area first."),
    68                     tr("Error"),
    69                     JOptionPane.ERROR_MESSAGE
    70             );
    71             return;
    72         }
    73 
    74         final Bounds area = selectedArea.get();
    75         final boolean zoom = dialog.isZoomToDownloadedDataRequired();
    76         final List<Pair<AbstractDownloadTask<?>, Future<?>>> tasks = new ArrayList<>();
    77 
    78         if (dialog.isDownloadOsmData()) {
    79             DownloadOsmTask task = new DownloadOsmTask();
    80             task.setZoomAfterDownload(zoom && !dialog.isDownloadGpxData() && !dialog.isDownloadNotes());
    81             Future<?> future = task.download(dialog.isNewLayerRequired(), area, null);
    82             Main.worker.submit(new PostDownloadHandler(task, future));
    83             if (zoom) {
    84                 tasks.add(new Pair<>(task, future));
    85             }
    86         }
    87 
    88         if (dialog.isDownloadGpxData()) {
    89             DownloadGpsTask task = new DownloadGpsTask();
    90             task.setZoomAfterDownload(zoom && !dialog.isDownloadOsmData() && !dialog.isDownloadNotes());
    91             Future<?> future = task.download(dialog.isNewLayerRequired(), area, null);
    92             Main.worker.submit(new PostDownloadHandler(task, future));
    93             if (zoom) {
    94                 tasks.add(new Pair<>(task, future));
    95             }
    96         }
    97 
    98         if (dialog.isDownloadNotes()) {
    99             DownloadNotesTask task = new DownloadNotesTask();
    100             task.setZoomAfterDownload(zoom && !dialog.isDownloadOsmData() && !dialog.isDownloadGpxData());
    101             Future<?> future = task.download(false, area, null);
    102             Main.worker.submit(new PostDownloadHandler(task, future));
    103             if (zoom) {
    104                 tasks.add(new Pair<>(task, future));
    105             }
    106         }
    107 
    108         if (zoom && tasks.size() > 1) {
    109             Main.worker.submit(() -> {
    110                 ProjectionBounds bounds = null;
    111                 // Wait for completion of download jobs
    112                 for (Pair<AbstractDownloadTask<?>, Future<?>> p : tasks) {
    113                     try {
    114                         p.b.get();
    115                         ProjectionBounds b = p.a.getDownloadProjectionBounds();
    116                         if (bounds == null) {
    117                             bounds = b;
    118                         } else if (b != null) {
    119                             bounds.extend(b);
    120                         }
    121                     } catch (InterruptedException | ExecutionException ex) {
    122                         Main.warn(ex);
    123                     }
    124                 }
    125                 // Zoom to the larger download bounds
    126                 if (Main.map != null && bounds != null) {
    127                     final ProjectionBounds pb = bounds;
    128                     GuiHelper.runInEDTAndWait(() -> Main.map.mapView.zoomTo(new ViewportData(pb)));
    129                 }
    130             });
    131         }
    13238    }
    13339}
  • src/org/openstreetmap/josm/actions/OverpassDownloadAction.java

     
    1 // License: GPL. For details, see LICENSE file.
    2 package org.openstreetmap.josm.actions;
    3 
    4 import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
    5 import static org.openstreetmap.josm.tools.I18n.tr;
    6 
    7 import java.awt.BorderLayout;
    8 import java.awt.Component;
    9 import java.awt.Dimension;
    10 import java.awt.GridBagLayout;
    11 import java.awt.event.ActionEvent;
    12 import java.awt.event.FocusEvent;
    13 import java.awt.event.FocusListener;
    14 import java.awt.event.KeyEvent;
    15 import java.util.Collection;
    16 import java.util.Optional;
    17 import java.util.concurrent.Future;
    18 import java.util.function.Consumer;
    19 
    20 import javax.swing.AbstractAction;
    21 import javax.swing.Action;
    22 import javax.swing.ActionMap;
    23 import javax.swing.JButton;
    24 import javax.swing.JLabel;
    25 import javax.swing.JOptionPane;
    26 import javax.swing.JPanel;
    27 import javax.swing.JScrollPane;
    28 import javax.swing.event.ListSelectionEvent;
    29 import javax.swing.event.ListSelectionListener;
    30 import javax.swing.plaf.basic.BasicArrowButton;
    31 
    32 import org.openstreetmap.josm.Main;
    33 import org.openstreetmap.josm.actions.downloadtasks.DownloadOsmTask;
    34 import org.openstreetmap.josm.actions.downloadtasks.PostDownloadHandler;
    35 import org.openstreetmap.josm.data.Bounds;
    36 import org.openstreetmap.josm.data.preferences.BooleanProperty;
    37 import org.openstreetmap.josm.gui.ConditionalOptionPaneUtil;
    38 import org.openstreetmap.josm.gui.download.DownloadDialog;
    39 import org.openstreetmap.josm.gui.download.OverpassQueryList;
    40 import org.openstreetmap.josm.gui.download.OverpassQueryWizardDialog;
    41 import org.openstreetmap.josm.gui.preferences.server.OverpassServerPreference;
    42 import org.openstreetmap.josm.gui.util.GuiHelper;
    43 import org.openstreetmap.josm.gui.widgets.JosmTextArea;
    44 import org.openstreetmap.josm.io.OverpassDownloadReader;
    45 import org.openstreetmap.josm.tools.GBC;
    46 import org.openstreetmap.josm.tools.ImageProvider;
    47 import org.openstreetmap.josm.tools.Shortcut;
    48 
    49 /**
    50  * Download map data from Overpass API server.
    51  * @since 8684
    52  */
    53 public class OverpassDownloadAction extends JosmAction {
    54 
    55     /**
    56      * Constructs a new {@code OverpassDownloadAction}.
    57      */
    58     public OverpassDownloadAction() {
    59         super(tr("Download from Overpass API ..."), "download-overpass", tr("Download map data from Overpass API server."),
    60                 // CHECKSTYLE.OFF: LineLength
    61                 Shortcut.registerShortcut("file:download-overpass", tr("File: {0}", tr("Download from Overpass API ...")), KeyEvent.VK_DOWN, Shortcut.ALT_SHIFT),
    62                 // CHECKSTYLE.ON: LineLength
    63                 true, "overpassdownload/download", true);
    64         putValue("help", ht("/Action/OverpassDownload"));
    65     }
    66 
    67     @Override
    68     public void actionPerformed(ActionEvent e) {
    69         OverpassDownloadDialog dialog = OverpassDownloadDialog.getInstance();
    70         dialog.restoreSettings();
    71         dialog.setVisible(true);
    72 
    73         if (dialog.isCanceled()) {
    74             return;
    75         }
    76 
    77         dialog.rememberSettings();
    78         Optional<Bounds> selectedArea = dialog.getSelectedDownloadArea();
    79         String overpassQuery = dialog.getRepairedOverpassQuery();
    80 
    81         /*
    82          * Absence of the selected area can be justified only if the overpass query
    83          * is not restricted to bbox.
    84          */
    85         if (!selectedArea.isPresent() && overpassQuery.contains("{{bbox}}")) {
    86             JOptionPane.showMessageDialog(
    87                     dialog,
    88                     tr("Please select a download area first."),
    89                     tr("Error"),
    90                     JOptionPane.ERROR_MESSAGE
    91             );
    92             return;
    93         }
    94 
    95         /*
    96          * A callback that is passed to PostDownloadReporter that is called once the download task
    97          * has finished. According to the number of errors happened, their type we decide whether we
    98          * want to save the last query in OverpassQueryList.
    99          */
    100         Consumer<Collection<Object>> errorReporter = errors -> {
    101 
    102             boolean onlyNoDataError = errors.size() == 1 &&
    103                     errors.contains("No data found in this area.");
    104 
    105             if (errors.isEmpty() || onlyNoDataError) {
    106                 dialog.saveHistoricItemOnSuccess(overpassQuery);
    107             }
    108         };
    109 
    110         /*
    111          * In order to support queries generated by the Overpass Turbo Query Wizard tool
    112          * which do not require the area to be specified.
    113          */
    114         Bounds area = selectedArea.orElseGet(() -> new Bounds(0, 0, 0, 0));
    115         DownloadOsmTask task = new DownloadOsmTask();
    116         task.setZoomAfterDownload(dialog.isZoomToDownloadedDataRequired());
    117         Future<?> future = task.download(
    118                 new OverpassDownloadReader(area, OverpassServerPreference.getOverpassServer(), overpassQuery),
    119                 dialog.isNewLayerRequired(), area, null);
    120         Main.worker.submit(new PostDownloadHandler(task, future, errorReporter));
    121     }
    122 
    123     private static final class DisableActionsFocusListener implements FocusListener {
    124 
    125         private final ActionMap actionMap;
    126 
    127         private DisableActionsFocusListener(ActionMap actionMap) {
    128             this.actionMap = actionMap;
    129         }
    130 
    131         @Override
    132         public void focusGained(FocusEvent e) {
    133             enableActions(false);
    134         }
    135 
    136         @Override
    137         public void focusLost(FocusEvent e) {
    138             enableActions(true);
    139         }
    140 
    141         private void enableActions(boolean enabled) {
    142             Object[] allKeys = actionMap.allKeys();
    143             if (allKeys != null) {
    144                 for (Object key : allKeys) {
    145                     Action action = actionMap.get(key);
    146                     if (action != null) {
    147                         action.setEnabled(enabled);
    148                     }
    149                 }
    150             }
    151         }
    152     }
    153 
    154     /**
    155      * The download dialog that overpass uses.
    156      * @since 12576 public
    157      */
    158     public static final class OverpassDownloadDialog extends DownloadDialog {
    159 
    160         private JosmTextArea overpassQuery;
    161         private OverpassQueryList overpassQueryList;
    162         private static OverpassDownloadDialog instance;
    163         private static final BooleanProperty OVERPASS_QUERY_LIST_OPENED =
    164                 new BooleanProperty("download.overpass.query-list.opened", false);
    165         private static final String ACTION_IMG_SUBDIR = "dialogs";
    166 
    167         private OverpassDownloadDialog(Component parent) {
    168             super(parent, ht("/Action/OverpassDownload"));
    169             cbDownloadOsmData.setEnabled(false);
    170             cbDownloadOsmData.setSelected(false);
    171             cbDownloadGpxData.setVisible(false);
    172             cbDownloadNotes.setVisible(false);
    173             cbStartup.setVisible(false);
    174         }
    175 
    176         public static OverpassDownloadDialog getInstance() {
    177             if (instance == null) {
    178                 instance = new OverpassDownloadDialog(Main.parent);
    179             }
    180             return instance;
    181         }
    182 
    183         @Override
    184         protected void buildMainPanelAboveDownloadSelections(JPanel pnl) {
    185             DisableActionsFocusListener disableActionsFocusListener =
    186                     new DisableActionsFocusListener(slippyMapChooser.getNavigationComponentActionMap());
    187 
    188             String tooltip = tr("Build an Overpass query using the Overpass Turbo Query Wizard tool");
    189             Action queryWizardAction = new AbstractAction() {
    190                 @Override
    191                 public void actionPerformed(ActionEvent e) {
    192                     new OverpassQueryWizardDialog(instance).showDialog();
    193                 }
    194             };
    195 
    196             JButton openQueryWizard = new JButton(tr("Query Wizard"));
    197             openQueryWizard.setToolTipText(tooltip);
    198             openQueryWizard.addActionListener(queryWizardAction);
    199 
    200             // use eol() that is needed for the invisible checkboxes cbDownloadGpxData, cbDownloadNotes
    201             pnl.add(openQueryWizard, GBC.eol());
    202             pnl.add(new JLabel(tr("Overpass query:")), GBC.std().insets(5, 5, 0, 0).anchor(GBC.NORTHWEST));
    203 
    204             // CHECKSTYLE.OFF: LineLength
    205             this.overpassQuery = new JosmTextArea(
    206                     "/*\n" +
    207                     tr("Place your Overpass query below or generate one using the Overpass Turbo Query Wizard")
    208                     + "\n*/",
    209                     8, 80);
    210             // CHECKSTYLE.ON: LineLength
    211             this.overpassQuery.setFont(GuiHelper.getMonospacedFont(overpassQuery));
    212             this.overpassQuery.addFocusListener(disableActionsFocusListener);
    213             this.overpassQuery.addFocusListener(new FocusListener() {
    214                 @Override
    215                 public void focusGained(FocusEvent e) {
    216                     overpassQuery.selectAll();
    217                 }
    218 
    219                 @Override
    220                 public void focusLost(FocusEvent e) {
    221                     // ignored
    222                 }
    223             });
    224 
    225 
    226             this.overpassQueryList = new OverpassQueryList(this, this.overpassQuery);
    227             this.overpassQueryList.setPreferredSize(new Dimension(350, 300));
    228 
    229             EditSnippetAction edit = new EditSnippetAction();
    230             RemoveSnippetAction remove = new RemoveSnippetAction();
    231             this.overpassQueryList.addSelectionListener(edit);
    232             this.overpassQueryList.addSelectionListener(remove);
    233 
    234             JPanel listPanel = new JPanel(new GridBagLayout());
    235             listPanel.add(new JLabel(tr("Your saved queries:")), GBC.eol().insets(2).anchor(GBC.CENTER));
    236             listPanel.add(this.overpassQueryList, GBC.eol().fill(GBC.BOTH));
    237             listPanel.add(new JButton(new AddSnippetAction()), GBC.std().fill(GBC.HORIZONTAL));
    238             listPanel.add(new JButton(edit), GBC.std().fill(GBC.HORIZONTAL));
    239             listPanel.add(new JButton(remove), GBC.std().fill(GBC.HORIZONTAL));
    240             listPanel.setVisible(OVERPASS_QUERY_LIST_OPENED.get());
    241 
    242             JScrollPane scrollPane = new JScrollPane(overpassQuery);
    243             BasicArrowButton arrowButton = new BasicArrowButton(listPanel.isVisible()
    244                 ? BasicArrowButton.EAST
    245                 : BasicArrowButton.WEST);
    246             arrowButton.setToolTipText(tr("Show/hide Overpass snippet list"));
    247             arrowButton.addActionListener(e -> {
    248                 if (listPanel.isVisible()) {
    249                     listPanel.setVisible(false);
    250                     arrowButton.setDirection(BasicArrowButton.WEST);
    251                     OVERPASS_QUERY_LIST_OPENED.put(Boolean.FALSE);
    252                 } else {
    253                     listPanel.setVisible(true);
    254                     arrowButton.setDirection(BasicArrowButton.EAST);
    255                     OVERPASS_QUERY_LIST_OPENED.put(Boolean.TRUE);
    256                 }
    257             });
    258 
    259             JPanel innerPanel = new JPanel(new BorderLayout());
    260             innerPanel.add(scrollPane, BorderLayout.CENTER);
    261             innerPanel.add(arrowButton, BorderLayout.EAST);
    262 
    263             JPanel pane = new JPanel(new BorderLayout());
    264             pane.add(innerPanel, BorderLayout.CENTER);
    265             pane.add(listPanel, BorderLayout.EAST);
    266 
    267             GBC gbc = GBC.eol().fill(GBC.HORIZONTAL); gbc.ipady = 200;
    268             pnl.add(pane, gbc);
    269         }
    270 
    271         public String getOverpassQuery() {
    272             return overpassQuery.getText();
    273         }
    274 
    275         String getRepairedOverpassQuery() {
    276             String query = getOverpassQuery();
    277             if (query.matches("(/\\*(\\*[^/]|[^\\*/])*\\*/|\\s)*")) {
    278                 // Empty query. User might want to download everything
    279                 boolean doFix = ConditionalOptionPaneUtil.showConfirmationDialog(
    280                         "download.overpass.fix.emptytoall",
    281                         this,
    282                         tr("You entered an empty query. Do you want to download all data in this area instead?"),
    283                         tr("Download all data?"),
    284                         JOptionPane.YES_NO_OPTION,
    285                         JOptionPane.QUESTION_MESSAGE,
    286                         JOptionPane.YES_OPTION);
    287                 if (doFix) {
    288                     return "[out:xml]; \n"
    289                             + query + "\n"
    290                             + "(\n"
    291                             + "    node({{bbox}});\n"
    292                             + "<;\n"
    293                             + ");\n"
    294                             + "(._;>;);"
    295                             + "out meta;";
    296                 }
    297             }
    298             // Note: We can add more repairs here. We might e.g. want to intercept missing 'out meta'.
    299             return query;
    300         }
    301 
    302         /**
    303          * Sets the query that is displayed
    304          * @param text The multiline query text.
    305          * @since 12576 public
    306          */
    307         public void setOverpassQuery(String text) {
    308             overpassQuery.setText(text);
    309         }
    310 
    311         /**
    312          * Adds the current query to {@link OverpassQueryList}.
    313          * @param overpassQueryToSave The query to save
    314          */
    315         void saveHistoricItemOnSuccess(String overpassQueryToSave) {
    316             overpassQueryList.saveHistoricItem(overpassQueryToSave);
    317         }
    318 
    319         @Override
    320         protected void updateSizeCheck() {
    321             displaySizeCheckResult(false);
    322         }
    323 
    324         /**
    325          * Triggers the download action to fire.
    326          * @since 12576 public
    327          */
    328         public void triggerDownload() {
    329             super.btnDownload.doClick();
    330         }
    331 
    332         /**
    333          * Action that delegates snippet creation to {@link OverpassQueryList#createNewItem()}.
    334          */
    335         class AddSnippetAction extends AbstractAction {
    336 
    337             /**
    338              * Constructs a new {@code AddSnippetAction}.
    339              */
    340             AddSnippetAction() {
    341                 super();
    342                 putValue(SMALL_ICON, ImageProvider.get(ACTION_IMG_SUBDIR, "add"));
    343                 putValue(SHORT_DESCRIPTION, tr("Add new snippet"));
    344             }
    345 
    346             @Override
    347             public void actionPerformed(ActionEvent e) {
    348                 overpassQueryList.createNewItem();
    349             }
    350         }
    351 
    352         /**
    353          * Action that delegates snippet removal to {@link OverpassQueryList#removeSelectedItem()}.
    354          */
    355         class RemoveSnippetAction extends AbstractAction implements ListSelectionListener {
    356 
    357             /**
    358              * Constructs a new {@code RemoveSnippetAction}.
    359              */
    360             RemoveSnippetAction() {
    361                 super();
    362                 putValue(SMALL_ICON, ImageProvider.get(ACTION_IMG_SUBDIR, "delete"));
    363                 putValue(SHORT_DESCRIPTION, tr("Delete selected snippet"));
    364                 checkEnabled();
    365             }
    366 
    367             @Override
    368             public void actionPerformed(ActionEvent e) {
    369                 overpassQueryList.removeSelectedItem();
    370             }
    371 
    372             /**
    373              * Disables the action if no items are selected.
    374              */
    375             void checkEnabled() {
    376                 setEnabled(overpassQueryList.getSelectedItem().isPresent());
    377             }
    378 
    379             @Override
    380             public void valueChanged(ListSelectionEvent e) {
    381                 checkEnabled();
    382             }
    383         }
    384 
    385         /**
    386          * Action that delegates snippet edit to {@link OverpassQueryList#editSelectedItem()}.
    387          */
    388         class EditSnippetAction extends AbstractAction implements ListSelectionListener {
    389 
    390             /**
    391              * Constructs a new {@code EditSnippetAction}.
    392              */
    393             EditSnippetAction() {
    394                 super();
    395                 putValue(SMALL_ICON, ImageProvider.get(ACTION_IMG_SUBDIR, "edit"));
    396                 putValue(SHORT_DESCRIPTION, tr("Edit selected snippet"));
    397                 checkEnabled();
    398             }
    399 
    400             @Override
    401             public void actionPerformed(ActionEvent e) {
    402                 overpassQueryList.editSelectedItem();
    403             }
    404 
    405             /**
    406              * Disables the action if no items are selected.
    407              */
    408             void checkEnabled() {
    409                 setEnabled(overpassQueryList.getSelectedItem().isPresent());
    410             }
    411 
    412             @Override
    413             public void valueChanged(ListSelectionEvent e) {
    414                 checkEnabled();
    415             }
    416         }
    417     }
    418 }
  • src/org/openstreetmap/josm/gui/MainMenu.java

     
    7272import org.openstreetmap.josm.actions.OpenLocationAction;
    7373import org.openstreetmap.josm.actions.OrthogonalizeAction;
    7474import org.openstreetmap.josm.actions.OrthogonalizeAction.Undo;
    75 import org.openstreetmap.josm.actions.OverpassDownloadAction;
    7675import org.openstreetmap.josm.actions.PasteAction;
    7776import org.openstreetmap.josm.actions.PasteAtSourcePositionAction;
    7877import org.openstreetmap.josm.actions.PasteTagsAction;
     
    159158    public final DownloadAction download = new DownloadAction();
    160159    /** File / Download in current view **/
    161160    public final DownloadOsmInViewAction downloadInView = new DownloadOsmInViewAction();
    162     /** File / Download from Overpass API... **/
    163     public final OverpassDownloadAction overpassDownload = new OverpassDownloadAction();
    164161    /** File / Download object... **/
    165162    public final DownloadPrimitiveAction downloadPrimitive = new DownloadPrimitiveAction();
    166163    /** File / Download notes in current view **/
     
    657654        fileMenu.addSeparator();
    658655        add(fileMenu, download);
    659656        add(fileMenu, downloadInView, true);
    660         add(fileMenu, overpassDownload, true);
    661657        add(fileMenu, downloadPrimitive);
    662658        add(fileMenu, searchNotes);
    663659        add(fileMenu, downloadNotesInView);
  • src/org/openstreetmap/josm/gui/download/AbstractDownloadSourcePanel.java

     
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.gui.download;
     3
     4import org.openstreetmap.josm.data.Bounds;
     5
     6import javax.swing.Icon;
     7import javax.swing.JPanel;
     8import java.util.Objects;
     9
     10/**
     11 * GUI representation of {@link DownloadSource} that is shown to the user in
     12 * {@link DownloadDialog}.
     13 * @param <T> The type of the data that a download source uses.
     14 */
     15public abstract class AbstractDownloadSourcePanel<T> extends JPanel {
     16
     17    public AbstractDownloadSourcePanel(final DownloadSource<T> downloadSource) {
     18        Objects.requireNonNull(downloadSource);
     19        this.downloadSource = downloadSource;
     20    }
     21
     22    /**
     23     * The download source of this panel.
     24     */
     25    protected transient DownloadSource<T> downloadSource;
     26
     27    /**
     28     * Gets the data.
     29     * @return Returns the data.
     30     */
     31    public abstract T getData();
     32
     33    /**
     34     * Gets the download source of this panel.
     35     * @return Returns the download source of this panel.
     36     */
     37    public DownloadSource<T> getDownloadSource() {
     38        return this.downloadSource;
     39    }
     40
     41    /**
     42     * Saves the current user preferences devoted to the data source.
     43     */
     44    public abstract void rememberSettings();
     45
     46    /**
     47     * Restores the latest user preferences devoted to the data source.
     48     */
     49    public abstract void restoreSettings();
     50
     51    /**
     52     * Performs the logic needed in case if the user triggered the download
     53     * action in {@link DownloadDialog}.
     54     * @return Returns {@code true} if the required procedure of handling the
     55     * download action succeeded and {@link DownloadDialog} can be closed, e.g. validation,
     56     * otherwise {@code false}.
     57     */
     58    public abstract boolean checkDownload(Bounds bbox, DownloadSettings settings);
     59
     60    /**
     61     * Performs the logic needed in case if the user triggered the cancel
     62     * action in {@link DownloadDialog}.
     63     */
     64    public void checkCancel() {
     65        // nothing, let download dialog to close
     66        // override if necessary
     67    }
     68
     69    /**
     70     * Gets the icon of the download source panel.
     71     * @return The icon. Can be {@code null} if there is no icon associated with
     72     * this download source.
     73     */
     74    public Icon getIcon() {
     75        return null;
     76    }
     77
     78    /**
     79     * Updates GUI components of the panel according to the bbox changes.
     80     * @param bbox The new value for the bounding box.
     81     */
     82    public void boudingBoxChanged(Bounds bbox) {
     83        // override this if the panel must react on bbox changes
     84    }
     85}
  • src/org/openstreetmap/josm/gui/download/DownloadDialog.java

     
    55import static org.openstreetmap.josm.tools.I18n.tr;
    66
    77import java.awt.BorderLayout;
    8 import java.awt.Color;
    98import java.awt.Component;
    109import java.awt.Dimension;
    1110import java.awt.FlowLayout;
    12 import java.awt.Font;
    1311import java.awt.Graphics;
    1412import java.awt.GridBagLayout;
    1513import java.awt.event.ActionEvent;
     
    1816import java.awt.event.WindowAdapter;
    1917import java.awt.event.WindowEvent;
    2018import java.util.ArrayList;
     19import java.util.Arrays;
    2120import java.util.List;
    2221import java.util.Optional;
     22import java.util.stream.IntStream;
    2323
    2424import javax.swing.AbstractAction;
     25import javax.swing.Icon;
    2526import javax.swing.JButton;
    2627import javax.swing.JCheckBox;
    2728import javax.swing.JComponent;
    2829import javax.swing.JDialog;
    2930import javax.swing.JLabel;
    30 import javax.swing.JOptionPane;
    3131import javax.swing.JPanel;
     32import javax.swing.JSplitPane;
    3233import javax.swing.JTabbedPane;
    3334import javax.swing.KeyStroke;
    34 import javax.swing.event.ChangeListener;
    3535
    3636import org.openstreetmap.josm.Main;
    3737import org.openstreetmap.josm.actions.ExpertToggleAction;
     
    4949import org.openstreetmap.josm.tools.ImageProvider;
    5050import org.openstreetmap.josm.tools.InputMapUtils;
    5151import org.openstreetmap.josm.tools.OsmUrlToBounds;
    52 import org.openstreetmap.josm.tools.Utils;
    5352import org.openstreetmap.josm.tools.WindowGeometry;
    5453
    5554/**
    56  * Dialog displayed to download OSM and/or GPS data from OSM server.
     55 * Dialog displayed to the user to download mapping data.
    5756 */
    5857public class DownloadDialog extends JDialog {
     58
     59    /**
     60     * Preference properties
     61     */
    5962    private static final IntegerProperty DOWNLOAD_TAB = new IntegerProperty("download.tab", 0);
    60 
     63    private static final IntegerProperty DOWNLOAD_SOURCE_TAB = new IntegerProperty("download-source.tab", 0);
     64    private static final IntegerProperty DIALOG_SPLIT = new IntegerProperty("download.split", 200);
    6165    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);
    6566    private static final BooleanProperty DOWNLOAD_NEWLAYER = new BooleanProperty("download.newlayer", false);
    6667    private static final BooleanProperty DOWNLOAD_ZOOMTODATA = new BooleanProperty("download.zoomtodata", true);
    6768
     
    8081        return instance;
    8182    }
    8283
    83     protected SlippyMapChooser slippyMapChooser;
     84    protected final transient List<DownloadSource> downloadSources = new ArrayList<>();
    8485    protected final transient List<DownloadSelection> downloadSelections = new ArrayList<>();
    8586    protected final JTabbedPane tpDownloadAreaSelectors = new JTabbedPane();
     87    protected final JTabbedPane downloadSourcesTab = new JTabbedPane();
     88
    8689    protected JCheckBox cbNewLayer;
    8790    protected JCheckBox cbStartup;
    8891    protected JCheckBox cbZoomToDownloadedData;
    89     protected final JLabel sizeCheck = new JLabel();
     92    protected SlippyMapChooser slippyMapChooser;
     93    protected JPanel mainPanel;
     94    protected JSplitPane dialogSplit;
     95
     96    /*
     97     * Keep the reference globally to avoid having it garbage collected
     98     */
     99    protected final transient ExpertToggleAction.ExpertModeChangeListener expertListener =
     100            getExpertModeListenerForDownloadSources();
    90101    protected transient Bounds currentBounds;
    91102    protected boolean canceled;
    92103
    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);
     104    protected JButton btnDownload;
     105    protected JButton btnCancel;
     106    protected JButton btnHelp;
    99107
     108    /**
     109     * Builds the main panel of the dialog.
     110     * @return The panel of the dialog.
     111     */
    100112    protected final JPanel buildMainPanel() {
    101         JPanel pnl = new JPanel(new GridBagLayout());
     113        mainPanel = new JPanel(new GridBagLayout());
    102114
    103         // size check depends on selected data source
    104         final ChangeListener checkboxChangeListener = e -> updateSizeCheck();
     115        downloadSources.add(new OSMDownloadSource());
     116        downloadSources.add(new OverpassDownloadSource());
    105117
    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));
     118        // register all default download sources
     119        for (int i = 0; i < downloadSources.size(); i++) {
     120            downloadSources.get(i).addGui(this);
     121        }
    120122
    121123        // must be created before hook
    122124        slippyMapChooser = new SlippyMapChooser();
    123125
    124         // hook for subclasses
    125         buildMainPanelAboveDownloadSelections(pnl);
    126 
    127126        // predefined download selections
    128127        downloadSelections.add(slippyMapChooser);
    129128        downloadSelections.add(new BookmarkSelection());
     
    134133        // add selections from plugins
    135134        PluginHandler.addDownloadSelection(downloadSelections);
    136135
    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);
     136        // register all default download selections
     137        for (int i = 0; i < downloadSelections.size(); i++) {
     138            downloadSelections.get(i).addGui(this);
    142139        }
    143140
    144         pnl.add(tpDownloadAreaSelectors, GBC.eol().fill());
     141        // allow to collapse the panes completely
     142        downloadSourcesTab.setMinimumSize(new Dimension(0, 0));
     143        tpDownloadAreaSelectors.setMinimumSize(new Dimension(0, 0));
    145144
    146         try {
    147             tpDownloadAreaSelectors.setSelectedIndex(DOWNLOAD_TAB.get());
    148         } catch (IndexOutOfBoundsException ex) {
    149             Main.trace(ex);
    150             DOWNLOAD_TAB.put(0);
    151         }
     145        dialogSplit = new JSplitPane(
     146                JSplitPane.VERTICAL_SPLIT,
     147                downloadSourcesTab,
     148                tpDownloadAreaSelectors);
    152149
    153         Font labelFont = sizeCheck.getFont();
    154         sizeCheck.setFont(labelFont.deriveFont(Font.PLAIN, labelFont.getSize()));
     150        mainPanel.add(dialogSplit, GBC.eol().fill());
    155151
    156152        cbNewLayer = new JCheckBox(tr("Download as new layer"));
    157153        cbNewLayer.setToolTipText(tr("<html>Select to download data into a new data layer.<br>"
     
    166162        cbZoomToDownloadedData = new JCheckBox(tr("Zoom to downloaded data"));
    167163        cbZoomToDownloadedData.setToolTipText(tr("Select to zoom to entire newly downloaded data."));
    168164
    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));
     165        mainPanel.add(cbNewLayer, GBC.std().anchor(GBC.WEST).insets(5, 5, 5, 5));
     166        mainPanel.add(cbStartup, GBC.std().anchor(GBC.WEST).insets(15, 5, 5, 5));
     167        mainPanel.add(cbZoomToDownloadedData, GBC.std().anchor(GBC.WEST).insets(15, 5, 5, 5));
    172168
    173169        ExpertToggleAction.addVisibilitySwitcher(cbZoomToDownloadedData);
    174170
    175         pnl.add(sizeCheck, GBC.eol().anchor(GBC.EAST).insets(5, 5, 5, 2));
    176 
    177171        if (!ExpertToggleAction.isExpert()) {
    178172            JLabel infoLabel = new JLabel(
    179173                    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));
     174            mainPanel.add(infoLabel, GBC.eol().anchor(GBC.SOUTH).insets(0, 0, 0, 0));
    181175        }
    182         return pnl;
     176        return mainPanel;
    183177    }
    184178
    185179    /* This should not be necessary, but if not here, repaint is not always correct in SlippyMap! */
     
    189183        super.paint(g);
    190184    }
    191185
     186    /**
     187     * Builds the button pane of the dialog.
     188     * @return The button panel of the dialog.
     189     */
    192190    protected final JPanel buildButtonPanel() {
     191        btnDownload = new JButton(new DownloadAction());
     192        btnCancel = new JButton(new CancelAction());
     193        btnHelp = new JButton(
     194                new ContextSensitiveHelpAction(getRootPane().getClientProperty("help").toString()));
     195
    193196        JPanel pnl = new JPanel(new FlowLayout());
    194197
    195         // -- download button
    196198        pnl.add(btnDownload);
     199        pnl.add(btnCancel);
     200        pnl.add(btnHelp);
     201
    197202        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);
    211203        InputMapUtils.enableEnter(btnCancel);
     204        InputMapUtils.addEscapeAction(getRootPane(), btnCancel.getAction());
     205        InputMapUtils.enableEnter(btnHelp);
    212206
    213         // -- cancel on ESC
    214         InputMapUtils.addEscapeAction(getRootPane(), actCancel);
     207        InputMapUtils.addEnterActionWhenAncestor(cbNewLayer, btnDownload.getAction());
     208        InputMapUtils.addEnterActionWhenAncestor(cbStartup, btnDownload.getAction());
     209        InputMapUtils.addEnterActionWhenAncestor(cbZoomToDownloadedData, btnDownload.getAction());
    215210
    216         // -- help button
    217         JButton btnHelp = new JButton(new ContextSensitiveHelpAction(getRootPane().getClientProperty("help").toString()));
    218         pnl.add(btnHelp);
    219         InputMapUtils.enableEnter(btnHelp);
    220 
    221211        return pnl;
    222212    }
    223213
     
    258248            }
    259249        });
    260250        addWindowListener(new WindowEventHandler());
     251        ExpertToggleAction.addExpertModeChangeListener(expertListener);
    261252        restoreSettings();
    262253    }
    263254
    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 
    289255    /**
    290256     * Distributes a "bounding box changed" from one DownloadSelection
    291      * object to the others, so they may update or clear their input fields.
     257     * object to the others, so they may update or clear their input fields. Also informs
     258     * download sources about the change, so they can react on it.
    292259     * @param b new current bounds
    293260     *
    294261     * @param eventSource - the DownloadSelection object that fired this notification.
     
    300267                s.setDownloadArea(currentBounds);
    301268            }
    302269        }
    303         updateSizeCheck();
     270
     271        for (Component ds : downloadSourcesTab.getComponents()) {
     272            if (ds instanceof AbstractDownloadSourcePanel) {
     273                ((AbstractDownloadSourcePanel) ds).boudingBoxChanged(b);
     274            }
     275        }
    304276    }
    305277
    306278    /**
     
    309281     */
    310282    public void startDownload(Bounds b) {
    311283        this.currentBounds = b;
    312         actDownload.run();
     284        startDownload();
    313285    }
    314286
    315287    /**
    316      * Replies true if the user selected to download OSM data
    317      *
    318      * @return true if the user selected to download OSM data
     288     * Starts download.
    319289     */
    320     public boolean isDownloadOsmData() {
    321         return cbDownloadOsmData.isSelected();
     290    public void startDownload() {
     291        btnDownload.doClick();
    322292    }
    323293
    324294    /**
    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     /**
    343295     * Replies true if the user requires to download into a new layer
    344296     *
    345297     * @return true if the user requires to download into a new layer
     
    359311    }
    360312
    361313    /**
     314     * Determines if the dialog autorun is enabled in preferences.
     315     * @return {@code true} if the download dialog must be open at startup, {@code false} otherwise
     316     */
     317    public static boolean isAutorunEnabled() {
     318        return DOWNLOAD_AUTORUN.get();
     319    }
     320
     321    /**
    362322     * Adds a new download area selector to the download dialog
    363323     *
    364324     * @param selector the download are selector
     
    369329    }
    370330
    371331    /**
     332     * Adds a new download source to the download dialog
     333     *
     334     * @param downloadSource The download source to be added.
     335     * @param <T> The type of the download data.
     336     */
     337    public <T> void addDownloadSource(DownloadSource<T> downloadSource) {
     338        if ((ExpertToggleAction.isExpert() && downloadSource.onlyExpert()) || !downloadSource.onlyExpert()) {
     339            addNewDownloadSourceTab(downloadSource);
     340        }
     341    }
     342
     343    /**
    372344     * Refreshes the tile sources
    373345     * @since 6364
    374346     */
     
    383355     */
    384356    public void rememberSettings() {
    385357        DOWNLOAD_TAB.put(tpDownloadAreaSelectors.getSelectedIndex());
    386         DOWNLOAD_OSM.put(cbDownloadOsmData.isSelected());
    387         DOWNLOAD_GPS.put(cbDownloadGpxData.isSelected());
    388         DOWNLOAD_NOTES.put(cbDownloadNotes.isSelected());
     358        DOWNLOAD_SOURCE_TAB.put(downloadSourcesTab.getSelectedIndex());
     359        DIALOG_SPLIT.put(dialogSplit.getDividerLocation());
    389360        DOWNLOAD_NEWLAYER.put(cbNewLayer.isSelected());
    390361        DOWNLOAD_ZOOMTODATA.put(cbZoomToDownloadedData.isSelected());
    391362        if (currentBounds != null) {
     
    397368     * Restores the previous settings in the download dialog.
    398369     */
    399370    public void restoreSettings() {
    400         cbDownloadOsmData.setSelected(DOWNLOAD_OSM.get());
    401         cbDownloadGpxData.setSelected(DOWNLOAD_GPS.get());
    402         cbDownloadNotes.setSelected(DOWNLOAD_NOTES.get());
    403371        cbNewLayer.setSelected(DOWNLOAD_NEWLAYER.get());
    404372        cbStartup.setSelected(isAutorunEnabled());
    405373        cbZoomToDownloadedData.setSelected(DOWNLOAD_ZOOMTODATA.get());
    406         int idx = Utils.clamp(DOWNLOAD_TAB.get(), 0, tpDownloadAreaSelectors.getTabCount() - 1);
    407         tpDownloadAreaSelectors.setSelectedIndex(idx);
     374        dialogSplit.setDividerLocation(DIALOG_SPLIT.get());
    408375
     376        try {
     377            tpDownloadAreaSelectors.setSelectedIndex(DOWNLOAD_TAB.get());
     378        } catch (IndexOutOfBoundsException e) {
     379            Main.trace(e);
     380            tpDownloadAreaSelectors.setSelectedIndex(0);
     381        }
     382
     383        try {
     384            downloadSourcesTab.setSelectedIndex(DOWNLOAD_SOURCE_TAB.get());
     385        } catch (IndexOutOfBoundsException e) {
     386            Main.trace(e);
     387            downloadSourcesTab.setSelectedIndex(0);
     388        }
     389
    409390        if (Main.isDisplayingMapView()) {
    410391            MapView mv = Main.map.mapView;
    411392            currentBounds = new Bounds(
     
    440421    }
    441422
    442423    /**
    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     /**
    451424     * Automatically opens the download dialog, if autorun is enabled.
    452425     * @see #isAutorunEnabled
    453426     */
     
    491464        return canceled;
    492465    }
    493466
     467    /**
     468     * Gets the global settings of the download dialog.
     469     * @return The {@link DownloadSettings} object that describes the current state of
     470     * the download dialog.
     471     */
     472    public DownloadSettings getDownloadSettings() {
     473        return new DownloadSettings(isNewLayerRequired(), isZoomToDownloadedDataRequired());
     474    }
     475
    494476    protected void setCanceled(boolean canceled) {
    495477        this.canceled = canceled;
    496478    }
    497479
    498     protected void buildMainPanelAboveDownloadSelections(JPanel pnl) {
    499         // Do nothing
     480    /**
     481     * Returns position of the download source in the tabbed pane.
     482     * @param downloadSource The download source.
     483     * @return The index of the download source, or -1 if it not in the pane.
     484     */
     485    protected int getDownloadSourceIndex(DownloadSource downloadSource) {
     486        return Arrays.stream(downloadSourcesTab.getComponents())
     487                .filter(it -> it instanceof AbstractDownloadSourcePanel)
     488                .map(it -> (AbstractDownloadSourcePanel) it)
     489                .filter(it -> it.getDownloadSource().equals(downloadSource))
     490                .findAny()
     491                .map(downloadSourcesTab::indexOfComponent)
     492                .orElse(-1);
    500493    }
    501494
     495    /**
     496     * Adds the download source to the download sources tab.
     497     * @param downloadSource The download source to be added.
     498     * @param <T> The type of the download data.
     499     */
     500    private <T> void addNewDownloadSourceTab(DownloadSource<T> downloadSource) {
     501        AbstractDownloadSourcePanel<T> panel = downloadSource.createPanel();
     502        downloadSourcesTab.add(panel, downloadSource.getLabel());
     503        Icon icon = panel.getIcon();
     504        if (icon != null) {
     505            int idx = getDownloadSourceIndex(downloadSource);
     506            downloadSourcesTab.setIconAt(
     507                    idx != -1 ? idx : downloadSourcesTab.getTabCount() - 1,
     508                    icon);
     509        }
     510    }
     511
     512    /**
     513     * Creates listener that removes/adds download sources from/to {@code downloadSourcesTab}
     514     * depending on the current mode.
     515     * @return The expert mode listener.
     516     */
     517    private ExpertToggleAction.ExpertModeChangeListener getExpertModeListenerForDownloadSources() {
     518        return isExpert -> {
     519            if (isExpert) {
     520                downloadSources.stream()
     521                        .filter(DownloadSource::onlyExpert)
     522                        .filter(it -> getDownloadSourceIndex(it) == -1)
     523                        .forEach(this::addNewDownloadSourceTab);
     524            } else {
     525                IntStream.range(0, downloadSourcesTab.getTabCount())
     526                        .mapToObj(downloadSourcesTab::getComponentAt)
     527                        .filter(it -> it instanceof AbstractDownloadSourcePanel)
     528                        .map(it -> (AbstractDownloadSourcePanel) it)
     529                        .filter(it -> it.getDownloadSource().onlyExpert())
     530                        .forEach(downloadSourcesTab::remove);
     531            }
     532        };
     533    }
     534
     535    /**
     536     * Action that is executed when the cancel button is pressed.
     537     */
    502538    class CancelAction extends AbstractAction {
    503539        CancelAction() {
    504540            putValue(NAME, tr("Cancel"));
     
    513549
    514550        @Override
    515551        public void actionPerformed(ActionEvent e) {
     552            AbstractDownloadSourcePanel pnl = (AbstractDownloadSourcePanel) downloadSourcesTab.getSelectedComponent();
    516553            run();
     554            pnl.checkCancel();
    517555        }
    518556    }
    519557
     558    /**
     559     * Action that is executed when the download button is pressed.
     560     */
    520561    class DownloadAction extends AbstractAction {
    521562        DownloadAction() {
    522563            putValue(NAME, tr("Download"));
     
    526567        }
    527568
    528569        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;
     570            Component panel = downloadSourcesTab.getSelectedComponent();
     571            if (panel instanceof AbstractDownloadSourcePanel) {
     572                AbstractDownloadSourcePanel pnl = (AbstractDownloadSourcePanel) panel;
     573                DownloadSettings downloadSettings = getDownloadSettings();
     574                if (pnl.checkDownload(currentBounds, downloadSettings)) {
     575                    rememberSettings();
     576                    setCanceled(false);
     577                    setVisible(false);
     578                    pnl.getDownloadSource().doDownload(currentBounds, pnl.getData(), downloadSettings);
     579                }
    547580            }
    548 
    549             setCanceled(false);
    550             setVisible(false);
    551581        }
    552582
    553583        @Override
  • src/org/openstreetmap/josm/gui/download/DownloadSettings.java

     
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.gui.download;
     3
     4/**
     5 * The global settings of {@link DownloadDialog}.
     6 */
     7public final class DownloadSettings {
     8
     9    private boolean downloadAsNewLayer;
     10    private boolean zoomToDownloadedData;
     11
     12    /**
     13     * Initializes a new instance of {@code DownloadSettings}.
     14     * @param downloadAsNewLayer The flag defining if a new layer must be created for the downloaded data.
     15     * @param zoomToDownloadedData The flag defining if the map view, see {@link SlippyMapChooser},
     16     *                             must zoom to the downloaded data.
     17     */
     18    public DownloadSettings(boolean downloadAsNewLayer, boolean zoomToDownloadedData) {
     19        this.downloadAsNewLayer = downloadAsNewLayer;
     20        this.zoomToDownloadedData = zoomToDownloadedData;
     21    }
     22
     23    /**
     24     * Gets the flag defining if a new layer must be created for the downloaded data.
     25     * @return {@code true} if a new layer must be created, {@code false} otherwise.
     26     */
     27    public boolean asNewLayer() {
     28        return this.downloadAsNewLayer;
     29    }
     30
     31    /**
     32     * Gets the flag defining if the map view must zoom to the downloaded data.
     33     * @return {@code true} if the view must zoom, {@code false} otherwise.
     34     */
     35    public boolean zoomToData() {
     36        return this.zoomToDownloadedData;
     37    }
     38}
  • src/org/openstreetmap/josm/gui/download/DownloadSource.java

     
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.gui.download;
     3
     4
     5import org.openstreetmap.josm.data.Bounds;
     6
     7/**
     8 * Defines an interface for different download sources.
     9 * @param <T> The type of the data that a download source uses.
     10 */
     11public interface DownloadSource<T> {
     12
     13    /**
     14     * Creates a panel with GUI specific for the download source.
     15     * @return Returns {@link AbstractDownloadSourcePanel}.
     16     */
     17    AbstractDownloadSourcePanel<T> createPanel();
     18
     19    /**
     20     * Downloads the data.
     21     * @param bbox The bounding box. Can be null if no bounding box selected.
     22     * @param data The required data for the download source.
     23     * @param settings The global settings of the download dialog, see {@link DownloadDialog}.
     24     */
     25    void doDownload(Bounds bbox, T data, DownloadSettings settings);
     26
     27    /**
     28     * Returns a string representation of this download source.
     29     * @return A string representation of this download source.
     30     */
     31    String getLabel();
     32
     33    /**
     34     * Add a download source to the dialog, see {@link DownloadDialog}.
     35     * @param dialog The download dialog.
     36     */
     37    void addGui(DownloadDialog dialog);
     38
     39    /**
     40     * Defines whether this download source should be visible only in the expert mode.
     41     * @return Returns {@code true} if the download source should be visible only in the
     42     * expert mode, {@code false} otherwise.
     43     */
     44    boolean onlyExpert();
     45}
  • src/org/openstreetmap/josm/gui/download/OSMDownloadSource.java

     
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.gui.download;
     3
     4import org.openstreetmap.josm.Main;
     5import org.openstreetmap.josm.actions.downloadtasks.AbstractDownloadTask;
     6import org.openstreetmap.josm.actions.downloadtasks.DownloadGpsTask;
     7import org.openstreetmap.josm.actions.downloadtasks.DownloadNotesTask;
     8import org.openstreetmap.josm.actions.downloadtasks.DownloadOsmTask;
     9import org.openstreetmap.josm.actions.downloadtasks.PostDownloadHandler;
     10import org.openstreetmap.josm.data.Bounds;
     11import org.openstreetmap.josm.data.ProjectionBounds;
     12import org.openstreetmap.josm.data.ViewportData;
     13import org.openstreetmap.josm.data.preferences.BooleanProperty;
     14import org.openstreetmap.josm.gui.util.GuiHelper;
     15import org.openstreetmap.josm.tools.GBC;
     16import org.openstreetmap.josm.tools.ImageProvider;
     17import org.openstreetmap.josm.tools.Pair;
     18
     19import javax.swing.Icon;
     20import javax.swing.JCheckBox;
     21import javax.swing.JLabel;
     22import javax.swing.JOptionPane;
     23import javax.swing.event.ChangeListener;
     24
     25import java.awt.Color;
     26import java.awt.Font;
     27import java.awt.GridBagLayout;
     28import java.util.ArrayList;
     29import java.util.List;
     30import java.util.concurrent.ExecutionException;
     31import java.util.concurrent.Future;
     32
     33import static org.openstreetmap.josm.tools.I18n.tr;
     34
     35/**
     36 * Class defines the way data is fetched from the OSM server.
     37 */
     38public class OSMDownloadSource implements DownloadSource<OSMDownloadSource.OSMDownloadData> {
     39
     40    @Override
     41    public AbstractDownloadSourcePanel<OSMDownloadData> createPanel() {
     42        return new OSMDownloadSourcePanel(this);
     43    }
     44
     45    @Override
     46    public void doDownload(Bounds bbox, OSMDownloadData data, DownloadSettings settings) {
     47        boolean zoom = settings.zoomToData();
     48        boolean newLayer = settings.asNewLayer();
     49        List<Pair<AbstractDownloadTask<?>, Future<?>>> tasks = new ArrayList<>();
     50
     51        if (data.isDownloadOSMData()) {
     52            DownloadOsmTask task = new DownloadOsmTask();
     53            task.setZoomAfterDownload(zoom && !data.isDownloadGPX() && !data.isDownloadNotes());
     54            Future<?> future = task.download(newLayer, bbox, null);
     55            Main.worker.submit(new PostDownloadHandler(task, future));
     56            if (zoom) {
     57                tasks.add(new Pair<>(task, future));
     58            }
     59        }
     60
     61        if (data.isDownloadGPX()) {
     62            DownloadGpsTask task = new DownloadGpsTask();
     63            task.setZoomAfterDownload(zoom && !data.isDownloadOSMData() && !data.isDownloadNotes());
     64            Future<?> future = task.download(newLayer, bbox, null);
     65            Main.worker.submit(new PostDownloadHandler(task, future));
     66            if (zoom) {
     67                tasks.add(new Pair<>(task, future));
     68            }
     69        }
     70
     71        if (data.isDownloadNotes()) {
     72            DownloadNotesTask task = new DownloadNotesTask();
     73            task.setZoomAfterDownload(zoom && !data.isDownloadOSMData() && !data.isDownloadGPX());
     74            Future<?> future = task.download(false, bbox, null);
     75            Main.worker.submit(new PostDownloadHandler(task, future));
     76            if (zoom) {
     77                tasks.add(new Pair<>(task, future));
     78            }
     79        }
     80
     81        if (zoom && tasks.size() > 1) {
     82            Main.worker.submit(() -> {
     83                ProjectionBounds bounds = null;
     84                // Wait for completion of download jobs
     85                for (Pair<AbstractDownloadTask<?>, Future<?>> p : tasks) {
     86                    try {
     87                        p.b.get();
     88                        ProjectionBounds b = p.a.getDownloadProjectionBounds();
     89                        if (bounds == null) {
     90                            bounds = b;
     91                        } else if (b != null) {
     92                            bounds.extend(b);
     93                        }
     94                    } catch (InterruptedException | ExecutionException ex) {
     95                        Main.warn(ex);
     96                    }
     97                }
     98                // Zoom to the larger download bounds
     99                if (Main.map != null && bounds != null) {
     100                    final ProjectionBounds pb = bounds;
     101                    GuiHelper.runInEDTAndWait(() -> Main.map.mapView.zoomTo(new ViewportData(pb)));
     102                }
     103            });
     104        }
     105    }
     106
     107    @Override
     108    public String getLabel() {
     109        return tr("Download from OSM");
     110    }
     111
     112    @Override
     113    public void addGui(DownloadDialog dialog) {
     114        dialog.addDownloadSource(this);
     115    }
     116
     117    @Override
     118    public boolean onlyExpert() {
     119        return false;
     120    }
     121
     122    /**
     123     * The GUI representation of the OSM download source.
     124     */
     125    public static class OSMDownloadSourcePanel extends AbstractDownloadSourcePanel<OSMDownloadData> {
     126
     127        private final JCheckBox cbDownloadOsmData;
     128        private final JCheckBox cbDownloadGpxData;
     129        private final JCheckBox cbDownloadNotes;
     130        private final JLabel sizeCheck = new JLabel();
     131
     132        private static final BooleanProperty DOWNLOAD_OSM = new BooleanProperty("download.osm.data", true);
     133        private static final BooleanProperty DOWNLOAD_GPS = new BooleanProperty("download.osm.gps", false);
     134        private static final BooleanProperty DOWNLOAD_NOTES = new BooleanProperty("download.osm.notes", false);
     135
     136        public OSMDownloadSourcePanel(OSMDownloadSource ds) {
     137            super(ds);
     138            setLayout(new GridBagLayout());
     139
     140            // size check depends on selected data source
     141            final ChangeListener checkboxChangeListener = e ->
     142                    DownloadDialog.getInstance().getSelectedDownloadArea().ifPresent(this::updateSizeCheck);
     143
     144            // adding the download tasks
     145            add(new JLabel(tr("Data Sources and Types:")), GBC.std().insets(5, 5, 1, 5).anchor(GBC.CENTER));
     146            cbDownloadOsmData = new JCheckBox(tr("OpenStreetMap data"), true);
     147            cbDownloadOsmData.setToolTipText(tr("Select to download OSM data in the selected download area."));
     148            cbDownloadOsmData.getModel().addChangeListener(checkboxChangeListener);
     149
     150            cbDownloadGpxData = new JCheckBox(tr("Raw GPS data"));
     151            cbDownloadGpxData.setToolTipText(tr("Select to download GPS traces in the selected download area."));
     152            cbDownloadGpxData.getModel().addChangeListener(checkboxChangeListener);
     153
     154            cbDownloadNotes = new JCheckBox(tr("Notes"));
     155            cbDownloadNotes.setToolTipText(tr("Select to download notes in the selected download area."));
     156            cbDownloadNotes.getModel().addChangeListener(checkboxChangeListener);
     157
     158            Font labelFont = sizeCheck.getFont();
     159            sizeCheck.setFont(labelFont.deriveFont(Font.PLAIN, labelFont.getSize()));
     160
     161            add(cbDownloadOsmData, GBC.std().insets(1, 5, 1, 5));
     162            add(cbDownloadGpxData, GBC.std().insets(1, 5, 1, 5));
     163            add(cbDownloadNotes, GBC.eol().insets(1, 5, 1, 5));
     164            add(sizeCheck, GBC.eol().anchor(GBC.EAST).insets(5, 5, 5, 2));
     165        }
     166
     167        @Override
     168        public OSMDownloadData getData() {
     169            return new OSMDownloadData(
     170                    isDownloadOsmData(),
     171                    isDownloadNotes(),
     172                    isDownloadGpxData());
     173        }
     174
     175        @Override
     176        public void rememberSettings() {
     177            DOWNLOAD_OSM.put(isDownloadOsmData());
     178            DOWNLOAD_GPS.put(isDownloadGpxData());
     179            DOWNLOAD_NOTES.put(isDownloadNotes());
     180        }
     181
     182        @Override
     183        public void restoreSettings() {
     184            cbDownloadOsmData.setSelected(DOWNLOAD_OSM.get());
     185            cbDownloadGpxData.setSelected(DOWNLOAD_GPS.get());
     186            cbDownloadNotes.setSelected(DOWNLOAD_NOTES.get());
     187        }
     188
     189        @Override
     190        public boolean checkDownload(Bounds bbox, DownloadSettings settings) {
     191            /*
     192             * It is mandatory to specify the area to download from OSM.
     193             */
     194            if (bbox == null) {
     195                JOptionPane.showMessageDialog(
     196                        this.getParent(),
     197                        tr("Please select a download area first."),
     198                        tr("Error"),
     199                        JOptionPane.ERROR_MESSAGE
     200                );
     201
     202                return false;
     203            }
     204
     205            /*
     206             * Checks if the user selected the type of data to download. At least one the following
     207             * must be chosen : raw osm data, gpx data, notes.
     208             * If none of those are selected, then the corresponding dialog is shown to inform the user.
     209             */
     210            if (!isDownloadOsmData() && !isDownloadGpxData() && !isDownloadNotes()) {
     211                JOptionPane.showMessageDialog(
     212                        this.getParent(),
     213                        tr("<html>Neither <strong>{0}</strong> nor <strong>{1}</strong> nor <strong>{2}</strong> is enabled.<br>"
     214                                        + "Please choose to either download OSM data, or GPX data, or Notes, or all.</html>",
     215                                cbDownloadOsmData.getText(),
     216                                cbDownloadGpxData.getText(),
     217                                cbDownloadNotes.getText()
     218                        ),
     219                        tr("Error"),
     220                        JOptionPane.ERROR_MESSAGE
     221                );
     222
     223                return false;
     224            }
     225
     226            this.rememberSettings();
     227
     228            return true;
     229        }
     230
     231        /**
     232         * Replies true if the user selected to download OSM data
     233         *
     234         * @return true if the user selected to download OSM data
     235         */
     236        public boolean isDownloadOsmData() {
     237            return cbDownloadOsmData.isSelected();
     238        }
     239
     240        /**
     241         * Replies true if the user selected to download GPX data
     242         *
     243         * @return true if the user selected to download GPX data
     244         */
     245        public boolean isDownloadGpxData() {
     246            return cbDownloadGpxData.isSelected();
     247        }
     248
     249        /**
     250         * Replies true if user selected to download notes
     251         *
     252         * @return true if user selected to download notes
     253         */
     254        public boolean isDownloadNotes() {
     255            return cbDownloadNotes.isSelected();
     256        }
     257
     258        @Override
     259        public Icon getIcon() {
     260            return ImageProvider.get("download");
     261        }
     262
     263        @Override
     264        public void boudingBoxChanged(Bounds bbox) {
     265            updateSizeCheck(bbox);
     266        }
     267
     268        private void updateSizeCheck(Bounds bbox) {
     269            boolean isAreaTooLarge = false;
     270            if (bbox == null) {
     271                sizeCheck.setText(tr("No area selected yet"));
     272                sizeCheck.setForeground(Color.darkGray);
     273            } else if (!isDownloadNotes() && !isDownloadOsmData() && !isDownloadGpxData()) {
     274                isAreaTooLarge = false;
     275            } else if (isDownloadNotes() && !isDownloadOsmData() && !isDownloadGpxData()) {
     276                // see max_note_request_area in https://github.com/openstreetmap/openstreetmap-website/blob/master/config/example.application.yml
     277                isAreaTooLarge = bbox.getArea() > Main.pref.getDouble("osm-server.max-request-area-notes", 25);
     278            } else {
     279                // see max_request_area in https://github.com/openstreetmap/openstreetmap-website/blob/master/config/example.application.yml
     280                isAreaTooLarge = bbox.getArea() > Main.pref.getDouble("osm-server.max-request-area", 0.25);
     281            }
     282
     283            displaySizeCheckResult(isAreaTooLarge);
     284        }
     285
     286        private void displaySizeCheckResult(boolean isAreaTooLarge) {
     287            if (isAreaTooLarge) {
     288                sizeCheck.setText(tr("Download area too large; will probably be rejected by server"));
     289                sizeCheck.setForeground(Color.red);
     290            } else {
     291                sizeCheck.setText(tr("Download area ok, size probably acceptable to server"));
     292                sizeCheck.setForeground(Color.darkGray);
     293            }
     294        }
     295
     296    }
     297
     298    /**
     299     * Encapsulates data that is required to download from the OSM server.
     300     */
     301    static class OSMDownloadData {
     302        private boolean downloadOSMData;
     303        private boolean downloadNotes;
     304        private boolean downloadGPX;
     305
     306        OSMDownloadData(boolean downloadOSMData, boolean downloadNotes, boolean downloadGPX) {
     307            this.downloadOSMData = downloadOSMData;
     308            this.downloadNotes = downloadNotes;
     309            this.downloadGPX = downloadGPX;
     310        }
     311
     312        boolean isDownloadOSMData() {
     313            return downloadOSMData;
     314        }
     315
     316        boolean isDownloadNotes() {
     317            return downloadNotes;
     318        }
     319
     320        boolean isDownloadGPX() {
     321            return downloadGPX;
     322        }
     323    }
     324}
  • src/org/openstreetmap/josm/gui/download/OverpassDownloadSource.java

     
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.gui.download;
     3
     4import org.openstreetmap.josm.Main;
     5import org.openstreetmap.josm.actions.downloadtasks.DownloadOsmTask;
     6import org.openstreetmap.josm.actions.downloadtasks.PostDownloadHandler;
     7import org.openstreetmap.josm.data.Bounds;
     8import org.openstreetmap.josm.data.preferences.BooleanProperty;
     9import org.openstreetmap.josm.gui.ConditionalOptionPaneUtil;
     10import org.openstreetmap.josm.gui.preferences.server.OverpassServerPreference;
     11import org.openstreetmap.josm.gui.util.GuiHelper;
     12import org.openstreetmap.josm.gui.widgets.JosmTextArea;
     13import org.openstreetmap.josm.io.OverpassDownloadReader;
     14import org.openstreetmap.josm.tools.GBC;
     15import org.openstreetmap.josm.tools.ImageProvider;
     16
     17import javax.swing.AbstractAction;
     18import javax.swing.Action;
     19import javax.swing.Icon;
     20import javax.swing.JButton;
     21import javax.swing.JLabel;
     22import javax.swing.JOptionPane;
     23import javax.swing.JPanel;
     24import javax.swing.JScrollPane;
     25import javax.swing.event.ListSelectionEvent;
     26import javax.swing.event.ListSelectionListener;
     27import javax.swing.plaf.basic.BasicArrowButton;
     28import java.awt.BorderLayout;
     29import java.awt.Dimension;
     30import java.awt.GridBagLayout;
     31import java.awt.event.ActionEvent;
     32import java.awt.event.FocusEvent;
     33import java.awt.event.FocusListener;
     34import java.util.Collection;
     35import java.util.concurrent.Future;
     36import java.util.function.Consumer;
     37
     38import static org.openstreetmap.josm.tools.I18n.tr;
     39
     40/**
     41 * Class defines the way data is fetched from Overpass API.
     42 */
     43public class OverpassDownloadSource implements DownloadSource<OverpassDownloadSource.OverpassDownloadData> {
     44
     45    @Override
     46    public AbstractDownloadSourcePanel<OverpassDownloadData> createPanel() {
     47        return new OverpassDownloadSourcePanel(this);
     48    }
     49
     50    @Override
     51    public void doDownload(Bounds bbox, OverpassDownloadData data, DownloadSettings settings) {
     52        /*
     53         * In order to support queries generated by the Overpass Turbo Query Wizard tool
     54         * which do not require the area to be specified.
     55         */
     56        Bounds area = bbox != null ? bbox : new Bounds(0, 0, 0, 0);
     57        DownloadOsmTask task = new DownloadOsmTask();
     58        task.setZoomAfterDownload(settings.zoomToData());
     59        Future<?> future = task.download(
     60                new OverpassDownloadReader(area, OverpassServerPreference.getOverpassServer(), data.getQuery()),
     61                settings.asNewLayer(), area, null);
     62        Main.worker.submit(new PostDownloadHandler(task, future, data.getErrorReporter()));
     63    }
     64
     65    @Override
     66    public String getLabel() {
     67        return tr("Download from Overpass API");
     68    }
     69
     70    @Override
     71    public void addGui(DownloadDialog dialog) {
     72        dialog.addDownloadSource(this);
     73    }
     74
     75    @Override
     76    public boolean onlyExpert() {
     77        return true;
     78    }
     79
     80    /**
     81     * The GUI representation of the Overpass download source.
     82     */
     83    public static class OverpassDownloadSourcePanel extends AbstractDownloadSourcePanel<OverpassDownloadData> {
     84
     85        private JosmTextArea overpassQuery;
     86        private OverpassQueryList overpassQueryList;
     87
     88        private static final BooleanProperty OVERPASS_QUERY_LIST_OPENED =
     89                new BooleanProperty("download.overpass.query-list.opened", false);
     90        private static final String ACTION_IMG_SUBDIR = "dialogs";
     91
     92        public OverpassDownloadSourcePanel(OverpassDownloadSource ds) {
     93            super(ds);
     94            setLayout(new BorderLayout());
     95
     96            String tooltip = tr("Build an Overpass query using the Overpass Turbo Query Wizard tool");
     97            Action queryWizardAction = new AbstractAction() {
     98                @Override
     99                public void actionPerformed(ActionEvent e) {
     100                    new OverpassQueryWizardDialog(OverpassDownloadSourcePanel.this).showDialog();
     101                }
     102            };
     103
     104            JButton openQueryWizard = new JButton(tr("Query Wizard"));
     105            openQueryWizard.setToolTipText(tooltip);
     106            openQueryWizard.addActionListener(queryWizardAction);
     107           
     108            // CHECKSTYLE.OFF: LineLength
     109            this.overpassQuery = new JosmTextArea(
     110                    "/*\n" +
     111                            tr("Place your Overpass query below or generate one using the Overpass Turbo Query Wizard")
     112                            + "\n*/",
     113                    8, 80);
     114            // CHECKSTYLE.ON: LineLength
     115            this.overpassQuery.setFont(GuiHelper.getMonospacedFont(overpassQuery));
     116            this.overpassQuery.addFocusListener(new FocusListener() {
     117                @Override
     118                public void focusGained(FocusEvent e) {
     119                    overpassQuery.selectAll();
     120                }
     121
     122                @Override
     123                public void focusLost(FocusEvent e) {
     124                    // ignored
     125                }
     126            });
     127
     128
     129            this.overpassQueryList = new OverpassQueryList(this, this.overpassQuery);
     130            this.overpassQueryList.setPreferredSize(new Dimension(350, 300));
     131
     132            EditSnippetAction edit = new EditSnippetAction();
     133            RemoveSnippetAction remove = new RemoveSnippetAction();
     134            this.overpassQueryList.addSelectionListener(edit);
     135            this.overpassQueryList.addSelectionListener(remove);
     136
     137            JPanel listPanel = new JPanel(new GridBagLayout());
     138            listPanel.add(new JLabel(tr("Your saved queries:")), GBC.eol().insets(2).anchor(GBC.CENTER));
     139            listPanel.add(this.overpassQueryList, GBC.eol().fill(GBC.BOTH));
     140            listPanel.add(new JButton(new AddSnippetAction()), GBC.std().fill(GBC.HORIZONTAL));
     141            listPanel.add(new JButton(edit), GBC.std().fill(GBC.HORIZONTAL));
     142            listPanel.add(new JButton(remove), GBC.std().fill(GBC.HORIZONTAL));
     143            listPanel.setVisible(OVERPASS_QUERY_LIST_OPENED.get());
     144
     145            JScrollPane scrollPane = new JScrollPane(overpassQuery);
     146            BasicArrowButton arrowButton = new BasicArrowButton(listPanel.isVisible()
     147                    ? BasicArrowButton.EAST
     148                    : BasicArrowButton.WEST);
     149            arrowButton.setToolTipText(tr("Show/hide Overpass snippet list"));
     150            arrowButton.addActionListener(e -> {
     151                if (listPanel.isVisible()) {
     152                    listPanel.setVisible(false);
     153                    arrowButton.setDirection(BasicArrowButton.WEST);
     154                    OVERPASS_QUERY_LIST_OPENED.put(Boolean.FALSE);
     155                } else {
     156                    listPanel.setVisible(true);
     157                    arrowButton.setDirection(BasicArrowButton.EAST);
     158                    OVERPASS_QUERY_LIST_OPENED.put(Boolean.TRUE);
     159                }
     160            });
     161
     162            JPanel innerPanel = new JPanel(new BorderLayout());
     163            innerPanel.add(scrollPane, BorderLayout.CENTER);
     164            innerPanel.add(arrowButton, BorderLayout.EAST);
     165
     166            JPanel leftPanel = new JPanel(new GridBagLayout());
     167            leftPanel.add(new JLabel(tr("Overpass query:")), GBC.eol().insets(5, 1, 5, 1).anchor(GBC.NORTHWEST));
     168            leftPanel.add(new JLabel(), GBC.eol().fill(GBC.VERTICAL));
     169            leftPanel.add(openQueryWizard, GBC.eol().anchor(GBC.CENTER));
     170            leftPanel.add(new JLabel(), GBC.eol().fill(GBC.VERTICAL));
     171
     172            add(leftPanel, BorderLayout.WEST);
     173            add(innerPanel, BorderLayout.CENTER);
     174            add(listPanel, BorderLayout.EAST);
     175        }
     176
     177        @Override
     178        public OverpassDownloadData getData() {
     179            String query = overpassQuery.getText();
     180            /*
     181             * A callback that is passed to PostDownloadReporter that is called once the download task
     182             * has finished. According to the number of errors happened, their type we decide whether we
     183             * want to save the last query in OverpassQueryList.
     184             */
     185            Consumer<Collection<Object>> errorReporter = errors -> {
     186
     187                boolean onlyNoDataError = errors.size() == 1 &&
     188                        errors.contains("No data found in this area.");
     189
     190                if (errors.isEmpty() || onlyNoDataError) {
     191                    overpassQueryList.saveHistoricItem(query);
     192                }
     193            };
     194
     195            return new OverpassDownloadData(query, errorReporter);
     196        }
     197
     198        @Override
     199        public void rememberSettings() {
     200            // nothing
     201        }
     202
     203        @Override
     204        public void restoreSettings() {
     205            // nothing
     206        }
     207
     208        @Override
     209        public boolean checkDownload(Bounds bbox, DownloadSettings settings) {
     210            String query = getData().getQuery();
     211
     212            /*
     213             * Absence of the selected area can be justified only if the overpass query
     214             * is not restricted to bbox.
     215             */
     216            if (bbox == null && query.contains("{{bbox}}")) {
     217                JOptionPane.showMessageDialog(
     218                        this.getParent(),
     219                        tr("Please select a download area first."),
     220                        tr("Error"),
     221                        JOptionPane.ERROR_MESSAGE
     222                );
     223                return false;
     224            }
     225
     226            /*
     227             * Check for an empty query. User might want to download everything, if so validation is passed,
     228             * otherwise return false.
     229             */
     230            if (query.matches("(/\\*(\\*[^/]|[^\\*/])*\\*/|\\s)*")) {
     231                boolean doFix = ConditionalOptionPaneUtil.showConfirmationDialog(
     232                        "download.overpass.fix.emptytoall",
     233                        this,
     234                        tr("You entered an empty query. Do you want to download all data in this area instead?"),
     235                        tr("Download all data?"),
     236                        JOptionPane.YES_NO_OPTION,
     237                        JOptionPane.QUESTION_MESSAGE,
     238                        JOptionPane.YES_OPTION);
     239                if (doFix) {
     240                    String repairedQuery = "[out:xml]; \n"
     241                            + query + "\n"
     242                            + "(\n"
     243                            + "    node({{bbox}});\n"
     244                            + "<;\n"
     245                            + ");\n"
     246                            + "(._;>;);"
     247                            + "out meta;";
     248                    this.overpassQuery.setText(repairedQuery);
     249                } else {
     250                    return false;
     251                }
     252            }
     253
     254            return true;
     255        }
     256
     257        /**
     258         * Sets query to the query text field.
     259         * @param query The query to set.
     260         */
     261        public void setOverpassQuery(String query) {
     262            this.overpassQuery.setText(query);
     263        }
     264
     265        @Override
     266        public Icon getIcon() {
     267            return ImageProvider.get("download-overpass");
     268        }
     269
     270        /**
     271         * Action that delegates snippet creation to {@link OverpassQueryList#createNewItem()}.
     272         */
     273        private class AddSnippetAction extends AbstractAction {
     274
     275            /**
     276             * Constructs a new {@code AddSnippetAction}.
     277             */
     278            AddSnippetAction() {
     279                super();
     280                putValue(SMALL_ICON, ImageProvider.get(ACTION_IMG_SUBDIR, "add"));
     281                putValue(SHORT_DESCRIPTION, tr("Add new snippet"));
     282            }
     283
     284            @Override
     285            public void actionPerformed(ActionEvent e) {
     286                overpassQueryList.createNewItem();
     287            }
     288        }
     289
     290        /**
     291         * Action that delegates snippet removal to {@link OverpassQueryList#removeSelectedItem()}.
     292         */
     293        private class RemoveSnippetAction extends AbstractAction implements ListSelectionListener {
     294
     295            /**
     296             * Constructs a new {@code RemoveSnippetAction}.
     297             */
     298            RemoveSnippetAction() {
     299                super();
     300                putValue(SMALL_ICON, ImageProvider.get(ACTION_IMG_SUBDIR, "delete"));
     301                putValue(SHORT_DESCRIPTION, tr("Delete selected snippet"));
     302                checkEnabled();
     303            }
     304
     305            @Override
     306            public void actionPerformed(ActionEvent e) {
     307                overpassQueryList.removeSelectedItem();
     308            }
     309
     310            /**
     311             * Disables the action if no items are selected.
     312             */
     313            void checkEnabled() {
     314                setEnabled(overpassQueryList.getSelectedItem().isPresent());
     315            }
     316
     317            @Override
     318            public void valueChanged(ListSelectionEvent e) {
     319                checkEnabled();
     320            }
     321        }
     322
     323        /**
     324         * Action that delegates snippet edit to {@link OverpassQueryList#editSelectedItem()}.
     325         */
     326        private class EditSnippetAction extends AbstractAction implements ListSelectionListener {
     327
     328            /**
     329             * Constructs a new {@code EditSnippetAction}.
     330             */
     331            EditSnippetAction() {
     332                super();
     333                putValue(SMALL_ICON, ImageProvider.get(ACTION_IMG_SUBDIR, "edit"));
     334                putValue(SHORT_DESCRIPTION, tr("Edit selected snippet"));
     335                checkEnabled();
     336            }
     337
     338            @Override
     339            public void actionPerformed(ActionEvent e) {
     340                overpassQueryList.editSelectedItem();
     341            }
     342
     343            /**
     344             * Disables the action if no items are selected.
     345             */
     346            void checkEnabled() {
     347                setEnabled(overpassQueryList.getSelectedItem().isPresent());
     348            }
     349
     350            @Override
     351            public void valueChanged(ListSelectionEvent e) {
     352                checkEnabled();
     353            }
     354        }
     355    }
     356
     357    /**
     358     * Encapsulates data that is required to preform download from Overpass API.
     359     */
     360    static class OverpassDownloadData {
     361        private String query;
     362        private Consumer<Collection<Object>> errorReporter;
     363
     364        OverpassDownloadData(String query, Consumer<Collection<Object>> errorReporter) {
     365            this.query = query;
     366            this.errorReporter = errorReporter;
     367        }
     368
     369        String getQuery() {
     370            return this.query;
     371        }
     372
     373        Consumer<Collection<Object>> getErrorReporter() {
     374            return this.errorReporter;
     375        }
     376    }
     377}
  • src/org/openstreetmap/josm/gui/download/OverpassQueryWizardDialog.java

     
    1919import javax.swing.text.JTextComponent;
    2020
    2121import org.openstreetmap.josm.Main;
    22 import org.openstreetmap.josm.actions.OverpassDownloadAction.OverpassDownloadDialog;
    2322import org.openstreetmap.josm.data.preferences.CollectionProperty;
    2423import org.openstreetmap.josm.gui.ExtendedDialog;
    2524import org.openstreetmap.josm.gui.util.GuiHelper;
     
    6261            + "#desc {width: 350px;}"
    6362            + "</style>\n";
    6463
    65     private final OverpassDownloadDialog parentDialog;
     64    private final OverpassDownloadSource.OverpassDownloadSourcePanel dsPanel;
    6665
    6766    /**
    6867     * Create a new {@link OverpassQueryWizardDialog}
    69      * @param parentDialog The parent this dialog should be displayed for
     68     * @param dsPanel The Overpass download source panel.
    7069     */
    71     public OverpassQueryWizardDialog(OverpassDownloadDialog parentDialog) {
    72         super(parentDialog, tr("Overpass Turbo Query Wizard"),
     70    public OverpassQueryWizardDialog(OverpassDownloadSource.OverpassDownloadSourcePanel dsPanel) {
     71        super(dsPanel.getParent(), tr("Overpass Turbo Query Wizard"),
    7372                tr("Build query"), tr("Build query and execute"), tr("Cancel"));
    74         this.parentDialog = parentDialog;
     73        this.dsPanel = dsPanel;
    7574
    7675        this.queryWizard = new HistoryComboBox();
    7776        this.overpassQueryBuilder = OverpassTurboQueryWizard.getInstance();
     
    8988
    9089        queryWizard.setPossibleItems(OVERPASS_WIZARD_HISTORY.get());
    9190
    92         setCancelButton(CANCEL);
    93         setDefaultButton(BUILD_AN_EXECUTE_QUERY + 1); // Build and execute button
     91        setCancelButton(CANCEL + 1);
     92        setDefaultButton(BUILD_AN_EXECUTE_QUERY + 1);
    9493        setContent(panel, false);
    9594    }
    9695
     
    108107                    this.saveHistory();
    109108                    super.buttonAction(BUILD_AN_EXECUTE_QUERY, evt);
    110109
    111                     parentDialog.triggerDownload();
     110                    DownloadDialog.getInstance().startDownload();
    112111                }
    113112                break;
    114113            default:
     
    141140        } catch (UncheckedParseException ex) {
    142141            Main.error(ex);
    143142            JOptionPane.showMessageDialog(
    144                     parentDialog,
     143                    dsPanel.getParent(),
    145144                    "<html>" +
    146145                     tr("The Overpass wizard could not parse the following query:") +
    147146                     Utils.joinAsHtmlUnorderedList(Collections.singleton(searchTerm)) +
     
    164163        Optional<String> q = this.tryParseSearchTerm(wizardSearchTerm);
    165164        if (q.isPresent()) {
    166165            String query = q.get();
    167             parentDialog.setOverpassQuery(query);
     166            dsPanel.setOverpassQuery(query);
    168167
    169168            return true;
    170169        }