001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.actions;
003
004import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
005import static org.openstreetmap.josm.tools.I18n.tr;
006
007import java.awt.BorderLayout;
008import java.awt.Component;
009import java.awt.GridLayout;
010import java.awt.Rectangle;
011import java.awt.event.ActionEvent;
012import java.awt.event.ActionListener;
013import java.awt.event.FocusEvent;
014import java.awt.event.FocusListener;
015import java.awt.event.KeyEvent;
016import java.util.ArrayList;
017import java.util.Collection;
018import java.util.Collections;
019import java.util.Deque;
020import java.util.LinkedList;
021import java.util.concurrent.Future;
022
023import javax.swing.AbstractAction;
024import javax.swing.Action;
025import javax.swing.ActionMap;
026import javax.swing.JButton;
027import javax.swing.JComponent;
028import javax.swing.JLabel;
029import javax.swing.JMenuItem;
030import javax.swing.JOptionPane;
031import javax.swing.JPanel;
032import javax.swing.JPopupMenu;
033import javax.swing.JScrollPane;
034import javax.swing.plaf.basic.BasicArrowButton;
035
036import org.openstreetmap.josm.Main;
037import org.openstreetmap.josm.actions.downloadtasks.DownloadOsmTask;
038import org.openstreetmap.josm.actions.downloadtasks.PostDownloadHandler;
039import org.openstreetmap.josm.data.Bounds;
040import org.openstreetmap.josm.data.preferences.CollectionProperty;
041import org.openstreetmap.josm.data.preferences.IntegerProperty;
042import org.openstreetmap.josm.gui.HelpAwareOptionPane;
043import org.openstreetmap.josm.gui.download.DownloadDialog;
044import org.openstreetmap.josm.gui.preferences.server.OverpassServerPreference;
045import org.openstreetmap.josm.gui.util.GuiHelper;
046import org.openstreetmap.josm.gui.widgets.HistoryComboBox;
047import org.openstreetmap.josm.gui.widgets.JosmTextArea;
048import org.openstreetmap.josm.io.OverpassDownloadReader;
049import org.openstreetmap.josm.tools.GBC;
050import org.openstreetmap.josm.tools.OverpassTurboQueryWizard;
051import org.openstreetmap.josm.tools.Shortcut;
052import org.openstreetmap.josm.tools.Utils;
053
054/**
055 * Download map data from Overpass API server.
056 * @since 8684
057 */
058public class OverpassDownloadAction extends JosmAction {
059
060    /**
061     * Constructs a new {@code OverpassDownloadAction}.
062     */
063    public OverpassDownloadAction() {
064        super(tr("Download from Overpass API ..."), "download-overpass", tr("Download map data from Overpass API server."),
065                // CHECKSTYLE.OFF: LineLength
066                Shortcut.registerShortcut("file:download-overpass", tr("File: {0}", tr("Download from Overpass API ...")), KeyEvent.VK_DOWN, Shortcut.ALT_SHIFT),
067                // CHECKSTYLE.ON: LineLength
068                true, "overpassdownload/download", true);
069        putValue("help", ht("/Action/OverpassDownload"));
070    }
071
072    @Override
073    public void actionPerformed(ActionEvent e) {
074        OverpassDownloadDialog dialog = OverpassDownloadDialog.getInstance();
075        dialog.restoreSettings();
076        dialog.setVisible(true);
077        if (!dialog.isCanceled()) {
078            dialog.rememberSettings();
079            Bounds area = dialog.getSelectedDownloadArea();
080            DownloadOsmTask task = new DownloadOsmTask();
081            Future<?> future = task.download(
082                    new OverpassDownloadReader(area, OverpassServerPreference.getOverpassServer(), dialog.getOverpassQuery()),
083                    dialog.isNewLayerRequired(), area, null);
084            Main.worker.submit(new PostDownloadHandler(task, future));
085        }
086    }
087
088    private static final class DisableActionsFocusListener implements FocusListener {
089
090        private final ActionMap actionMap;
091
092        private DisableActionsFocusListener(ActionMap actionMap) {
093            this.actionMap = actionMap;
094        }
095
096        @Override
097        public void focusGained(FocusEvent e) {
098            enableActions(false);
099        }
100
101        @Override
102        public void focusLost(FocusEvent e) {
103            enableActions(true);
104        }
105
106        private void enableActions(boolean enabled) {
107            for (Object key : actionMap.allKeys()) {
108                Action action = actionMap.get(key);
109                if (action != null) {
110                    action.setEnabled(enabled);
111                }
112            }
113        }
114    }
115
116    private static final class OverpassDownloadDialog extends DownloadDialog {
117
118        private HistoryComboBox overpassWizard;
119        private JosmTextArea overpassQuery;
120        private static OverpassDownloadDialog instance;
121        private static final CollectionProperty OVERPASS_WIZARD_HISTORY = new CollectionProperty("download.overpass.wizard",
122                new ArrayList<String>());
123
124        private OverpassDownloadDialog(Component parent) {
125            super(parent, ht("/Action/OverpassDownload"));
126            cbDownloadOsmData.setEnabled(false);
127            cbDownloadOsmData.setSelected(false);
128            cbDownloadGpxData.setVisible(false);
129            cbDownloadNotes.setVisible(false);
130            cbStartup.setVisible(false);
131        }
132
133        public static OverpassDownloadDialog getInstance() {
134            if (instance == null) {
135                instance = new OverpassDownloadDialog(Main.parent);
136            }
137            return instance;
138        }
139
140        @Override
141        protected void buildMainPanelAboveDownloadSelections(JPanel pnl) {
142
143            DisableActionsFocusListener disableActionsFocusListener =
144                    new DisableActionsFocusListener(slippyMapChooser.getNavigationComponentActionMap());
145
146            pnl.add(new JLabel(), GBC.eol()); // needed for the invisible checkboxes cbDownloadGpxData, cbDownloadNotes
147
148            final String tooltip = tr("Builds an Overpass query using the Overpass Turbo query wizard");
149            overpassWizard = new HistoryComboBox();
150            overpassWizard.setToolTipText(tooltip);
151            overpassWizard.getEditor().getEditorComponent().addFocusListener(disableActionsFocusListener);
152            final JButton buildQuery = new JButton(tr("Build query"));
153            buildQuery.addActionListener(new AbstractAction() {
154                @Override
155                public void actionPerformed(ActionEvent e) {
156                    final String overpassWizardText = overpassWizard.getText();
157                    try {
158                        overpassQuery.setText(OverpassTurboQueryWizard.getInstance().constructQuery(overpassWizardText));
159                    } catch (OverpassTurboQueryWizard.ParseException ex) {
160                        HelpAwareOptionPane.showOptionDialog(
161                                Main.parent,
162                                tr("<html>The Overpass wizard could not parse the following query:"
163                                        + Utils.joinAsHtmlUnorderedList(Collections.singleton(overpassWizardText))),
164                                tr("Parse error"),
165                                JOptionPane.ERROR_MESSAGE,
166                                null
167                        );
168                    }
169                }
170            });
171            buildQuery.setToolTipText(tooltip);
172            pnl.add(buildQuery, GBC.std().insets(5, 5, 5, 5));
173            pnl.add(overpassWizard, GBC.eol().fill(GBC.HORIZONTAL));
174
175            overpassQuery = new JosmTextArea("", 8, 80);
176            overpassQuery.setFont(GuiHelper.getMonospacedFont(overpassQuery));
177            overpassQuery.addFocusListener(disableActionsFocusListener);
178            JScrollPane scrollPane = new JScrollPane(overpassQuery);
179            final JPanel pane = new JPanel(new BorderLayout());
180            final BasicArrowButton arrowButton = new BasicArrowButton(BasicArrowButton.SOUTH);
181            arrowButton.addActionListener(new AbstractAction() {
182                @Override
183                public void actionPerformed(ActionEvent e) {
184                    OverpassQueryHistoryPopup.show(arrowButton, OverpassDownloadDialog.this);
185                }
186            });
187            pane.add(scrollPane, BorderLayout.CENTER);
188            pane.add(arrowButton, BorderLayout.EAST);
189            pnl.add(new JLabel(tr("Overpass query: ")), GBC.std().insets(5, 5, 5, 5));
190            GBC gbc = GBC.eol().fill(GBC.HORIZONTAL);
191            gbc.ipady = 200;
192            pnl.add(pane, gbc);
193
194        }
195
196        public String getOverpassQuery() {
197            return overpassQuery.getText();
198        }
199
200        public void setOverpassQuery(String text) {
201            overpassQuery.setText(text);
202        }
203
204        @Override
205        public void restoreSettings() {
206            super.restoreSettings();
207            overpassWizard.setPossibleItems(OVERPASS_WIZARD_HISTORY.get());
208        }
209
210        @Override
211        public void rememberSettings() {
212            super.rememberSettings();
213            overpassWizard.addCurrentItemToHistory();
214            OVERPASS_WIZARD_HISTORY.put(overpassWizard.getHistory());
215            OverpassQueryHistoryPopup.addToHistory(getOverpassQuery());
216        }
217
218    }
219
220    static class OverpassQueryHistoryPopup extends JPopupMenu {
221
222        static final CollectionProperty OVERPASS_QUERY_HISTORY = new CollectionProperty("download.overpass.query", new ArrayList<String>());
223        static final IntegerProperty OVERPASS_QUERY_HISTORY_SIZE = new IntegerProperty("download.overpass.query.size", 12);
224
225        OverpassQueryHistoryPopup(final OverpassDownloadDialog dialog) {
226            final Collection<String> history = OVERPASS_QUERY_HISTORY.get();
227            setLayout(new GridLayout((int) Math.ceil(history.size() / 2.), 2));
228            for (final String i : history) {
229                add(new OverpassQueryHistoryItem(i, dialog));
230            }
231        }
232
233        static void show(final JComponent parent, final OverpassDownloadDialog dialog) {
234            final OverpassQueryHistoryPopup menu = new OverpassQueryHistoryPopup(dialog);
235            final Rectangle r = parent.getBounds();
236            menu.show(parent.getParent(), r.x + r.width - (int) menu.getPreferredSize().getWidth(), r.y + r.height);
237        }
238
239        static void addToHistory(final String query) {
240            final Deque<String> history = new LinkedList<>(OVERPASS_QUERY_HISTORY.get());
241            if (!history.contains(query)) {
242                history.add(query);
243            }
244            while (history.size() > OVERPASS_QUERY_HISTORY_SIZE.get()) {
245                history.removeFirst();
246            }
247            OVERPASS_QUERY_HISTORY.put(history);
248        }
249    }
250
251    static class OverpassQueryHistoryItem extends JMenuItem implements ActionListener {
252
253        final String query;
254        final OverpassDownloadDialog dialog;
255
256        OverpassQueryHistoryItem(final String query, final OverpassDownloadDialog dialog) {
257            this.query = query;
258            this.dialog = dialog;
259            setText("<html><pre style='width:300px;'>" +
260                    Utils.escapeReservedCharactersHTML(Utils.restrictStringLines(query, 7)));
261            addActionListener(this);
262        }
263
264        @Override
265        public void actionPerformed(ActionEvent e) {
266            dialog.setOverpassQuery(query);
267        }
268    }
269
270}