source: josm/trunk/src/org/openstreetmap/josm/gui/download/PlaceSelection.java@ 15011

Last change on this file since 15011 was 15011, checked in by GerdP, 5 years ago

fix #15558: apply 15558-v3.patch

  • Property svn:eol-style set to native
File size: 16.3 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.Component;
8import java.awt.Dimension;
9import java.awt.GridBagLayout;
10import java.awt.GridLayout;
11import java.awt.event.ActionEvent;
12import java.awt.event.MouseAdapter;
13import java.awt.event.MouseEvent;
14import java.io.IOException;
15import java.io.Reader;
16import java.net.URL;
17import java.text.DecimalFormat;
18import java.util.ArrayList;
19import java.util.Collections;
20import java.util.List;
21import java.util.StringTokenizer;
22
23import javax.swing.AbstractAction;
24import javax.swing.BorderFactory;
25import javax.swing.DefaultListSelectionModel;
26import javax.swing.JButton;
27import javax.swing.JLabel;
28import javax.swing.JOptionPane;
29import javax.swing.JPanel;
30import javax.swing.JScrollPane;
31import javax.swing.JTable;
32import javax.swing.ListSelectionModel;
33import javax.swing.UIManager;
34import javax.swing.event.DocumentEvent;
35import javax.swing.event.DocumentListener;
36import javax.swing.event.ListSelectionEvent;
37import javax.swing.event.ListSelectionListener;
38import javax.swing.table.DefaultTableColumnModel;
39import javax.swing.table.DefaultTableModel;
40import javax.swing.table.TableCellRenderer;
41import javax.swing.table.TableColumn;
42import javax.xml.parsers.ParserConfigurationException;
43
44import org.openstreetmap.josm.data.Bounds;
45import org.openstreetmap.josm.gui.ExceptionDialogUtil;
46import org.openstreetmap.josm.gui.HelpAwareOptionPane;
47import org.openstreetmap.josm.gui.MainApplication;
48import org.openstreetmap.josm.gui.PleaseWaitRunnable;
49import org.openstreetmap.josm.gui.util.GuiHelper;
50import org.openstreetmap.josm.gui.widgets.HistoryComboBox;
51import org.openstreetmap.josm.gui.widgets.JosmComboBox;
52import org.openstreetmap.josm.io.NameFinder;
53import org.openstreetmap.josm.io.NameFinder.SearchResult;
54import org.openstreetmap.josm.io.OsmTransferException;
55import org.openstreetmap.josm.spi.preferences.Config;
56import org.openstreetmap.josm.tools.GBC;
57import org.openstreetmap.josm.tools.HttpClient;
58import org.openstreetmap.josm.tools.ImageProvider;
59import org.openstreetmap.josm.tools.Logging;
60import org.openstreetmap.josm.tools.Utils;
61import org.xml.sax.SAXException;
62import org.xml.sax.SAXParseException;
63
64/**
65 * Place selector.
66 * @since 1329
67 */
68public class PlaceSelection implements DownloadSelection {
69 private static final String HISTORY_KEY = "download.places.history";
70
71 private HistoryComboBox cbSearchExpression;
72 private NamedResultTableModel model;
73 private NamedResultTableColumnModel columnmodel;
74 private JTable tblSearchResults;
75 private DownloadDialog parent;
76 private static final Server[] SERVERS = new Server[] {
77 new Server("Nominatim", NameFinder.NOMINATIM_URL, tr("Class Type"), tr("Bounds"))
78 };
79 private final JosmComboBox<Server> server = new JosmComboBox<>(SERVERS);
80
81 private static class Server {
82 public final String name;
83 public final String url;
84 public final String thirdcol;
85 public final String fourthcol;
86
87 Server(String n, String u, String t, String f) {
88 name = n;
89 url = u;
90 thirdcol = t;
91 fourthcol = f;
92 }
93
94 @Override
95 public String toString() {
96 return name;
97 }
98 }
99
100 protected JPanel buildSearchPanel() {
101 JPanel lpanel = new JPanel(new GridLayout(2, 2));
102 JPanel panel = new JPanel(new GridBagLayout());
103
104 lpanel.add(new JLabel(tr("Choose the server for searching:")));
105 lpanel.add(server);
106 String s = Config.getPref().get("namefinder.server", SERVERS[0].name);
107 for (int i = 0; i < SERVERS.length; ++i) {
108 if (SERVERS[i].name.equals(s)) {
109 server.setSelectedIndex(i);
110 }
111 }
112 lpanel.add(new JLabel(tr("Enter a place name to search for:")));
113
114 cbSearchExpression = new HistoryComboBox();
115 cbSearchExpression.setToolTipText(tr("Enter a place name to search for"));
116 cbSearchExpression.setPossibleItemsTopDown(Config.getPref().getList(HISTORY_KEY, Collections.emptyList()));
117 lpanel.add(cbSearchExpression);
118
119 panel.add(lpanel, GBC.std().fill(GBC.HORIZONTAL).insets(5, 5, 0, 5));
120 SearchAction searchAction = new SearchAction();
121 JButton btnSearch = new JButton(searchAction);
122 cbSearchExpression.getEditorComponent().getDocument().addDocumentListener(searchAction);
123 cbSearchExpression.getEditorComponent().addActionListener(searchAction);
124
125 panel.add(btnSearch, GBC.eol().insets(5, 5, 0, 5));
126
127 return panel;
128 }
129
130 /**
131 * Adds a new tab to the download dialog in JOSM.
132 *
133 * This method is, for all intents and purposes, the constructor for this class.
134 */
135 @Override
136 public void addGui(final DownloadDialog gui) {
137 JPanel panel = new JPanel(new BorderLayout());
138 panel.add(buildSearchPanel(), BorderLayout.NORTH);
139
140 DefaultListSelectionModel selectionModel = new DefaultListSelectionModel();
141 model = new NamedResultTableModel(selectionModel);
142 columnmodel = new NamedResultTableColumnModel();
143 tblSearchResults = new JTable(model, columnmodel);
144 tblSearchResults.setSelectionModel(selectionModel);
145 JScrollPane scrollPane = new JScrollPane(tblSearchResults);
146 scrollPane.setPreferredSize(new Dimension(200, 200));
147 panel.add(scrollPane, BorderLayout.CENTER);
148
149 if (gui != null)
150 gui.addDownloadAreaSelector(panel, tr("Areas around places"));
151
152 scrollPane.setPreferredSize(scrollPane.getPreferredSize());
153 tblSearchResults.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
154 tblSearchResults.getSelectionModel().addListSelectionListener(new ListSelectionHandler());
155 tblSearchResults.addMouseListener(new MouseAdapter() {
156 @Override
157 public void mouseClicked(MouseEvent e) {
158 if (e.getClickCount() > 1) {
159 SearchResult sr = model.getSelectedSearchResult();
160 if (sr != null) {
161 parent.startDownload(sr.getDownloadArea());
162 }
163 }
164 }
165 });
166 parent = gui;
167 }
168
169 @Override
170 public void setDownloadArea(Bounds area) {
171 tblSearchResults.clearSelection();
172 }
173
174 class SearchAction extends AbstractAction implements DocumentListener {
175
176 SearchAction() {
177 putValue(NAME, tr("Search..."));
178 new ImageProvider("dialogs", "search").getResource().attachImageIcon(this, true);
179 putValue(SHORT_DESCRIPTION, tr("Click to start searching for places"));
180 updateEnabledState();
181 }
182
183 @Override
184 public void actionPerformed(ActionEvent e) {
185 if (!isEnabled() || cbSearchExpression.getText().trim().isEmpty())
186 return;
187 cbSearchExpression.addCurrentItemToHistory();
188 Config.getPref().putList(HISTORY_KEY, cbSearchExpression.getHistory());
189 NameQueryTask task = new NameQueryTask(cbSearchExpression.getText());
190 MainApplication.worker.submit(task);
191 }
192
193 protected final void updateEnabledState() {
194 setEnabled(!cbSearchExpression.getText().trim().isEmpty());
195 }
196
197 @Override
198 public void changedUpdate(DocumentEvent e) {
199 updateEnabledState();
200 }
201
202 @Override
203 public void insertUpdate(DocumentEvent e) {
204 updateEnabledState();
205 }
206
207 @Override
208 public void removeUpdate(DocumentEvent e) {
209 updateEnabledState();
210 }
211 }
212
213 class NameQueryTask extends PleaseWaitRunnable {
214
215 private final String searchExpression;
216 private HttpClient connection;
217 private List<SearchResult> data;
218 private boolean canceled;
219 private final Server useserver;
220 private Exception lastException;
221
222 NameQueryTask(String searchExpression) {
223 super(tr("Querying name server"), false /* don't ignore exceptions */);
224 this.searchExpression = searchExpression;
225 useserver = (Server) server.getSelectedItem();
226 Config.getPref().put("namefinder.server", useserver.name);
227 }
228
229 @Override
230 protected void cancel() {
231 this.canceled = true;
232 synchronized (this) {
233 if (connection != null) {
234 connection.disconnect();
235 }
236 }
237 }
238
239 @Override
240 protected void finish() {
241 if (canceled)
242 return;
243 if (lastException != null) {
244 ExceptionDialogUtil.explainException(lastException);
245 return;
246 }
247 columnmodel.setHeadlines(useserver.thirdcol, useserver.fourthcol);
248 model.setData(this.data);
249 }
250
251 @Override
252 protected void realRun() throws SAXException, IOException, OsmTransferException {
253 String urlString = useserver.url+Utils.encodeUrl(searchExpression);
254
255 try {
256 getProgressMonitor().indeterminateSubTask(tr("Querying name server ..."));
257 URL url = new URL(urlString);
258 synchronized (this) {
259 connection = HttpClient.create(url);
260 connection.connect();
261 }
262 try (Reader reader = connection.getResponse().getContentReader()) {
263 data = NameFinder.parseSearchResults(reader);
264 }
265 } catch (SAXParseException e) {
266 if (!canceled) {
267 // Nominatim sometimes returns garbage, see #5934, #10643
268 Logging.log(Logging.LEVEL_WARN, tr("Error occurred with query ''{0}'': ''{1}''", urlString, e.getMessage()), e);
269 GuiHelper.runInEDTAndWait(() -> HelpAwareOptionPane.showOptionDialog(
270 MainApplication.getMainFrame(),
271 tr("Name server returned invalid data. Please try again."),
272 tr("Bad response"),
273 JOptionPane.WARNING_MESSAGE, null
274 ));
275 }
276 } catch (IOException | ParserConfigurationException e) {
277 if (!canceled) {
278 OsmTransferException ex = new OsmTransferException(e);
279 ex.setUrl(urlString);
280 lastException = ex;
281 }
282 }
283 }
284 }
285
286 static class NamedResultTableModel extends DefaultTableModel {
287 private transient List<SearchResult> data;
288 private final transient ListSelectionModel selectionModel;
289
290 NamedResultTableModel(ListSelectionModel selectionModel) {
291 data = new ArrayList<>();
292 this.selectionModel = selectionModel;
293 }
294
295 @Override
296 public int getRowCount() {
297 return data != null ? data.size() : 0;
298 }
299
300 @Override
301 public Object getValueAt(int row, int column) {
302 return data != null ? data.get(row) : null;
303 }
304
305 public void setData(List<SearchResult> data) {
306 if (data == null) {
307 this.data.clear();
308 } else {
309 this.data = new ArrayList<>(data);
310 }
311 fireTableDataChanged();
312 }
313
314 @Override
315 public boolean isCellEditable(int row, int column) {
316 return false;
317 }
318
319 public SearchResult getSelectedSearchResult() {
320 if (selectionModel.getMinSelectionIndex() < 0)
321 return null;
322 return data.get(selectionModel.getMinSelectionIndex());
323 }
324 }
325
326 static class NamedResultTableColumnModel extends DefaultTableColumnModel {
327 private TableColumn col3;
328 private TableColumn col4;
329
330 NamedResultTableColumnModel() {
331 createColumns();
332 }
333
334 protected final void createColumns() {
335 TableColumn col;
336 NamedResultCellRenderer renderer = new NamedResultCellRenderer();
337
338 // column 0 - Name
339 col = new TableColumn(0);
340 col.setHeaderValue(tr("Name"));
341 col.setResizable(true);
342 col.setPreferredWidth(200);
343 col.setCellRenderer(renderer);
344 addColumn(col);
345
346 // column 1 - Version
347 col = new TableColumn(1);
348 col.setHeaderValue(tr("Type"));
349 col.setResizable(true);
350 col.setPreferredWidth(100);
351 col.setCellRenderer(renderer);
352 addColumn(col);
353
354 // column 2 - Near
355 col3 = new TableColumn(2);
356 col3.setHeaderValue(SERVERS[0].thirdcol);
357 col3.setResizable(true);
358 col3.setPreferredWidth(100);
359 col3.setCellRenderer(renderer);
360 addColumn(col3);
361
362 // column 3 - Zoom
363 col4 = new TableColumn(3);
364 col4.setHeaderValue(SERVERS[0].fourthcol);
365 col4.setResizable(true);
366 col4.setPreferredWidth(50);
367 col4.setCellRenderer(renderer);
368 addColumn(col4);
369 }
370
371 public void setHeadlines(String third, String fourth) {
372 col3.setHeaderValue(third);
373 col4.setHeaderValue(fourth);
374 fireColumnMarginChanged();
375 }
376 }
377
378 class ListSelectionHandler implements ListSelectionListener {
379 @Override
380 public void valueChanged(ListSelectionEvent lse) {
381 SearchResult r = model.getSelectedSearchResult();
382 if (r != null) {
383 parent.boundingBoxChanged(r.getDownloadArea(), PlaceSelection.this);
384 }
385 }
386 }
387
388 static class NamedResultCellRenderer extends JLabel implements TableCellRenderer {
389
390 /**
391 * Constructs a new {@code NamedResultCellRenderer}.
392 */
393 NamedResultCellRenderer() {
394 setOpaque(true);
395 setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2));
396 }
397
398 protected void reset() {
399 setText("");
400 setIcon(null);
401 }
402
403 protected void renderColor(boolean selected) {
404 if (selected) {
405 setForeground(UIManager.getColor("Table.selectionForeground"));
406 setBackground(UIManager.getColor("Table.selectionBackground"));
407 } else {
408 setForeground(UIManager.getColor("Table.foreground"));
409 setBackground(UIManager.getColor("Table.background"));
410 }
411 }
412
413 protected String lineWrapDescription(String description) {
414 StringBuilder ret = new StringBuilder();
415 StringBuilder line = new StringBuilder();
416 StringTokenizer tok = new StringTokenizer(description, " ");
417 while (tok.hasMoreElements()) {
418 String t = tok.nextToken();
419 if (line.length() == 0) {
420 line.append(t);
421 } else if (line.length() < 80) {
422 line.append(' ').append(t);
423 } else {
424 line.append(' ').append(t).append("<br>");
425 ret.append(line);
426 line = new StringBuilder();
427 }
428 }
429 ret.insert(0, "<html>");
430 ret.append("</html>");
431 return ret.toString();
432 }
433
434 @Override
435 public Component getTableCellRendererComponent(JTable table, Object value,
436 boolean isSelected, boolean hasFocus, int row, int column) {
437
438 reset();
439 renderColor(isSelected);
440
441 if (value == null)
442 return this;
443 SearchResult sr = (SearchResult) value;
444 switch(column) {
445 case 0:
446 setText(sr.getName());
447 break;
448 case 1:
449 setText(sr.getInfo());
450 break;
451 case 2:
452 setText(sr.getNearestPlace());
453 break;
454 case 3:
455 if (sr.getBounds() != null) {
456 setText(sr.getBounds().toShortString(new DecimalFormat("0.000")));
457 } else {
458 setText(sr.getZoom() != 0 ? Integer.toString(sr.getZoom()) : tr("unknown"));
459 }
460 break;
461 default: // Do nothing
462 }
463 setToolTipText(lineWrapDescription(sr.getDescription()));
464 return this;
465 }
466 }
467}
Note: See TracBrowser for help on using the repository browser.