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

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

fix #7917 - Control the number of items displayed at once in all comboboxes (20 by default, configurable with gui.combobox.maximum-row-count)

  • 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.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 JosmComboBox server = new JosmComboBox(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") && atts.getValue("lat") != null) {
229 currentResult = new PlaceSelection.SearchResult();
230 currentResult.name = atts.getValue("display_name");
231 currentResult.description = currentResult.name;
232 currentResult.info = atts.getValue("class");
233 if (currentResult.info != null) {
234 currentResult.info = tr(currentResult.info);
235 }
236 currentResult.nearestPlace = tr(atts.getValue("type"));
237 currentResult.lat = Double.parseDouble(atts.getValue("lat"));
238 currentResult.lon = Double.parseDouble(atts.getValue("lon"));
239 String[] bbox = atts.getValue("boundingbox").split(",");
240 currentResult.bounds = new Bounds(
241 new LatLon(Double.parseDouble(bbox[0]), Double.parseDouble(bbox[2])),
242 new LatLon(Double.parseDouble(bbox[1]), Double.parseDouble(bbox[3])));
243 data.add(currentResult);
244 }
245 } catch (NumberFormatException x) {
246 x.printStackTrace(); // SAXException does not chain correctly
247 throw new SAXException(x.getMessage(), x);
248 } catch (NullPointerException x) {
249 x.printStackTrace(); // SAXException does not chain correctly
250 throw new SAXException(tr("Null pointer exception, possibly some missing tags."), x);
251 }
252 }
253
254 /**
255 * Detect ending elements.
256 */
257 @Override
258 public void endElement(String namespaceURI, String localName, String qName) throws SAXException {
259 if (qName.equals("searchresults")) {
260 } else if (qName.equals("description") && description != null) {
261 currentResult.description = description.toString();
262 description = null;
263 }
264 depth--;
265
266 }
267
268 /**
269 * Read characters for description.
270 */
271 @Override
272 public void characters(char[] data, int start, int length) throws org.xml.sax.SAXException {
273 if (description != null) {
274 description.append(data, start, length);
275 }
276 }
277
278 public List<SearchResult> getResult() {
279 return data;
280 }
281 }
282
283 class SearchAction extends AbstractAction implements DocumentListener {
284
285 public SearchAction() {
286 putValue(NAME, tr("Search ..."));
287 putValue(SMALL_ICON, ImageProvider.get("dialogs","search"));
288 putValue(SHORT_DESCRIPTION, tr("Click to start searching for places"));
289 updateEnabledState();
290 }
291
292 public void actionPerformed(ActionEvent e) {
293 if (!isEnabled() || cbSearchExpression.getText().trim().length() == 0)
294 return;
295 cbSearchExpression.addCurrentItemToHistory();
296 Main.pref.putCollection(HISTORY_KEY, cbSearchExpression.getHistory());
297 NameQueryTask task = new NameQueryTask(cbSearchExpression.getText());
298 Main.worker.submit(task);
299 }
300
301 protected void updateEnabledState() {
302 setEnabled(cbSearchExpression.getText().trim().length() > 0);
303 }
304
305 public void changedUpdate(DocumentEvent e) {
306 updateEnabledState();
307 }
308
309 public void insertUpdate(DocumentEvent e) {
310 updateEnabledState();
311 }
312
313 public void removeUpdate(DocumentEvent e) {
314 updateEnabledState();
315 }
316 }
317
318 class NameQueryTask extends PleaseWaitRunnable {
319
320 private String searchExpression;
321 private HttpURLConnection connection;
322 private List<SearchResult> data;
323 private boolean canceled = false;
324 private Server useserver;
325 private Exception lastException;
326
327 public NameQueryTask(String searchExpression) {
328 super(tr("Querying name server"),false /* don't ignore exceptions */);
329 this.searchExpression = searchExpression;
330 useserver = (Server)server.getSelectedItem();
331 Main.pref.put("namefinder.server", useserver.name);
332 }
333
334 @Override
335 protected void cancel() {
336 this.canceled = true;
337 synchronized (this) {
338 if (connection != null) {
339 connection.disconnect();
340 }
341 }
342 }
343
344 @Override
345 protected void finish() {
346 if (canceled)
347 return;
348 if (lastException != null) {
349 ExceptionDialogUtil.explainException(lastException);
350 return;
351 }
352 columnmodel.setHeadlines(useserver.thirdcol, useserver.fourthcol);
353 model.setData(this.data);
354 }
355
356 @Override
357 protected void realRun() throws SAXException, IOException, OsmTransferException {
358 String urlString = useserver.url+java.net.URLEncoder.encode(searchExpression, "UTF-8");
359
360 try {
361 getProgressMonitor().indeterminateSubTask(tr("Querying name server ..."));
362 URL url = new URL(urlString);
363 synchronized(this) {
364 connection = (HttpURLConnection)url.openConnection();
365 }
366 connection.setConnectTimeout(Main.pref.getInteger("socket.timeout.connect",15)*1000);
367 InputStream inputStream = connection.getInputStream();
368 InputSource inputSource = new InputSource(new InputStreamReader(inputStream, "UTF-8"));
369 NameFinderResultParser parser = new NameFinderResultParser();
370 SAXParserFactory.newInstance().newSAXParser().parse(inputSource, parser);
371 this.data = parser.getResult();
372 } catch(Exception e) {
373 if (canceled)
374 // ignore exception
375 return;
376 OsmTransferException ex = new OsmTransferException(e);
377 ex.setUrl(urlString);
378 lastException = ex;
379 }
380 }
381 }
382
383 static class NamedResultTableModel extends DefaultTableModel {
384 private ArrayList<SearchResult> data;
385 private ListSelectionModel selectionModel;
386
387 public NamedResultTableModel(ListSelectionModel selectionModel) {
388 data = new ArrayList<SearchResult>();
389 this.selectionModel = selectionModel;
390 }
391 @Override
392 public int getRowCount() {
393 if (data == null) return 0;
394 return data.size();
395 }
396
397 @Override
398 public Object getValueAt(int row, int column) {
399 if (data == null) return null;
400 return data.get(row);
401 }
402
403 public void setData(List<SearchResult> data) {
404 if (data == null) {
405 this.data.clear();
406 } else {
407 this.data =new ArrayList<SearchResult>(data);
408 }
409 fireTableDataChanged();
410 }
411 @Override
412 public boolean isCellEditable(int row, int column) {
413 return false;
414 }
415
416 public SearchResult getSelectedSearchResult() {
417 if (selectionModel.getMinSelectionIndex() < 0)
418 return null;
419 return data.get(selectionModel.getMinSelectionIndex());
420 }
421 }
422
423 static class NamedResultTableColumnModel extends DefaultTableColumnModel {
424 TableColumn col3 = null;
425 TableColumn col4 = null;
426 protected void createColumns() {
427 TableColumn col = null;
428 NamedResultCellRenderer renderer = new NamedResultCellRenderer();
429
430 // column 0 - Name
431 col = new TableColumn(0);
432 col.setHeaderValue(tr("Name"));
433 col.setResizable(true);
434 col.setPreferredWidth(200);
435 col.setCellRenderer(renderer);
436 addColumn(col);
437
438 // column 1 - Version
439 col = new TableColumn(1);
440 col.setHeaderValue(tr("Type"));
441 col.setResizable(true);
442 col.setPreferredWidth(100);
443 col.setCellRenderer(renderer);
444 addColumn(col);
445
446 // column 2 - Near
447 col3 = new TableColumn(2);
448 col3.setHeaderValue(servers[0].thirdcol);
449 col3.setResizable(true);
450 col3.setPreferredWidth(100);
451 col3.setCellRenderer(renderer);
452 addColumn(col3);
453
454 // column 3 - Zoom
455 col4 = new TableColumn(3);
456 col4.setHeaderValue(servers[0].fourthcol);
457 col4.setResizable(true);
458 col4.setPreferredWidth(50);
459 col4.setCellRenderer(renderer);
460 addColumn(col4);
461 }
462 public void setHeadlines(String third, String fourth) {
463 col3.setHeaderValue(third);
464 col4.setHeaderValue(fourth);
465 fireColumnMarginChanged();
466 }
467
468 public NamedResultTableColumnModel() {
469 createColumns();
470 }
471 }
472
473 class ListSelectionHandler implements ListSelectionListener {
474 public void valueChanged(ListSelectionEvent lse) {
475 SearchResult r = model.getSelectedSearchResult();
476 if (r != null) {
477 parent.boundingBoxChanged(r.getDownloadArea(), PlaceSelection.this);
478 }
479 }
480 }
481
482 static class NamedResultCellRenderer extends JLabel implements TableCellRenderer {
483
484 public NamedResultCellRenderer() {
485 setOpaque(true);
486 setBorder(BorderFactory.createEmptyBorder(2,2,2,2));
487 }
488
489 protected void reset() {
490 setText("");
491 setIcon(null);
492 }
493
494 protected void renderColor(boolean selected) {
495 if (selected) {
496 setForeground(UIManager.getColor("Table.selectionForeground"));
497 setBackground(UIManager.getColor("Table.selectionBackground"));
498 } else {
499 setForeground(UIManager.getColor("Table.foreground"));
500 setBackground(UIManager.getColor("Table.background"));
501 }
502 }
503
504 protected String lineWrapDescription(String description) {
505 StringBuffer ret = new StringBuffer();
506 StringBuffer line = new StringBuffer();
507 StringTokenizer tok = new StringTokenizer(description, " ");
508 while(tok.hasMoreElements()) {
509 String t = tok.nextToken();
510 if (line.length() == 0) {
511 line.append(t);
512 } else if (line.length() < 80) {
513 line.append(" ").append(t);
514 } else {
515 line.append(" ").append(t).append("<br>");
516 ret.append(line);
517 line = new StringBuffer();
518 }
519 }
520 ret.insert(0, "<html>");
521 ret.append("</html>");
522 return ret.toString();
523 }
524
525 public Component getTableCellRendererComponent(JTable table, Object value,
526 boolean isSelected, boolean hasFocus, int row, int column) {
527
528 reset();
529 renderColor(isSelected);
530
531 if (value == null) return this;
532 SearchResult sr = (SearchResult) value;
533 switch(column) {
534 case 0:
535 setText(sr.name);
536 break;
537 case 1:
538 setText(sr.info);
539 break;
540 case 2:
541 setText(sr.nearestPlace);
542 break;
543 case 3:
544 if(sr.bounds != null) {
545 setText(sr.bounds.toShortString(new DecimalFormat("0.000")));
546 } else {
547 setText(sr.zoom != 0 ? Integer.toString(sr.zoom) : tr("unknown"));
548 }
549 break;
550 }
551 setToolTipText(lineWrapDescription(sr.description));
552 return this;
553 }
554 }
555}
Note: See TracBrowser for help on using the repository browser.