source: josm/trunk/src/org/openstreetmap/josm/gui/download/OverpassDownloadSource.java@ 12652

Last change on this file since 12652 was 12652, checked in by michael2402, 7 years ago

Apply #15167: Merge OSM and overpass download dialog. Patch by bafonins

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