Index: trunk/src/org/openstreetmap/josm/actions/DownloadAction.java
===================================================================
--- trunk/src/org/openstreetmap/josm/actions/DownloadAction.java	(revision 12565)
+++ trunk/src/org/openstreetmap/josm/actions/DownloadAction.java	(revision 12574)
@@ -9,6 +9,9 @@
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Optional;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Future;
+
+import javax.swing.JOptionPane;
 
 import org.openstreetmap.josm.Main;
@@ -51,60 +54,79 @@
         dialog.restoreSettings();
         dialog.setVisible(true);
-        if (!dialog.isCanceled()) {
-            dialog.rememberSettings();
-            final Bounds area = dialog.getSelectedDownloadArea();
-            final boolean zoom = dialog.isZoomToDownloadedDataRequired();
-            final List<Pair<AbstractDownloadTask<?>, Future<?>>> tasks = new ArrayList<>();
-            if (dialog.isDownloadOsmData()) {
-                DownloadOsmTask task = new DownloadOsmTask();
-                task.setZoomAfterDownload(zoom && !dialog.isDownloadGpxData() && !dialog.isDownloadNotes());
-                Future<?> future = task.download(dialog.isNewLayerRequired(), area, null);
-                Main.worker.submit(new PostDownloadHandler(task, future));
-                if (zoom) {
-                    tasks.add(new Pair<>(task, future));
+
+        if (dialog.isCanceled()) {
+            return;
+        }
+
+        dialog.rememberSettings();
+
+        Optional<Bounds> selectedArea = dialog.getSelectedDownloadArea();
+        if (!selectedArea.isPresent()) {
+            JOptionPane.showMessageDialog(
+                    dialog,
+                    tr("Please select a download area first."),
+                    tr("Error"),
+                    JOptionPane.ERROR_MESSAGE
+            );
+            return;
+        }
+
+        final Bounds area = selectedArea.get();
+        final boolean zoom = dialog.isZoomToDownloadedDataRequired();
+        final List<Pair<AbstractDownloadTask<?>, Future<?>>> tasks = new ArrayList<>();
+
+        if (dialog.isDownloadOsmData()) {
+            DownloadOsmTask task = new DownloadOsmTask();
+            task.setZoomAfterDownload(zoom && !dialog.isDownloadGpxData() && !dialog.isDownloadNotes());
+            Future<?> future = task.download(dialog.isNewLayerRequired(), area, null);
+            Main.worker.submit(new PostDownloadHandler(task, future));
+            if (zoom) {
+                tasks.add(new Pair<>(task, future));
+            }
+        }
+
+        if (dialog.isDownloadGpxData()) {
+            DownloadGpsTask task = new DownloadGpsTask();
+            task.setZoomAfterDownload(zoom && !dialog.isDownloadOsmData() && !dialog.isDownloadNotes());
+            Future<?> future = task.download(dialog.isNewLayerRequired(), area, null);
+            Main.worker.submit(new PostDownloadHandler(task, future));
+            if (zoom) {
+                tasks.add(new Pair<>(task, future));
+            }
+        }
+
+        if (dialog.isDownloadNotes()) {
+            DownloadNotesTask task = new DownloadNotesTask();
+            task.setZoomAfterDownload(zoom && !dialog.isDownloadOsmData() && !dialog.isDownloadGpxData());
+            Future<?> future = task.download(false, area, null);
+            Main.worker.submit(new PostDownloadHandler(task, future));
+            if (zoom) {
+                tasks.add(new Pair<>(task, future));
+            }
+        }
+
+        if (zoom && tasks.size() > 1) {
+            Main.worker.submit(() -> {
+                ProjectionBounds bounds = null;
+                // Wait for completion of download jobs
+                for (Pair<AbstractDownloadTask<?>, Future<?>> p : tasks) {
+                    try {
+                        p.b.get();
+                        ProjectionBounds b = p.a.getDownloadProjectionBounds();
+                        if (bounds == null) {
+                            bounds = b;
+                        } else if (b != null) {
+                            bounds.extend(b);
+                        }
+                    } catch (InterruptedException | ExecutionException ex) {
+                        Main.warn(ex);
+                    }
                 }
-            }
-            if (dialog.isDownloadGpxData()) {
-                DownloadGpsTask task = new DownloadGpsTask();
-                task.setZoomAfterDownload(zoom && !dialog.isDownloadOsmData() && !dialog.isDownloadNotes());
-                Future<?> future = task.download(dialog.isNewLayerRequired(), area, null);
-                Main.worker.submit(new PostDownloadHandler(task, future));
-                if (zoom) {
-                    tasks.add(new Pair<>(task, future));
+                // Zoom to the larger download bounds
+                if (Main.map != null && bounds != null) {
+                    final ProjectionBounds pb = bounds;
+                    GuiHelper.runInEDTAndWait(() -> Main.map.mapView.zoomTo(new ViewportData(pb)));
                 }
-            }
-            if (dialog.isDownloadNotes()) {
-                DownloadNotesTask task = new DownloadNotesTask();
-                task.setZoomAfterDownload(zoom && !dialog.isDownloadOsmData() && !dialog.isDownloadGpxData());
-                Future<?> future = task.download(false, area, null);
-                Main.worker.submit(new PostDownloadHandler(task, future));
-                if (zoom) {
-                    tasks.add(new Pair<>(task, future));
-                }
-            }
-            if (zoom && tasks.size() > 1) {
-                Main.worker.submit(() -> {
-                    ProjectionBounds bounds = null;
-                    // Wait for completion of download jobs
-                    for (Pair<AbstractDownloadTask<?>, Future<?>> p : tasks) {
-                        try {
-                            p.b.get();
-                            ProjectionBounds b = p.a.getDownloadProjectionBounds();
-                            if (bounds == null) {
-                                bounds = b;
-                            } else if (b != null) {
-                                bounds.extend(b);
-                            }
-                        } catch (InterruptedException | ExecutionException ex) {
-                            Main.warn(ex);
-                        }
-                    }
-                    // Zoom to the larger download bounds
-                    if (Main.map != null && bounds != null) {
-                        final ProjectionBounds pb = bounds;
-                        GuiHelper.runInEDTAndWait(() -> Main.map.mapView.zoomTo(new ViewportData(pb)));
-                    }
-                });
-            }
+            });
         }
     }
Index: trunk/src/org/openstreetmap/josm/actions/OverpassDownloadAction.java
===================================================================
--- trunk/src/org/openstreetmap/josm/actions/OverpassDownloadAction.java	(revision 12565)
+++ trunk/src/org/openstreetmap/josm/actions/OverpassDownloadAction.java	(revision 12574)
@@ -7,17 +7,17 @@
 import java.awt.BorderLayout;
 import java.awt.Component;
-import java.awt.GridLayout;
-import java.awt.Rectangle;
+import java.awt.Dimension;
+import java.awt.GridBagLayout;
 import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
 import java.awt.event.FocusEvent;
 import java.awt.event.FocusListener;
 import java.awt.event.KeyEvent;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.Deque;
-import java.util.LinkedList;
+import java.util.Optional;
 import java.util.concurrent.Future;
+import java.util.function.Consumer;
 
 import javax.swing.AbstractAction;
@@ -25,12 +25,12 @@
 import javax.swing.ActionMap;
 import javax.swing.JButton;
-import javax.swing.JComponent;
+import javax.swing.JEditorPane;
 import javax.swing.JLabel;
-import javax.swing.JMenuItem;
 import javax.swing.JOptionPane;
 import javax.swing.JPanel;
-import javax.swing.JPopupMenu;
 import javax.swing.JScrollPane;
+import javax.swing.event.HyperlinkEvent;
 import javax.swing.plaf.basic.BasicArrowButton;
+import javax.swing.text.JTextComponent;
 
 import org.openstreetmap.josm.Main;
@@ -38,8 +38,9 @@
 import org.openstreetmap.josm.actions.downloadtasks.PostDownloadHandler;
 import org.openstreetmap.josm.data.Bounds;
+import org.openstreetmap.josm.data.preferences.BooleanProperty;
 import org.openstreetmap.josm.data.preferences.CollectionProperty;
-import org.openstreetmap.josm.data.preferences.IntegerProperty;
-import org.openstreetmap.josm.gui.HelpAwareOptionPane;
+import org.openstreetmap.josm.gui.ExtendedDialog;
 import org.openstreetmap.josm.gui.download.DownloadDialog;
+import org.openstreetmap.josm.gui.download.OverpassQueryList;
 import org.openstreetmap.josm.gui.preferences.server.OverpassServerPreference;
 import org.openstreetmap.josm.gui.util.GuiHelper;
@@ -48,5 +49,5 @@
 import org.openstreetmap.josm.io.OverpassDownloadReader;
 import org.openstreetmap.josm.tools.GBC;
-import org.openstreetmap.josm.tools.InputMapUtils;
+import org.openstreetmap.josm.tools.OpenBrowser;
 import org.openstreetmap.josm.tools.OverpassTurboQueryWizard;
 import org.openstreetmap.josm.tools.Shortcut;
@@ -77,14 +78,53 @@
         dialog.restoreSettings();
         dialog.setVisible(true);
-        if (!dialog.isCanceled()) {
-            dialog.rememberSettings();
-            Bounds area = dialog.getSelectedDownloadArea();
-            DownloadOsmTask task = new DownloadOsmTask();
-            task.setZoomAfterDownload(dialog.isZoomToDownloadedDataRequired());
-            Future<?> future = task.download(
-                    new OverpassDownloadReader(area, OverpassServerPreference.getOverpassServer(), dialog.getOverpassQuery()),
-                    dialog.isNewLayerRequired(), area, null);
-            Main.worker.submit(new PostDownloadHandler(task, future));
-        }
+
+        if (dialog.isCanceled()) {
+            return;
+        }
+
+        dialog.rememberSettings();
+        Optional<Bounds> selectedArea = dialog.getSelectedDownloadArea();
+        String overpassQuery = dialog.getOverpassQuery();
+
+        /*
+         * Absence of the selected area can be justified only if the overpass query
+         * is not restricted to bbox.
+         */
+        if (!selectedArea.isPresent() && overpassQuery.contains("{{bbox}}")) {
+            JOptionPane.showMessageDialog(
+                    dialog,
+                    tr("Please select a download area first."),
+                    tr("Error"),
+                    JOptionPane.ERROR_MESSAGE
+            );
+            return;
+        }
+
+        /*
+         * A callback that is passed to PostDownloadReporter that is called once the download task
+         * has finished. According to the number of errors happened, their type we decide whether we
+         * want to save the last query in OverpassQueryList.
+         */
+        Consumer<Collection> errorReporter = (errors) -> {
+
+            boolean onlyNoDataError = errors.size() == 1 &&
+                    errors.contains("No data found in this area.");
+
+            if (errors.isEmpty() || onlyNoDataError) {
+                dialog.saveHistoricItemOnSuccess();
+            }
+        };
+
+        /*
+         * In order to support queries generated by the Overpass Turbo Query Wizard tool
+         * which do not require the area to be specified.
+         */
+        Bounds area = selectedArea.orElseGet(() -> new Bounds(0, 0, 0, 0));
+        DownloadOsmTask task = new DownloadOsmTask();
+        task.setZoomAfterDownload(dialog.isZoomToDownloadedDataRequired());
+        Future<?> future = task.download(
+                new OverpassDownloadReader(area, OverpassServerPreference.getOverpassServer(), dialog.getOverpassQuery()),
+                dialog.isNewLayerRequired(), area, null);
+        Main.worker.submit(new PostDownloadHandler(task, future, errorReporter));
     }
 
@@ -122,9 +162,9 @@
     private static final class OverpassDownloadDialog extends DownloadDialog {
 
-        private HistoryComboBox overpassWizard;
         private JosmTextArea overpassQuery;
+        private OverpassQueryList overpassQueryList;
         private static OverpassDownloadDialog instance;
-        private static final CollectionProperty OVERPASS_WIZARD_HISTORY = new CollectionProperty("download.overpass.wizard",
-                new ArrayList<String>());
+        private static final BooleanProperty OVERPASS_QUERY_LIST_OPENED =
+                new BooleanProperty("download.overpass.query-list.opened", false);
 
         private OverpassDownloadDialog(Component parent) {
@@ -146,81 +186,89 @@
         @Override
         protected void buildMainPanelAboveDownloadSelections(JPanel pnl) {
+            // needed for the invisible checkboxes cbDownloadGpxData, cbDownloadNotes
+            pnl.add(new JLabel(), GBC.eol());
 
             DisableActionsFocusListener disableActionsFocusListener =
                     new DisableActionsFocusListener(slippyMapChooser.getNavigationComponentActionMap());
 
-            pnl.add(new JLabel(), GBC.eol()); // needed for the invisible checkboxes cbDownloadGpxData, cbDownloadNotes
-
-            final String tooltip = tr("Builds an Overpass query using the Overpass Turbo query wizard");
-            overpassWizard = new HistoryComboBox();
-            overpassWizard.setToolTipText(tooltip);
-            overpassWizard.getEditorComponent().addFocusListener(disableActionsFocusListener);
-            final JButton buildQuery = new JButton(tr("Build query"));
-            final Action buildQueryAction = new AbstractAction() {
+            String tooltip = tr("Build an Overpass query using the Overpass Turbo Query Wizard tool");
+            Action queryWizardAction = new AbstractAction() {
                 @Override
                 public void actionPerformed(ActionEvent e) {
-                    final String overpassWizardText = overpassWizard.getText();
-                    try {
-                        overpassQuery.setText(OverpassTurboQueryWizard.getInstance().constructQuery(overpassWizardText));
-                    } catch (UncheckedParseException ex) {
-                        Main.error(ex);
-                        HelpAwareOptionPane.showOptionDialog(
-                                Main.parent,
-                                tr("<html>The Overpass wizard could not parse the following query:"
-                                        + Utils.joinAsHtmlUnorderedList(Collections.singleton(overpassWizardText))),
-                                tr("Parse error"),
-                                JOptionPane.ERROR_MESSAGE,
-                                null
-                        );
-                    }
+                    QueryWizardDialog.getInstance().showDialog();
                 }
             };
-            buildQuery.addActionListener(buildQueryAction);
-            buildQuery.setToolTipText(tooltip);
-            pnl.add(buildQuery, GBC.std().insets(5, 5, 5, 5));
-            pnl.add(overpassWizard, GBC.eol().fill(GBC.HORIZONTAL));
-            InputMapUtils.addEnterAction(overpassWizard.getEditorComponent(), buildQueryAction);
-
-            overpassQuery = new JosmTextArea("", 8, 80);
-            overpassQuery.setFont(GuiHelper.getMonospacedFont(overpassQuery));
-            overpassQuery.addFocusListener(disableActionsFocusListener);
+
+            JButton openQueryWizard = new JButton("Query Wizard");
+            openQueryWizard.setToolTipText(tooltip);
+            openQueryWizard.addActionListener(queryWizardAction);
+
+            // CHECKSTYLE.OFF: LineLength
+            this.overpassQuery = new JosmTextArea(
+                    "/*\n" +
+                    tr("Place your Overpass query below or generate one using the Overpass Turbo Query Wizard")
+                    + "\n*/",
+                    8, 80);
+            // CHECKSTYLE.ON: LineLength
+            this.overpassQuery.setFont(GuiHelper.getMonospacedFont(overpassQuery));
+            this.overpassQuery.addFocusListener(disableActionsFocusListener);
+            this.overpassQuery.addFocusListener(new FocusListener() {
+                @Override
+                public void focusGained(FocusEvent e) {
+                    overpassQuery.selectAll();
+                }
+
+                @Override
+                public void focusLost(FocusEvent e) {
+
+                }
+            });
+
+            this.overpassQueryList = new OverpassQueryList(this, this.overpassQuery);
+            overpassQueryList.setToolTipText(tr("Show/hide Overpass snippet list"));
+            overpassQueryList.setVisible(OVERPASS_QUERY_LIST_OPENED.get());
+            overpassQueryList.setPreferredSize(new Dimension(350, 300));
             JScrollPane scrollPane = new JScrollPane(overpassQuery);
-            final JPanel pane = new JPanel(new BorderLayout());
-            final BasicArrowButton arrowButton = new BasicArrowButton(BasicArrowButton.SOUTH);
-            arrowButton.addActionListener(new AbstractAction() {
-                @Override
-                public void actionPerformed(ActionEvent e) {
-                    OverpassQueryHistoryPopup.show(arrowButton, OverpassDownloadDialog.this);
+            BasicArrowButton arrowButton = new BasicArrowButton(overpassQueryList.isVisible()
+                ? BasicArrowButton.EAST
+                : BasicArrowButton.WEST);
+            arrowButton.addActionListener(e ->  {
+                if (overpassQueryList.isVisible()) {
+                    overpassQueryList.setVisible(false);
+                    arrowButton.setDirection(BasicArrowButton.WEST);
+                    OVERPASS_QUERY_LIST_OPENED.put(false);
+                } else {
+                    overpassQueryList.setVisible(true);
+                    arrowButton.setDirection(BasicArrowButton.EAST);
+                    OVERPASS_QUERY_LIST_OPENED.put(false);
                 }
             });
-            pane.add(scrollPane, BorderLayout.CENTER);
-            pane.add(arrowButton, BorderLayout.EAST);
-            pnl.add(new JLabel(tr("Overpass query: ")), GBC.std().insets(5, 5, 5, 5));
-            GBC gbc = GBC.eol().fill(GBC.HORIZONTAL);
-            gbc.ipady = 200;
+
+            JPanel innerPanel = new JPanel(new BorderLayout());
+            innerPanel.add(scrollPane, BorderLayout.CENTER);
+            innerPanel.add(arrowButton, BorderLayout.EAST);
+
+            JPanel pane = new JPanel(new BorderLayout());
+            pane.add(innerPanel, BorderLayout.CENTER);
+            pane.add(overpassQueryList, BorderLayout.EAST);
+
+            GBC gbc = GBC.eol().fill(GBC.HORIZONTAL); gbc.ipady = 200;
+            pnl.add(openQueryWizard, GBC.std().insets(5, 5, 5, 5));
             pnl.add(pane, gbc);
-
-        }
-
-        public String getOverpassQuery() {
+        }
+
+        String getOverpassQuery() {
             return overpassQuery.getText();
         }
 
-        public void setOverpassQuery(String text) {
+        void setOverpassQuery(String text) {
             overpassQuery.setText(text);
         }
 
-        @Override
-        public void restoreSettings() {
-            super.restoreSettings();
-            overpassWizard.setPossibleItems(OVERPASS_WIZARD_HISTORY.get());
-        }
-
-        @Override
-        public void rememberSettings() {
-            super.rememberSettings();
-            overpassWizard.addCurrentItemToHistory();
-            OVERPASS_WIZARD_HISTORY.put(overpassWizard.getHistory());
-            OverpassQueryHistoryPopup.addToHistory(getOverpassQuery());
+        /**
+         * Adds the current query to {@link OverpassQueryList}.
+         */
+        void saveHistoricItemOnSuccess() {
+            overpassQueryList.saveHistoricItem(overpassQuery.getText());
         }
 
@@ -229,55 +277,218 @@
             displaySizeCheckResult(false);
         }
+
+        /**
+         * Triggers the download action to fire.
+         */
+        private void triggerDownload() {
+            super.btnDownload.doClick();
+        }
     }
 
-    static class OverpassQueryHistoryPopup extends JPopupMenu {
-
-        static final CollectionProperty OVERPASS_QUERY_HISTORY = new CollectionProperty("download.overpass.query", new ArrayList<String>());
-        static final IntegerProperty OVERPASS_QUERY_HISTORY_SIZE = new IntegerProperty("download.overpass.query.size", 12);
-
-        OverpassQueryHistoryPopup(final OverpassDownloadDialog dialog) {
-            final Collection<String> history = OVERPASS_QUERY_HISTORY.get();
-            setLayout(new GridLayout((int) Math.ceil(history.size() / 2.), 2));
-            for (final String i : history) {
-                add(new OverpassQueryHistoryItem(i, dialog));
-            }
-        }
-
-        static void show(final JComponent parent, final OverpassDownloadDialog dialog) {
-            final OverpassQueryHistoryPopup menu = new OverpassQueryHistoryPopup(dialog);
-            final Rectangle r = parent.getBounds();
-            menu.show(parent.getParent(), r.x + r.width - (int) menu.getPreferredSize().getWidth(), r.y + r.height);
-        }
-
-        static void addToHistory(final String query) {
-            final Deque<String> history = new LinkedList<>(OVERPASS_QUERY_HISTORY.get());
-            if (!history.contains(query)) {
-                history.add(query);
-            }
-            while (history.size() > OVERPASS_QUERY_HISTORY_SIZE.get()) {
-                history.removeFirst();
-            }
-            OVERPASS_QUERY_HISTORY.put(history);
+    private static final class QueryWizardDialog extends ExtendedDialog {
+
+        private static QueryWizardDialog dialog;
+        private final HistoryComboBox queryWizard;
+        private final OverpassTurboQueryWizard overpassQueryBuilder;
+        private static final CollectionProperty OVERPASS_WIZARD_HISTORY =
+                new CollectionProperty("download.overpass.wizard", new ArrayList<String>());
+
+        // dialog buttons
+        private static final int BUILD_QUERY = 0;
+        private static final int BUILD_AN_EXECUTE_QUERY = 1;
+        private static final int CANCEL = 2;
+
+        /**
+         * Get an instance of {@link QueryWizardDialog}.
+         * @return The instance
+         */
+        public static QueryWizardDialog getInstance() {
+            if (dialog == null) {
+                dialog = new QueryWizardDialog();
+            }
+
+            return dialog;
+        }
+
+        private static final String DESCRIPTION_STYLE =
+                "<style type=\"text/css\">\n"
+                + "table { border-spacing: 0pt;}\n"
+                + "h3 {text-align: center; padding: 8px;}\n"
+                + "td {border: 1px solid #dddddd; text-align: left; padding: 8px;}\n"
+                + "#desc {width: 350px;}"
+                + "</style>\n";
+
+        private QueryWizardDialog() {
+            super(OverpassDownloadDialog.getInstance(), tr("Overpass Turbo Query Wizard"),
+                    tr("Build query"), tr("Build query and execute"), tr("Cancel"));
+
+            this.queryWizard = new HistoryComboBox();
+            this.overpassQueryBuilder = OverpassTurboQueryWizard.getInstance();
+
+            JPanel panel = new JPanel(new GridBagLayout());
+
+            JLabel searchLabel = new JLabel(tr("Search :"));
+            JTextComponent descPane = this.buildDescriptionSection();
+            JScrollPane scroll = GuiHelper.embedInVerticalScrollPane(descPane);
+            scroll.getVerticalScrollBar().setUnitIncrement(10); // make scrolling smooth
+
+            panel.add(searchLabel, GBC.std().insets(0, 0, 0, 20).anchor(GBC.SOUTHEAST));
+            panel.add(queryWizard, GBC.eol().insets(0, 0, 0, 15).fill(GBC.HORIZONTAL).anchor(GBC.SOUTH));
+            panel.add(scroll, GBC.eol().fill(GBC.BOTH).anchor(GBC.CENTER));
+
+            queryWizard.setPossibleItems(OVERPASS_WIZARD_HISTORY.get());
+
+            setCancelButton(CANCEL);
+            setDefaultButton(BUILD_AN_EXECUTE_QUERY + 1); // Build and execute button
+            setContent(panel, false);
+        }
+
+        @Override
+        public void buttonAction(int buttonIndex, ActionEvent evt) {
+            switch (buttonIndex) {
+                case BUILD_QUERY:
+                    if (this.buildQueryAction()) {
+                        this.saveHistory();
+                        super.buttonAction(BUILD_QUERY, evt);
+                    }
+                    break;
+                case BUILD_AN_EXECUTE_QUERY:
+                    if (this.buildQueryAction()) {
+                        this.saveHistory();
+                        super.buttonAction(BUILD_AN_EXECUTE_QUERY, evt);
+
+                        OverpassDownloadDialog.getInstance().triggerDownload();
+                    }
+                    break;
+                default:
+                    super.buttonAction(buttonIndex, evt);
+
+            }
+        }
+
+        /**
+         * Saves the latest, successfully parsed search term.
+         */
+        private void saveHistory() {
+            queryWizard.addCurrentItemToHistory();
+            OVERPASS_WIZARD_HISTORY.put(queryWizard.getHistory());
+        }
+
+        /**
+         * Tries to process a search term using {@link OverpassTurboQueryWizard}. If the term cannot
+         * be parsed, the the corresponding dialog is shown.
+         * @param searchTerm The search term to parse.
+         * @return {@link Optional#empty()} if an exception was thrown when parsing, meaning
+         * that the term cannot be processed, or non-empty {@link Optional} containing the result
+         * of parsing.
+         */
+        private Optional<String> tryParseSearchTerm(String searchTerm) {
+            try {
+                String query = this.overpassQueryBuilder.constructQuery(searchTerm);
+
+                return Optional.of(query);
+            } catch (UncheckedParseException ex) {
+                Main.error(ex);
+                JOptionPane.showMessageDialog(
+                        OverpassDownloadDialog.getInstance(),
+                        "<html>" +
+                         tr("The Overpass wizard could not parse the following query:") +
+                         Utils.joinAsHtmlUnorderedList(Collections.singleton(searchTerm)) +
+                         "</html>",
+                        tr("Parse error"),
+                        JOptionPane.ERROR_MESSAGE
+                );
+
+                return Optional.empty();
+            }
+        }
+
+        /**
+         * Builds an Overpass query out from {@link QueryWizardDialog#queryWizard} contents.
+         * @return {@code true} if the query successfully built, {@code false} otherwise.
+         */
+        private boolean buildQueryAction() {
+            final String wizardSearchTerm = this.queryWizard.getText();
+
+            Optional<String> q = this.tryParseSearchTerm(wizardSearchTerm);
+            if (q.isPresent()) {
+                String query = q.get();
+                OverpassDownloadDialog.getInstance().setOverpassQuery(query);
+
+                return true;
+            }
+
+            return false;
+        }
+
+        private JTextComponent buildDescriptionSection() {
+            JEditorPane descriptionSection = new JEditorPane("text/html", this.getDescriptionContent());
+            descriptionSection.setEditable(false);
+            descriptionSection.addHyperlinkListener(e -> {
+                if (HyperlinkEvent.EventType.ACTIVATED.equals(e.getEventType())) {
+                    OpenBrowser.displayUrl(e.getURL().toString());
+                }
+            });
+
+            return descriptionSection;
+        }
+
+        private String getDescriptionContent() {
+            return new StringBuilder("<html>")
+                    .append(DESCRIPTION_STYLE)
+                    .append("<body>")
+                    .append("<h3>")
+                    .append(tr("Query Wizard"))
+                    .append("</h3>")
+                    .append("<p>")
+                    .append(tr("Allows you to interact with <i>Overpass API</i> by writing declarative, human-readable terms."))
+                    .append(tr("The <i>Query Wizard</i> tool will transform those to a valid overpass query."))
+                    .append(tr("For more detailed description see "))
+                    .append(tr("<a href=\"{0}\">OSM Wiki</a>.", Main.getOSMWebsite() + "/wiki/Overpass_turbo/Wizard"))
+                    .append("</p>")
+                    .append("<h3>").append(tr("Hints")).append("</h3>")
+                    .append("<table>").append("<tr>").append("<td>")
+                    .append(Utils.joinAsHtmlUnorderedList(Arrays.asList("<i>type:node</i>", "<i>type:relation</i>", "<i>type:way</i>")))
+                    .append("</td>").append("<td>")
+                    .append("<span>").append(tr("Download objects of a certain type.")).append("</span>")
+                    .append("</td>").append("</tr>")
+                    .append("<tr>").append("<td>")
+                    .append(Utils.joinAsHtmlUnorderedList(
+                            Arrays.asList("<i>key=value in <u>location</u></i>",
+                                    "<i>key=value around <u>location</u></i>",
+                                    "<i>key=value in bbox</i>")))
+                    .append("</td>").append("<td>")
+                    .append(tr("Download object by specifying a specific location. For example,"))
+                    .append(Utils.joinAsHtmlUnorderedList(Arrays.asList(
+                            tr("{0} all objects having {1} as attribute are downloaded.", "<i>tourism=hotel in Berlin</i> -", "'tourism=hotel'"),
+                            tr("{0} all object with the corresponding key/value pair located around Berlin. Note, the default value for radius "+
+                                    "is set to 1000m, but it can be changed in the generated query.", "<i>tourism=hotel around Berlin</i> -"),
+                            tr("{0} all objects within the current selection that have {1} as attribute.", "<i>tourism=hotel in bbox</i> -",
+                                    "'tourism=hotel'"))))
+                    .append("<span>")
+                    .append(tr("Instead of <i>location</i> any valid place name can be used like address, city, etc."))
+                    .append("</span>")
+                    .append("</td>").append("</tr>")
+                    .append("<tr>").append("<td>")
+                    .append(Utils.joinAsHtmlUnorderedList(Arrays.asList("<i>key=value</i>", "<i>key=*</i>", "<i>key~regex</i>",
+                            "<i>key!=value</i>", "<i>key!~regex</i>", "<i>key=\"combined value\"</i>")))
+                    .append("</td>").append("<td>")
+                    .append(tr("<span>Download objects that have some concrete key/value pair, only the key with any contents for the value, " +
+                            "the value matching some regular expression. 'Not equal' operators are supported as well.</span>"))
+                    .append("</td>").append("</tr>")
+                    .append("<tr>").append("<td>")
+                    .append(Utils.joinAsHtmlUnorderedList(Arrays.asList(
+                            tr("<i>expression1 {0} expression2</i>", "or"),
+                            tr("<i>expression1 {0} expression2</i>", "and"))))
+                    .append("</td>").append("<td>")
+                    .append("<span>")
+                    .append(tr("Basic logical operators can be used to create more sophisticated queries. Instead of 'or' - '|', '||' " +
+                            "can be used, and instead of 'and' - '&', '&&'."))
+                    .append("</span>")
+                    .append("</td>").append("</tr>").append("</table>")
+                    .append("</body>")
+                    .append("</html>")
+                    .toString();
         }
     }
-
-    static class OverpassQueryHistoryItem extends JMenuItem implements ActionListener {
-
-        final String query;
-        final OverpassDownloadDialog dialog;
-
-        OverpassQueryHistoryItem(final String query, final OverpassDownloadDialog dialog) {
-            this.query = query;
-            this.dialog = dialog;
-            setText("<html><pre style='width:300px;'>" +
-                    Utils.escapeReservedCharactersHTML(Utils.restrictStringLines(query, 7)));
-            addActionListener(this);
-        }
-
-        @Override
-        public void actionPerformed(ActionEvent e) {
-            dialog.setOverpassQuery(query);
-        }
-    }
-
 }
Index: trunk/src/org/openstreetmap/josm/actions/downloadtasks/PostDownloadHandler.java
===================================================================
--- trunk/src/org/openstreetmap/josm/actions/downloadtasks/PostDownloadHandler.java	(revision 12565)
+++ trunk/src/org/openstreetmap/josm/actions/downloadtasks/PostDownloadHandler.java	(revision 12574)
@@ -12,4 +12,5 @@
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Future;
+import java.util.function.Consumer;
 
 import javax.swing.JOptionPane;
@@ -29,4 +30,5 @@
     private final DownloadTask task;
     private final Future<?> future;
+    private Consumer<Collection> errorReporter;
 
     /**
@@ -38,4 +40,16 @@
         this.task = task;
         this.future = future;
+    }
+
+    /**
+     * constructor
+     * @param task the asynchronous download task
+     * @param future the future on which the completion of the download task can be synchronized
+     * @param errorReporter a callback to inform about the number errors happened during the download
+     *                      task
+     */
+    public PostDownloadHandler(DownloadTask task, Future<?> future, Consumer<Collection> errorReporter) {
+        this(task, future);
+        this.errorReporter = errorReporter;
     }
 
@@ -54,6 +68,11 @@
         //
         Set<Object> errors = new LinkedHashSet<>(task.getErrorObjects());
-        if (errors.isEmpty())
+        if (this.errorReporter != null) {
+            errorReporter.accept(errors);
+        }
+
+        if (errors.isEmpty()) {
             return;
+        }
 
         // just one error object?
Index: trunk/src/org/openstreetmap/josm/gui/download/DownloadDialog.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/download/DownloadDialog.java	(revision 12565)
+++ trunk/src/org/openstreetmap/josm/gui/download/DownloadDialog.java	(revision 12574)
@@ -20,4 +20,5 @@
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Optional;
 
 import javax.swing.AbstractAction;
@@ -458,9 +459,10 @@
 
     /**
-     * Replies the currently selected download area.
-     * @return the currently selected download area. May be {@code null}, if no download area is selected yet.
-     */
-    public Bounds getSelectedDownloadArea() {
-        return currentBounds;
+     * Returns an {@link Optional} of the currently selected download area.
+     * @return An {@link Optional} of the currently selected download area.
+     * @since 12574 Return type changed to optional
+     */
+    public Optional<Bounds> getSelectedDownloadArea() {
+        return Optional.ofNullable(currentBounds);
     }
 
@@ -525,18 +527,14 @@
 
         public void run() {
-            if (currentBounds == null) {
-                JOptionPane.showMessageDialog(
-                        DownloadDialog.this,
-                        tr("Please select a download area first."),
-                        tr("Error"),
-                        JOptionPane.ERROR_MESSAGE
-                );
-                return;
-            }
+            /*
+             * Checks if the user selected the type of data to download. At least one the following
+             * must be chosen : raw osm data, gpx data, notes.
+             * If none of those are selected, then the corresponding dialog is shown to inform the user.
+             */
             if (!isDownloadOsmData() && !isDownloadGpxData() && !isDownloadNotes()) {
                 JOptionPane.showMessageDialog(
                         DownloadDialog.this,
                         tr("<html>Neither <strong>{0}</strong> nor <strong>{1}</strong> nor <strong>{2}</strong> is enabled.<br>"
-                                + "Please choose to either download OSM data, or GPX data, or Notes, or all.</html>",
+                                        + "Please choose to either download OSM data, or GPX data, or Notes, or all.</html>",
                                 cbDownloadOsmData.getText(),
                                 cbDownloadGpxData.getText(),
@@ -548,4 +546,5 @@
                 return;
             }
+
             setCanceled(false);
             setVisible(false);
Index: trunk/src/org/openstreetmap/josm/gui/download/OverpassQueryList.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/download/OverpassQueryList.java	(revision 12574)
+++ trunk/src/org/openstreetmap/josm/gui/download/OverpassQueryList.java	(revision 12574)
@@ -0,0 +1,624 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.download;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.GridBagLayout;
+import java.awt.Point;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+import javax.swing.AbstractAction;
+import javax.swing.BorderFactory;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JPopupMenu;
+import javax.swing.JScrollPane;
+import javax.swing.JTextField;
+import javax.swing.ListCellRenderer;
+import javax.swing.SwingUtilities;
+import javax.swing.border.CompoundBorder;
+import javax.swing.text.JTextComponent;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.gui.ExtendedDialog;
+import org.openstreetmap.josm.gui.util.GuiHelper;
+import org.openstreetmap.josm.gui.widgets.AbstractTextComponentValidator;
+import org.openstreetmap.josm.gui.widgets.DefaultTextComponentValidator;
+import org.openstreetmap.josm.gui.widgets.JosmTextArea;
+import org.openstreetmap.josm.gui.widgets.SearchTextResultListPanel;
+import org.openstreetmap.josm.tools.GBC;
+import org.openstreetmap.josm.tools.Utils;
+
+/**
+ * A component to select user saved Overpass queries.
+ * @since 12574
+ */
+public final class OverpassQueryList extends SearchTextResultListPanel<OverpassQueryList.SelectorItem> {
+
+    private final DateTimeFormatter format = DateTimeFormatter.ofPattern("HH:mm:ss, dd-MM-yyyy");
+
+    /*
+     * GUI elements
+     */
+    private final JTextComponent target;
+    private final Component componentParent;
+
+    /*
+     * All loaded elements within the list.
+     */
+    private final transient Map<String, SelectorItem> items;
+
+    /*
+     * Preferences
+     */
+    private static final String KEY_KEY = "key";
+    private static final String QUERY_KEY = "query";
+    private static final String USE_COUNT_KEY = "useCount";
+    private static final String PREFERENCE_ITEMS = "download.overpass.query";
+
+    /**
+     * Constructs a new {@code OverpassQueryList}.
+     * @param parent The parent of this component.
+     * @param target The text component to which the queries must be added.
+     */
+    public OverpassQueryList(Component parent, JTextComponent target) {
+        this.target = target;
+        this.componentParent = parent;
+        this.items = this.restorePreferences();
+
+        OverpassQueryListMouseAdapter mouseHandler = new OverpassQueryListMouseAdapter(lsResult, lsResultModel);
+        super.lsResult.setCellRenderer(new OverpassQueryCellRendered());
+        super.setDblClickListener(this::getDblClickListener);
+        super.lsResult.addMouseListener(mouseHandler);
+        super.lsResult.addMouseMotionListener(mouseHandler);
+
+        filterItems();
+    }
+
+    /**
+     * Returns currently selected element from the list.
+     * @return An {@link Optional#empty()} if nothing is selected, otherwise
+     * the idem is returned.
+     */
+    public synchronized Optional<SelectorItem> getSelectedItem() {
+        int idx = lsResult.getSelectedIndex();
+        if (lsResultModel.getSize() == 0 || idx == -1) {
+            return Optional.empty();
+        }
+
+        SelectorItem item = lsResultModel.getElementAt(idx);
+        item.increaseUsageCount();
+
+        this.items.values().stream()
+                .filter(it -> !it.getKey().equals(item.getKey()))
+                .forEach(SelectorItem::decreaseUsageCount);
+
+        filterItems();
+
+        return Optional.of(item);
+    }
+
+    /**
+     * Adds a new historic item to the list. The key has form 'history {current date}'.
+     * Note, the item is not saved if there is already a historic item with the same query.
+     * @param query The query of the item.
+     * @exception IllegalArgumentException if the query is empty.
+     * @exception NullPointerException if the query is {@code null}.
+     */
+    public synchronized void saveHistoricItem(String query) {
+        boolean historicExist = this.items.values().stream()
+                .filter(it -> it.getKey().contains("history"))
+                .map(SelectorItem::getQuery)
+                .anyMatch(q -> q.equals(query));
+
+        if (!historicExist) {
+            SelectorItem item = new SelectorItem(
+                    "history " + LocalDateTime.now().format(this.format),
+                    query);
+
+            this.items.put(item.getKey(), item);
+
+            savePreferences();
+            filterItems();
+        }
+    }
+
+    /**
+     * Removes currently selected item, saves the current state to preferences and
+     * updates the view.
+     */
+    private synchronized void removeSelectedItem() {
+        Optional<SelectorItem> it = this.getSelectedItem();
+
+        if (!it.isPresent()) {
+            JOptionPane.showMessageDialog(
+                    componentParent,
+                    tr("Please select an item first"));
+            return;
+        }
+
+        SelectorItem item = it.get();
+        if (this.items.remove(item.getKey(), item)) {
+            savePreferences();
+            filterItems();
+        }
+    }
+
+    /**
+     * Opens {@link EditItemDialog} for the selected item, saves the current state
+     * to preferences and updates the view.
+     */
+    private synchronized void editSelectedItem() {
+        Optional<SelectorItem> it = this.getSelectedItem();
+
+        if (!it.isPresent()) {
+            JOptionPane.showMessageDialog(
+                    componentParent,
+                    tr("Please select an item first"));
+            return;
+        }
+
+        SelectorItem item = it.get();
+
+        EditItemDialog dialog = new EditItemDialog(
+                componentParent,
+                tr("Edit item"),
+                item.getKey(),
+                item.getQuery(),
+                new String[] {tr("Save")});
+        dialog.showDialog();
+
+        Optional<SelectorItem> editedItem = dialog.getOutputItem();
+        editedItem.ifPresent(i -> {
+            this.items.remove(item.getKey(), item);
+            this.items.put(i.getKey(), i);
+
+            savePreferences();
+            filterItems();
+        });
+    }
+
+    /**
+     * Opens {@link EditItemDialog}, saves the state to preferences if a new item is added
+     * and updates the view.
+     */
+    private synchronized void createNewItem() {
+        EditItemDialog dialog = new EditItemDialog(componentParent, tr("Add snippet"), tr("Add"));
+        dialog.showDialog();
+
+        Optional<SelectorItem> newItem = dialog.getOutputItem();
+        newItem.ifPresent(i -> {
+            items.put(i.getKey(), new SelectorItem(i.getKey(), i.getQuery()));
+            savePreferences();
+            filterItems();
+        });
+    }
+
+    @Override
+    public void setDblClickListener(ActionListener dblClickListener) {
+        // this listener is already set within this class
+    }
+
+    @Override
+    protected void filterItems() {
+        String text = edSearchText.getText().toLowerCase(Locale.ENGLISH);
+
+        super.lsResultModel.setItems(this.items.values().stream()
+                .filter(item -> item.getKey().contains(text))
+                .collect(Collectors.toList()));
+    }
+
+    private void getDblClickListener(ActionEvent e) {
+        Optional<SelectorItem> selectedItem = this.getSelectedItem();
+
+        if (!selectedItem.isPresent()) {
+            return;
+        }
+
+        SelectorItem item = selectedItem.get();
+        this.target.setText(item.getQuery());
+    }
+
+    /**
+     * Saves all elements from the list to {@link Main#pref}.
+     */
+    private void savePreferences() {
+        Collection<Map<String, String>> toSave = new ArrayList<>(this.items.size());
+        for (SelectorItem item : this.items.values()) {
+            Map<String, String> it = new HashMap<>();
+            it.put(KEY_KEY, item.getKey());
+            it.put(QUERY_KEY, item.getQuery());
+            it.put(USE_COUNT_KEY, Integer.toString(item.getUsageCount()));
+
+            toSave.add(it);
+        }
+
+        Main.pref.putListOfStructs(PREFERENCE_ITEMS, toSave);
+    }
+
+    /**
+     * Loads the user saved items from {@link Main#pref}.
+     * @return A set of the user saved items.
+     */
+    private Map<String, SelectorItem> restorePreferences() {
+        Collection<Map<String, String>> toRetrieve =
+                Main.pref.getListOfStructs(PREFERENCE_ITEMS, Collections.emptyList());
+        Map<String, SelectorItem> result = new HashMap<>();
+
+        for (Map<String, String> entry : toRetrieve) {
+            String key = entry.get(KEY_KEY);
+            String query = entry.get(QUERY_KEY);
+            int usageCount = Integer.parseInt(entry.get(USE_COUNT_KEY));
+
+            result.put(key, new SelectorItem(key, query, usageCount));
+        }
+
+        return result;
+    }
+
+    private class OverpassQueryListMouseAdapter extends MouseAdapter {
+
+        private final JList list;
+        private final ResultListModel model;
+        private final JPopupMenu emptySelectionPopup = new JPopupMenu();
+        private final JPopupMenu elementPopup = new JPopupMenu();
+        private final JPopupMenu queryLookup = new JPopupMenu();
+
+        OverpassQueryListMouseAdapter(JList list, ResultListModel listModel) {
+            this.list = list;
+            this.model = listModel;
+
+            this.initPopupMenus();
+        }
+
+        /*
+         * Do not select the closest element if the user clicked on
+         * an empty area within the list.
+         */
+        private int locationToIndex(Point p) {
+            int idx = list.locationToIndex(p);
+
+            if (idx != -1 && !list.getCellBounds(idx, idx).contains(p)) {
+                return -1;
+            } else {
+                return idx;
+            }
+        }
+
+        @Override
+        public void mouseClicked(MouseEvent e) {
+            super.mouseClicked(e);
+            if (SwingUtilities.isRightMouseButton(e)) {
+                int index = locationToIndex(e.getPoint());
+
+                if (model.getSize() == 0 || index == -1) {
+                    list.clearSelection();
+                    emptySelectionPopup.show(list, e.getX(), e.getY());
+                } else {
+                    list.setSelectedIndex(index);
+                    list.ensureIndexIsVisible(index);
+                    elementPopup.show(list, e.getX(), e.getY());
+                }
+            }
+        }
+
+        @Override
+        public void mouseMoved(MouseEvent e) {
+            super.mouseMoved(e);
+            int idx = locationToIndex(e.getPoint());
+            if (idx == -1) {
+                return;
+            }
+
+            SelectorItem item = (SelectorItem) model.getElementAt(idx);
+            list.setToolTipText("<html><pre style='width:300px;'>" +
+                    Utils.escapeReservedCharactersHTML(Utils.restrictStringLines(item.getQuery(), 9)));
+        }
+
+        private void initPopupMenus() {
+            AbstractAction add = new AbstractAction(tr("Add")) {
+                @Override
+                public void actionPerformed(ActionEvent e) {
+                    createNewItem();
+                }
+            };
+            AbstractAction edit = new AbstractAction(tr("Edit")) {
+                @Override
+                public void actionPerformed(ActionEvent e) {
+                    editSelectedItem();
+                }
+            };
+            AbstractAction remove = new AbstractAction(tr("Remove")) {
+                @Override
+                public void actionPerformed(ActionEvent e) {
+                    removeSelectedItem();
+                }
+            };
+            this.emptySelectionPopup.add(add);
+            this.elementPopup.add(add);
+            this.elementPopup.add(edit);
+            this.elementPopup.add(remove);
+        }
+    }
+
+    /**
+     * This class defines the way each element is rendered in the list.
+     */
+    private static class OverpassQueryCellRendered extends JLabel implements ListCellRenderer<SelectorItem> {
+
+        OverpassQueryCellRendered() {
+            setOpaque(true);
+        }
+
+        @Override
+        public Component getListCellRendererComponent(
+                JList<? extends SelectorItem> list,
+                SelectorItem value,
+                int index,
+                boolean isSelected,
+                boolean cellHasFocus) {
+
+            Font font = list.getFont();
+            if (isSelected) {
+                setFont(new Font(font.getFontName(), Font.BOLD, font.getSize() + 2));
+                setBackground(list.getSelectionBackground());
+                setForeground(list.getSelectionForeground());
+            } else {
+                setFont(new Font(font.getFontName(), Font.PLAIN, font.getSize() + 2));
+                setBackground(list.getBackground());
+                setForeground(list.getForeground());
+            }
+
+            setEnabled(list.isEnabled());
+            setText(value.getKey());
+
+            if (isSelected && cellHasFocus) {
+                setBorder(new CompoundBorder(
+                        BorderFactory.createLineBorder(Color.BLACK, 1),
+                        BorderFactory.createEmptyBorder(2, 0, 2, 0)));
+            } else {
+                setBorder(new CompoundBorder(
+                        null,
+                        BorderFactory.createEmptyBorder(2, 0, 2, 0)));
+            }
+
+            return this;
+        }
+    }
+
+    /**
+     * Dialog that provides functionality to add/edit an item from the list.
+     */
+    private final class EditItemDialog extends ExtendedDialog {
+
+        private final JTextField name;
+        private final JosmTextArea query;
+        private final int initialNameHash;
+
+        private final transient AbstractTextComponentValidator queryValidator;
+        private final transient AbstractTextComponentValidator nameValidator;
+
+        private static final int SUCCESS_BTN = 0;
+        private static final int CANCEL_BTN = 1;
+
+        /**
+         * Added/Edited object to be returned. If {@link Optional#empty()} then probably
+         * the user closed the dialog, otherwise {@link SelectorItem} is present.
+         */
+        private transient Optional<SelectorItem> outputItem = Optional.empty();
+
+        EditItemDialog(Component parent, String title, String... buttonTexts) {
+            this(parent, title, "", "", buttonTexts);
+        }
+
+        EditItemDialog(
+                Component parent,
+                String title,
+                String nameToEdit,
+                String queryToEdit,
+                String... buttonTexts) {
+            super(parent, title, buttonTexts);
+
+            this.initialNameHash = nameToEdit.hashCode();
+
+            this.name = new JTextField(nameToEdit);
+            this.query = new JosmTextArea(queryToEdit);
+
+            this.queryValidator = new DefaultTextComponentValidator(this.query, "", tr("Query cannot be empty"));
+            this.nameValidator = new AbstractTextComponentValidator(this.name) {
+                @Override
+                public void validate() {
+                    if (isValid()) {
+                        feedbackValid(tr("This name can be used for the item"));
+                    } else {
+                        feedbackInvalid(tr("Item with this name already exists"));
+                    }
+                }
+
+                @Override
+                public boolean isValid() {
+                    String currentName = name.getText();
+                    int currentHash = currentName.hashCode();
+
+                    return !Utils.isStripEmpty(currentName) &&
+                            !(currentHash != initialNameHash &&
+                                    items.containsKey(currentName));
+                }
+            };
+
+            this.name.getDocument().addDocumentListener(this.nameValidator);
+            this.query.getDocument().addDocumentListener(this.queryValidator);
+
+            JPanel panel = new JPanel(new GridBagLayout());
+            JScrollPane queryScrollPane = GuiHelper.embedInVerticalScrollPane(this.query);
+            queryScrollPane.getVerticalScrollBar().setUnitIncrement(10); // make scrolling smooth
+
+            GBC constraint = GBC.eol().insets(8, 0, 8, 8).anchor(GBC.CENTER).fill(GBC.HORIZONTAL);
+            constraint.ipady = 250;
+            panel.add(this.name, GBC.eol().insets(5).anchor(GBC.SOUTHEAST).fill(GBC.HORIZONTAL));
+            panel.add(queryScrollPane, constraint);
+
+            setDefaultButton(SUCCESS_BTN);
+            setCancelButton(CANCEL_BTN);
+            setPreferredSize(new Dimension(400, 400));
+            setContent(panel, false);
+        }
+
+        /**
+         * Gets a new {@link SelectorItem} if one was created/modified.
+         * @return A {@link SelectorItem} object created out of the fields of the dialog.
+         */
+        public Optional<SelectorItem> getOutputItem() {
+            return this.outputItem;
+        }
+
+        @Override
+        protected void buttonAction(int buttonIndex, ActionEvent evt) {
+            if (buttonIndex == SUCCESS_BTN) {
+                if (!this.nameValidator.isValid()) {
+                    JOptionPane.showMessageDialog(
+                            componentParent,
+                            tr("The item cannot be created with provided name"),
+                            tr("Warning"),
+                            JOptionPane.WARNING_MESSAGE);
+                } else if (!this.queryValidator.isValid()) {
+                    JOptionPane.showMessageDialog(
+                            componentParent,
+                            tr("The item cannot be created with an empty query"),
+                            tr("Warning"),
+                            JOptionPane.WARNING_MESSAGE);
+                } else {
+                    this.outputItem = Optional.of(new SelectorItem(this.name.getText(), this.query.getText()));
+                    super.buttonAction(buttonIndex, evt);
+                }
+            } else {
+                super.buttonAction(buttonIndex, evt);
+            }
+        }
+    }
+
+    /**
+     * This class represents an Overpass query used by the user that can be
+     * shown within {@link OverpassQueryList}.
+     */
+    public static class SelectorItem {
+        private final String itemKey;
+        private final String query;
+        private int usageCount;
+
+        /**
+         * Constructs a new {@code SelectorItem}.
+         * @param key The key of this item.
+         * @param query The query of the item.
+         * @exception NullPointerException if any parameter is {@code null}.
+         * @exception IllegalArgumentException if any parameter is empty.
+         */
+        public SelectorItem(String key, String query) {
+            this(key, query, 1);
+        }
+
+        /**
+         * Constructs a new {@code SelectorItem}.
+         * @param key The key of this item.
+         * @param query The query of the item.
+         * @param usageCount The number of times this query was used.
+         * @exception NullPointerException if any parameter is {@code null}.
+         * @exception IllegalArgumentException if any parameter is empty.
+         */
+        public SelectorItem(String key, String query, int usageCount) {
+            Objects.requireNonNull(key);
+            Objects.requireNonNull(query);
+
+            if (Utils.isStripEmpty(key)) {
+                throw new IllegalArgumentException("The key of the item cannot be empty");
+            }
+            if (Utils.isStripEmpty(query)) {
+                throw new IllegalArgumentException("The query cannot be empty");
+            }
+
+            this.itemKey = key;
+            this.query = query;
+            this.usageCount = usageCount;
+        }
+
+        /**
+         * Gets the key (a string that is displayed in the selector) of this item.
+         * @return A string representing the key of this item.
+         */
+        public String getKey() {
+            return this.itemKey;
+        }
+
+        /**
+         * Gets the overpass query of this item.
+         * @return A string representing the overpass query of this item.
+         */
+        public String getQuery() {
+            return this.query;
+        }
+
+        /**
+         * Gets the number of times the query was used by the user.
+         * @return The usage count of this item.
+         */
+        public int getUsageCount() {
+            return this.usageCount;
+        }
+
+        /**
+         * Increments the {@link SelectorItem#usageCount} by one till
+         * it reaches {@link Integer#MAX_VALUE}.
+         */
+        public void increaseUsageCount() {
+            if (this.usageCount < Integer.MAX_VALUE) {
+                this.usageCount++;
+            }
+        }
+
+        /**
+         * Decrements the {@link SelectorItem#usageCount} ny one till
+         * it reaches 0.
+         */
+        public void decreaseUsageCount() {
+            if (this.usageCount > 0) {
+                this.usageCount--;
+            }
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (!(o instanceof SelectorItem)) return false;
+
+            SelectorItem that = (SelectorItem) o;
+
+            return itemKey.equals(that.itemKey) &&
+                    query.equals(that.getKey());
+        }
+
+        @Override
+        public int hashCode() {
+            return itemKey.hashCode();
+        }
+    }
+}
