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

Last change on this file since 2626 was 2626, checked in by jttt, 14 years ago

Fixed some of the warnings found by FindBugs

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