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

Last change on this file since 3083 was 3083, checked in by bastiK, 14 years ago

added svn:eol-style=native to source files

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