001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.download;
003
004import static org.openstreetmap.josm.tools.I18n.tr;
005
006import java.awt.event.ActionEvent;
007import java.util.ArrayList;
008import java.util.Collections;
009import java.util.Optional;
010
011import javax.swing.JOptionPane;
012
013import org.openstreetmap.josm.data.osm.search.SearchSetting;
014import org.openstreetmap.josm.data.preferences.ListProperty;
015import org.openstreetmap.josm.gui.dialogs.SearchDialog;
016import org.openstreetmap.josm.gui.download.overpass.OverpassWizardRegistration.OverpassWizardCallbacks;
017import org.openstreetmap.josm.gui.tagging.ac.AutoCompComboBoxModel;
018import org.openstreetmap.josm.tools.Logging;
019import org.openstreetmap.josm.tools.SearchCompilerQueryWizard;
020import org.openstreetmap.josm.tools.UncheckedParseException;
021import org.openstreetmap.josm.tools.Utils;
022
023/**
024 * This dialog provides an easy and fast way to create an overpass query.
025 * @since 12576
026 * @since 12652: Moved here
027 */
028public final class OverpassQueryWizardDialog extends SearchDialog {
029
030    private static final ListProperty OVERPASS_WIZARD_HISTORY =
031            new ListProperty("download.overpass.wizard", new ArrayList<String>());
032    private final OverpassWizardCallbacks callbacks;
033
034    // dialog buttons
035    private static final int BUILD_QUERY = 0;
036    private static final int BUILD_AN_EXECUTE_QUERY = 1;
037    private static final int CANCEL = 2;
038
039    private AutoCompComboBoxModel<SearchSetting> model;
040
041    /** preferences reader/writer with automatic transmogrification to and from String */
042    private AutoCompComboBoxModel<SearchSetting>.Preferences prefs;
043
044    /**
045     * Create a new {@link OverpassQueryWizardDialog}
046     * @param callbacks The Overpass download source panel.
047     */
048    public OverpassQueryWizardDialog(OverpassWizardCallbacks callbacks) {
049        super(new SearchSetting(), new AutoCompComboBoxModel<>(), new PanelOptions(false, true), callbacks.getParent(),
050                tr("Overpass Query Wizard"),
051                tr("Build query"), tr("Build query and execute"), tr("Cancel"));
052        this.callbacks = callbacks;
053        model = hcbSearchString.getModel();
054        setButtonIcons("dialogs/magic-wand", "download-overpass", "cancel");
055        setCancelButton(CANCEL + 1);
056        setDefaultButton(BUILD_AN_EXECUTE_QUERY + 1);
057        prefs = model.prefs(SearchSetting::fromString, SearchSetting::toString);
058        prefs.load(OVERPASS_WIZARD_HISTORY);
059    }
060
061    @Override
062    public void buttonAction(int buttonIndex, ActionEvent evt) {
063        switch (buttonIndex) {
064            case BUILD_QUERY:
065                if (this.buildQueryAction()) {
066                    this.saveHistory();
067                    super.buttonAction(BUILD_QUERY, evt);
068                }
069                break;
070            case BUILD_AN_EXECUTE_QUERY:
071                if (this.buildQueryAction()) {
072                    this.saveHistory();
073                    super.buttonAction(BUILD_AN_EXECUTE_QUERY, evt);
074
075                    DownloadDialog.getInstance().startDownload();
076                }
077                break;
078            default:
079                super.buttonAction(buttonIndex, evt);
080        }
081    }
082
083    /**
084     * Saves the latest, successfully parsed search term.
085     */
086    private void saveHistory() {
087        Optional.ofNullable(SearchSetting.fromString(hcbSearchString.getText()))
088            .ifPresent(model::addTopElement);
089        prefs.save(OVERPASS_WIZARD_HISTORY);
090    }
091
092    /**
093     * Tries to process a search term using {@link SearchCompilerQueryWizard}. If the term cannot
094     * be parsed, the the corresponding dialog is shown.
095     * @param searchTerm The search term to parse.
096     * @return {@link Optional#empty()} if an exception was thrown when parsing, meaning
097     * that the term cannot be processed, or non-empty {@link Optional} containing the result
098     * of parsing.
099     */
100    private Optional<String> tryParseSearchTerm(String searchTerm) {
101        try {
102            return Optional.of(SearchCompilerQueryWizard.constructQuery(searchTerm));
103        } catch (UncheckedParseException | IllegalStateException ex) {
104            Logging.error(ex);
105            JOptionPane.showMessageDialog(
106                    callbacks.getParent(),
107                    "<html>" +
108                     tr("The Overpass wizard could not parse the following query:") +
109                     Utils.joinAsHtmlUnorderedList(Collections.singleton(Utils.escapeReservedCharactersHTML(searchTerm))) +
110                     "</html>",
111                    tr("Parse error"),
112                    JOptionPane.ERROR_MESSAGE
113            );
114            return Optional.empty();
115        }
116    }
117
118    /**
119     * Builds an Overpass query out from {@link SearchSetting} contents.
120     * @return {@code true} if the query successfully built, {@code false} otherwise.
121     */
122    private boolean buildQueryAction() {
123        Optional<String> q = tryParseSearchTerm(getSearchSettings().text);
124        q.ifPresent(callbacks::submitWizardResult);
125        return q.isPresent();
126    }
127}