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