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

Last change on this file since 5587 was 5587, checked in by Don-vip, 11 years ago

Single entry point in Utils to open HTTP connections

  • Property svn:eol-style set to native
File size: 20.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.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.InputStream;
16import java.io.InputStreamReader;
17import java.net.HttpURLConnection;
18import java.net.URL;
19import java.text.DecimalFormat;
20import java.util.ArrayList;
21import java.util.Collections;
22import java.util.LinkedList;
23import java.util.List;
24import java.util.StringTokenizer;
25
26import javax.swing.AbstractAction;
27import javax.swing.BorderFactory;
28import javax.swing.DefaultListSelectionModel;
29import javax.swing.JButton;
30import javax.swing.JLabel;
31import javax.swing.JPanel;
32import javax.swing.JScrollPane;
33import javax.swing.JTable;
34import javax.swing.JTextField;
35import javax.swing.ListSelectionModel;
36import javax.swing.UIManager;
37import javax.swing.event.DocumentEvent;
38import javax.swing.event.DocumentListener;
39import javax.swing.event.ListSelectionEvent;
40import javax.swing.event.ListSelectionListener;
41import javax.swing.table.DefaultTableColumnModel;
42import javax.swing.table.DefaultTableModel;
43import javax.swing.table.TableCellRenderer;
44import javax.swing.table.TableColumn;
45import javax.xml.parsers.SAXParserFactory;
46
47import org.openstreetmap.josm.Main;
48import org.openstreetmap.josm.data.Bounds;
49import org.openstreetmap.josm.data.coor.LatLon;
50import org.openstreetmap.josm.gui.ExceptionDialogUtil;
51import org.openstreetmap.josm.gui.PleaseWaitRunnable;
52import org.openstreetmap.josm.gui.widgets.HistoryComboBox;
53import org.openstreetmap.josm.gui.widgets.JosmComboBox;
54import org.openstreetmap.josm.io.OsmTransferException;
55import org.openstreetmap.josm.tools.GBC;
56import org.openstreetmap.josm.tools.ImageProvider;
57import org.openstreetmap.josm.tools.OsmUrlToBounds;
58import org.openstreetmap.josm.tools.Utils;
59import org.xml.sax.Attributes;
60import org.xml.sax.InputSource;
61import org.xml.sax.SAXException;
62import org.xml.sax.helpers.DefaultHandler;
63
64public class PlaceSelection implements DownloadSelection {
65 private static final String HISTORY_KEY = "download.places.history";
66
67 private HistoryComboBox cbSearchExpression;
68 private JButton btnSearch;
69 private NamedResultTableModel model;
70 private NamedResultTableColumnModel columnmodel;
71 private JTable tblSearchResults;
72 private DownloadDialog parent;
73 private final static Server[] servers = new Server[]{
74 new Server("Nominatim","http://nominatim.openstreetmap.org/search?format=xml&q=",tr("Class Type"),tr("Bounds")),
75 //new Server("Namefinder","http://gazetteer.openstreetmap.org/namefinder/search.xml?find=",tr("Near"),trc("placeselection", "Zoom"))
76 };
77 private final JosmComboBox server = new JosmComboBox(servers);
78
79 private static class Server {
80 public String name;
81 public String url;
82 public String thirdcol;
83 public String fourthcol;
84 @Override
85 public String toString() {
86 return name;
87 }
88 public Server(String n, String u, String t, String f) {
89 name = n;
90 url = u;
91 thirdcol = t;
92 fourthcol = f;
93 }
94 }
95
96 protected JPanel buildSearchPanel() {
97 JPanel lpanel = new JPanel();
98 lpanel.setLayout(new GridLayout(2,2));
99 JPanel panel = new JPanel();
100 panel.setLayout(new GridBagLayout());
101
102 lpanel.add(new JLabel(tr("Choose the server for searching:")));
103 lpanel.add(server);
104 String s = Main.pref.get("namefinder.server", servers[0].name);
105 for (int i = 0; i < servers.length; ++i) {
106 if (servers[i].name.equals(s)) {
107 server.setSelectedIndex(i);
108 }
109 }
110 lpanel.add(new JLabel(tr("Enter a place name to search for:")));
111
112 cbSearchExpression = new HistoryComboBox();
113 cbSearchExpression.setToolTipText(tr("Enter a place name to search for"));
114 List<String> cmtHistory = new LinkedList<String>(Main.pref.getCollection(HISTORY_KEY, new LinkedList<String>()));
115 Collections.reverse(cmtHistory);
116 cbSearchExpression.setPossibleItems(cmtHistory);
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 btnSearch = new JButton(searchAction);
122 ((JTextField)cbSearchExpression.getEditor().getEditorComponent()).getDocument().addDocumentListener(searchAction);
123 ((JTextField)cbSearchExpression.getEditor().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 public void addGui(final DownloadDialog gui) {
136 JPanel panel = new JPanel();
137 panel.setLayout(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 gui.addDownloadAreaSelector(panel, tr("Areas around places"));
150
151 scrollPane.setPreferredSize(scrollPane.getPreferredSize());
152 tblSearchResults.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
153 tblSearchResults.getSelectionModel().addListSelectionListener(new ListSelectionHandler());
154 tblSearchResults.addMouseListener(new MouseAdapter() {
155 @Override public void mouseClicked(MouseEvent e) {
156 if (e.getClickCount() > 1) {
157 SearchResult sr = model.getSelectedSearchResult();
158 if (sr == null) return;
159 parent.startDownload(sr.getDownloadArea());
160 }
161 }
162 });
163 parent = gui;
164 }
165
166 public void setDownloadArea(Bounds area) {
167 tblSearchResults.clearSelection();
168 }
169
170 /**
171 * Data storage for search results.
172 */
173 static private class SearchResult {
174 public String name;
175 public String info;
176 public String nearestPlace;
177 public String description;
178 public double lat;
179 public double lon;
180 public int zoom = 0;
181 public Bounds bounds = null;
182
183 public Bounds getDownloadArea() {
184 return bounds != null ? bounds : OsmUrlToBounds.positionToBounds(lat, lon, zoom);
185 }
186 }
187
188 /**
189 * A very primitive parser for the name finder's output.
190 * Structure of xml described here: http://wiki.openstreetmap.org/index.php/Name_finder
191 *
192 */
193 private static class NameFinderResultParser extends DefaultHandler {
194 private SearchResult currentResult = null;
195 private StringBuffer description = null;
196 private int depth = 0;
197 private List<SearchResult> data = new LinkedList<SearchResult>();
198
199 /**
200 * Detect starting elements.
201 *
202 */
203 @Override
204 public void startElement(String namespaceURI, String localName, String qName, Attributes atts)
205 throws SAXException {
206 depth++;
207 try {
208 if (qName.equals("searchresults")) {
209 // do nothing
210 } else if (qName.equals("named") && (depth == 2)) {
211 currentResult = new PlaceSelection.SearchResult();
212 currentResult.name = atts.getValue("name");
213 currentResult.info = atts.getValue("info");
214 if(currentResult.info != null) {
215 currentResult.info = tr(currentResult.info);
216 }
217 currentResult.lat = Double.parseDouble(atts.getValue("lat"));
218 currentResult.lon = Double.parseDouble(atts.getValue("lon"));
219 currentResult.zoom = Integer.parseInt(atts.getValue("zoom"));
220 data.add(currentResult);
221 } else if (qName.equals("description") && (depth == 3)) {
222 description = new StringBuffer();
223 } else if (qName.equals("named") && (depth == 4)) {
224 // this is a "named" place in the nearest places list.
225 String info = atts.getValue("info");
226 if ("city".equals(info) || "town".equals(info) || "village".equals(info)) {
227 currentResult.nearestPlace = atts.getValue("name");
228 }
229 } else if (qName.equals("place") && atts.getValue("lat") != null) {
230 currentResult = new PlaceSelection.SearchResult();
231 currentResult.name = atts.getValue("display_name");
232 currentResult.description = currentResult.name;
233 currentResult.info = atts.getValue("class");
234 if (currentResult.info != null) {
235 currentResult.info = tr(currentResult.info);
236 }
237 currentResult.nearestPlace = tr(atts.getValue("type"));
238 currentResult.lat = Double.parseDouble(atts.getValue("lat"));
239 currentResult.lon = Double.parseDouble(atts.getValue("lon"));
240 String[] bbox = atts.getValue("boundingbox").split(",");
241 currentResult.bounds = new Bounds(
242 new LatLon(Double.parseDouble(bbox[0]), Double.parseDouble(bbox[2])),
243 new LatLon(Double.parseDouble(bbox[1]), Double.parseDouble(bbox[3])));
244 data.add(currentResult);
245 }
246 } catch (NumberFormatException x) {
247 x.printStackTrace(); // SAXException does not chain correctly
248 throw new SAXException(x.getMessage(), x);
249 } catch (NullPointerException x) {
250 x.printStackTrace(); // SAXException does not chain correctly
251 throw new SAXException(tr("Null pointer exception, possibly some missing tags."), x);
252 }
253 }
254
255 /**
256 * Detect ending elements.
257 */
258 @Override
259 public void endElement(String namespaceURI, String localName, String qName) throws SAXException {
260 if (qName.equals("searchresults")) {
261 } else if (qName.equals("description") && description != null) {
262 currentResult.description = description.toString();
263 description = null;
264 }
265 depth--;
266
267 }
268
269 /**
270 * Read characters for description.
271 */
272 @Override
273 public void characters(char[] data, int start, int length) throws org.xml.sax.SAXException {
274 if (description != null) {
275 description.append(data, start, length);
276 }
277 }
278
279 public List<SearchResult> getResult() {
280 return data;
281 }
282 }
283
284 class SearchAction extends AbstractAction implements DocumentListener {
285
286 public SearchAction() {
287 putValue(NAME, tr("Search ..."));
288 putValue(SMALL_ICON, ImageProvider.get("dialogs","search"));
289 putValue(SHORT_DESCRIPTION, tr("Click to start searching for places"));
290 updateEnabledState();
291 }
292
293 public void actionPerformed(ActionEvent e) {
294 if (!isEnabled() || cbSearchExpression.getText().trim().length() == 0)
295 return;
296 cbSearchExpression.addCurrentItemToHistory();
297 Main.pref.putCollection(HISTORY_KEY, cbSearchExpression.getHistory());
298 NameQueryTask task = new NameQueryTask(cbSearchExpression.getText());
299 Main.worker.submit(task);
300 }
301
302 protected void updateEnabledState() {
303 setEnabled(cbSearchExpression.getText().trim().length() > 0);
304 }
305
306 public void changedUpdate(DocumentEvent e) {
307 updateEnabledState();
308 }
309
310 public void insertUpdate(DocumentEvent e) {
311 updateEnabledState();
312 }
313
314 public void removeUpdate(DocumentEvent e) {
315 updateEnabledState();
316 }
317 }
318
319 class NameQueryTask extends PleaseWaitRunnable {
320
321 private String searchExpression;
322 private HttpURLConnection connection;
323 private List<SearchResult> data;
324 private boolean canceled = false;
325 private Server useserver;
326 private Exception lastException;
327
328 public NameQueryTask(String searchExpression) {
329 super(tr("Querying name server"),false /* don't ignore exceptions */);
330 this.searchExpression = searchExpression;
331 useserver = (Server)server.getSelectedItem();
332 Main.pref.put("namefinder.server", useserver.name);
333 }
334
335 @Override
336 protected void cancel() {
337 this.canceled = true;
338 synchronized (this) {
339 if (connection != null) {
340 connection.disconnect();
341 }
342 }
343 }
344
345 @Override
346 protected void finish() {
347 if (canceled)
348 return;
349 if (lastException != null) {
350 ExceptionDialogUtil.explainException(lastException);
351 return;
352 }
353 columnmodel.setHeadlines(useserver.thirdcol, useserver.fourthcol);
354 model.setData(this.data);
355 }
356
357 @Override
358 protected void realRun() throws SAXException, IOException, OsmTransferException {
359 String urlString = useserver.url+java.net.URLEncoder.encode(searchExpression, "UTF-8");
360
361 try {
362 getProgressMonitor().indeterminateSubTask(tr("Querying name server ..."));
363 URL url = new URL(urlString);
364 synchronized(this) {
365 connection = Utils.openHttpConnection(url);
366 }
367 connection.setConnectTimeout(Main.pref.getInteger("socket.timeout.connect",15)*1000);
368 InputStream inputStream = connection.getInputStream();
369 InputSource inputSource = new InputSource(new InputStreamReader(inputStream, "UTF-8"));
370 NameFinderResultParser parser = new NameFinderResultParser();
371 SAXParserFactory.newInstance().newSAXParser().parse(inputSource, parser);
372 this.data = parser.getResult();
373 } catch(Exception e) {
374 if (canceled)
375 // ignore exception
376 return;
377 OsmTransferException ex = new OsmTransferException(e);
378 ex.setUrl(urlString);
379 lastException = ex;
380 }
381 }
382 }
383
384 static class NamedResultTableModel extends DefaultTableModel {
385 private ArrayList<SearchResult> data;
386 private ListSelectionModel selectionModel;
387
388 public NamedResultTableModel(ListSelectionModel selectionModel) {
389 data = new ArrayList<SearchResult>();
390 this.selectionModel = selectionModel;
391 }
392 @Override
393 public int getRowCount() {
394 if (data == null) return 0;
395 return data.size();
396 }
397
398 @Override
399 public Object getValueAt(int row, int column) {
400 if (data == null) return null;
401 return data.get(row);
402 }
403
404 public void setData(List<SearchResult> data) {
405 if (data == null) {
406 this.data.clear();
407 } else {
408 this.data =new ArrayList<SearchResult>(data);
409 }
410 fireTableDataChanged();
411 }
412 @Override
413 public boolean isCellEditable(int row, int column) {
414 return false;
415 }
416
417 public SearchResult getSelectedSearchResult() {
418 if (selectionModel.getMinSelectionIndex() < 0)
419 return null;
420 return data.get(selectionModel.getMinSelectionIndex());
421 }
422 }
423
424 static class NamedResultTableColumnModel extends DefaultTableColumnModel {
425 TableColumn col3 = null;
426 TableColumn col4 = null;
427 protected void createColumns() {
428 TableColumn col = null;
429 NamedResultCellRenderer renderer = new NamedResultCellRenderer();
430
431 // column 0 - Name
432 col = new TableColumn(0);
433 col.setHeaderValue(tr("Name"));
434 col.setResizable(true);
435 col.setPreferredWidth(200);
436 col.setCellRenderer(renderer);
437 addColumn(col);
438
439 // column 1 - Version
440 col = new TableColumn(1);
441 col.setHeaderValue(tr("Type"));
442 col.setResizable(true);
443 col.setPreferredWidth(100);
444 col.setCellRenderer(renderer);
445 addColumn(col);
446
447 // column 2 - Near
448 col3 = new TableColumn(2);
449 col3.setHeaderValue(servers[0].thirdcol);
450 col3.setResizable(true);
451 col3.setPreferredWidth(100);
452 col3.setCellRenderer(renderer);
453 addColumn(col3);
454
455 // column 3 - Zoom
456 col4 = new TableColumn(3);
457 col4.setHeaderValue(servers[0].fourthcol);
458 col4.setResizable(true);
459 col4.setPreferredWidth(50);
460 col4.setCellRenderer(renderer);
461 addColumn(col4);
462 }
463 public void setHeadlines(String third, String fourth) {
464 col3.setHeaderValue(third);
465 col4.setHeaderValue(fourth);
466 fireColumnMarginChanged();
467 }
468
469 public NamedResultTableColumnModel() {
470 createColumns();
471 }
472 }
473
474 class ListSelectionHandler implements ListSelectionListener {
475 public void valueChanged(ListSelectionEvent lse) {
476 SearchResult r = model.getSelectedSearchResult();
477 if (r != null) {
478 parent.boundingBoxChanged(r.getDownloadArea(), PlaceSelection.this);
479 }
480 }
481 }
482
483 static class NamedResultCellRenderer extends JLabel implements TableCellRenderer {
484
485 public NamedResultCellRenderer() {
486 setOpaque(true);
487 setBorder(BorderFactory.createEmptyBorder(2,2,2,2));
488 }
489
490 protected void reset() {
491 setText("");
492 setIcon(null);
493 }
494
495 protected void renderColor(boolean selected) {
496 if (selected) {
497 setForeground(UIManager.getColor("Table.selectionForeground"));
498 setBackground(UIManager.getColor("Table.selectionBackground"));
499 } else {
500 setForeground(UIManager.getColor("Table.foreground"));
501 setBackground(UIManager.getColor("Table.background"));
502 }
503 }
504
505 protected String lineWrapDescription(String description) {
506 StringBuffer ret = new StringBuffer();
507 StringBuffer line = new StringBuffer();
508 StringTokenizer tok = new StringTokenizer(description, " ");
509 while(tok.hasMoreElements()) {
510 String t = tok.nextToken();
511 if (line.length() == 0) {
512 line.append(t);
513 } else if (line.length() < 80) {
514 line.append(" ").append(t);
515 } else {
516 line.append(" ").append(t).append("<br>");
517 ret.append(line);
518 line = new StringBuffer();
519 }
520 }
521 ret.insert(0, "<html>");
522 ret.append("</html>");
523 return ret.toString();
524 }
525
526 public Component getTableCellRendererComponent(JTable table, Object value,
527 boolean isSelected, boolean hasFocus, int row, int column) {
528
529 reset();
530 renderColor(isSelected);
531
532 if (value == null) return this;
533 SearchResult sr = (SearchResult) value;
534 switch(column) {
535 case 0:
536 setText(sr.name);
537 break;
538 case 1:
539 setText(sr.info);
540 break;
541 case 2:
542 setText(sr.nearestPlace);
543 break;
544 case 3:
545 if(sr.bounds != null) {
546 setText(sr.bounds.toShortString(new DecimalFormat("0.000")));
547 } else {
548 setText(sr.zoom != 0 ? Integer.toString(sr.zoom) : tr("unknown"));
549 }
550 break;
551 }
552 setToolTipText(lineWrapDescription(sr.description));
553 return this;
554 }
555 }
556}
Note: See TracBrowser for help on using the repository browser.