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

Last change on this file since 12938 was 12903, checked in by Don-vip, 7 years ago

fix #15267 - fix various usability problems with new download dialog

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