1 | package org.openstreetmap.josm.gui.download;
|
---|
2 |
|
---|
3 | import static org.openstreetmap.josm.tools.I18n.tr;
|
---|
4 |
|
---|
5 | import java.awt.Component;
|
---|
6 | import java.awt.Cursor;
|
---|
7 | import java.awt.Dimension;
|
---|
8 | import java.awt.GridBagLayout;
|
---|
9 | import java.awt.event.ActionEvent;
|
---|
10 | import java.awt.event.ActionListener;
|
---|
11 | import java.awt.event.MouseAdapter;
|
---|
12 | import java.awt.event.MouseEvent;
|
---|
13 | import java.io.InputStream;
|
---|
14 | import java.io.InputStreamReader;
|
---|
15 | import java.net.HttpURLConnection;
|
---|
16 | import java.net.URL;
|
---|
17 |
|
---|
18 | import javax.swing.JButton;
|
---|
19 | import javax.swing.JComponent;
|
---|
20 | import javax.swing.JLabel;
|
---|
21 | import javax.swing.JOptionPane;
|
---|
22 | import javax.swing.JPanel;
|
---|
23 | import javax.swing.JScrollPane;
|
---|
24 | import javax.swing.JTable;
|
---|
25 | import javax.swing.JTextField;
|
---|
26 | import javax.swing.ListSelectionModel;
|
---|
27 | import javax.swing.event.ListSelectionEvent;
|
---|
28 | import javax.swing.event.ListSelectionListener;
|
---|
29 | import javax.swing.table.DefaultTableCellRenderer;
|
---|
30 | import javax.swing.table.DefaultTableModel;
|
---|
31 | import javax.xml.parsers.SAXParserFactory;
|
---|
32 |
|
---|
33 | import org.openstreetmap.josm.Main;
|
---|
34 | import org.openstreetmap.josm.gui.OptionPaneUtil;
|
---|
35 | import org.openstreetmap.josm.gui.download.DownloadDialog;
|
---|
36 | import org.openstreetmap.josm.gui.download.DownloadSelection;
|
---|
37 | import org.openstreetmap.josm.tools.GBC;
|
---|
38 | import org.xml.sax.Attributes;
|
---|
39 | import org.xml.sax.InputSource;
|
---|
40 | import org.xml.sax.SAXException;
|
---|
41 | import org.xml.sax.helpers.DefaultHandler;
|
---|
42 |
|
---|
43 | public class PlaceSelection implements DownloadSelection {
|
---|
44 |
|
---|
45 | private JTextField searchTerm = new JTextField();
|
---|
46 | private JButton submitSearch = new JButton(tr("Search..."));
|
---|
47 | private DefaultTableModel searchResults = new DefaultTableModel() {
|
---|
48 | @Override public boolean isCellEditable(int row, int col) { return false; }
|
---|
49 | };
|
---|
50 | private JTable searchResultDisplay = new JTable(searchResults);
|
---|
51 | private boolean updatingSelf;
|
---|
52 |
|
---|
53 | /**
|
---|
54 | * Data storage for search results.
|
---|
55 | */
|
---|
56 | class SearchResult
|
---|
57 | {
|
---|
58 | public String name;
|
---|
59 | public String type;
|
---|
60 | public String nearestPlace;
|
---|
61 | public String description;
|
---|
62 | public double lat;
|
---|
63 | public double lon;
|
---|
64 | public int zoom;
|
---|
65 | }
|
---|
66 |
|
---|
67 | /**
|
---|
68 | * A very primitive parser for the name finder's output.
|
---|
69 | * Structure of xml described here: http://wiki.openstreetmap.org/index.php/Name_finder
|
---|
70 | *
|
---|
71 | */
|
---|
72 | private class Parser extends DefaultHandler
|
---|
73 | {
|
---|
74 | private SearchResult currentResult = null;
|
---|
75 | private StringBuffer description = null;
|
---|
76 | private int depth = 0;
|
---|
77 | /**
|
---|
78 | * Detect starting elements.
|
---|
79 | *
|
---|
80 | */
|
---|
81 | @Override public void startElement(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException
|
---|
82 | {
|
---|
83 | depth++;
|
---|
84 | try
|
---|
85 | {
|
---|
86 | if (qName.equals("searchresults"))
|
---|
87 | {
|
---|
88 | searchResults.setRowCount(0);
|
---|
89 | }
|
---|
90 | else if (qName.equals("named") && (depth == 2))
|
---|
91 | {
|
---|
92 | currentResult = new PlaceSelection.SearchResult();
|
---|
93 | currentResult.name = atts.getValue("name");
|
---|
94 | currentResult.type = atts.getValue("info");
|
---|
95 | currentResult.lat = Double.parseDouble(atts.getValue("lat"));
|
---|
96 | currentResult.lon = Double.parseDouble(atts.getValue("lon"));
|
---|
97 | currentResult.zoom = Integer.parseInt(atts.getValue("zoom"));
|
---|
98 | searchResults.addRow(new Object[] { currentResult, currentResult, currentResult, currentResult });
|
---|
99 | }
|
---|
100 | else if (qName.equals("description") && (depth == 3))
|
---|
101 | {
|
---|
102 | description = new StringBuffer();
|
---|
103 | }
|
---|
104 | else if (qName.equals("named") && (depth == 4))
|
---|
105 | {
|
---|
106 | // this is a "named" place in the nearest places list.
|
---|
107 | String info = atts.getValue("info");
|
---|
108 | if ("city".equals(info) || "town".equals(info) || "village".equals(info)) {
|
---|
109 | currentResult.nearestPlace = atts.getValue("name");
|
---|
110 | }
|
---|
111 | }
|
---|
112 | }
|
---|
113 | catch (NumberFormatException x)
|
---|
114 | {
|
---|
115 | x.printStackTrace(); // SAXException does not chain correctly
|
---|
116 | throw new SAXException(x.getMessage(), x);
|
---|
117 | }
|
---|
118 | catch (NullPointerException x)
|
---|
119 | {
|
---|
120 | x.printStackTrace(); // SAXException does not chain correctly
|
---|
121 | throw new SAXException(tr("Null pointer exception, possibly some missing tags."), x);
|
---|
122 | }
|
---|
123 | }
|
---|
124 | /**
|
---|
125 | * Detect ending elements.
|
---|
126 | */
|
---|
127 | @Override public void endElement(String namespaceURI, String localName, String qName) throws SAXException
|
---|
128 | {
|
---|
129 |
|
---|
130 | if (qName.equals("searchresults"))
|
---|
131 | {
|
---|
132 | }
|
---|
133 | else if (qName.equals("description") && description != null)
|
---|
134 | {
|
---|
135 | currentResult.description = description.toString();
|
---|
136 | description = null;
|
---|
137 | }
|
---|
138 | depth--;
|
---|
139 |
|
---|
140 | }
|
---|
141 | /**
|
---|
142 | * Read characters for description.
|
---|
143 | */
|
---|
144 | @Override public void characters(char[] data, int start, int length) throws org.xml.sax.SAXException
|
---|
145 | {
|
---|
146 | if (description != null)
|
---|
147 | {
|
---|
148 | description.append(data, start, length);
|
---|
149 | }
|
---|
150 | }
|
---|
151 | }
|
---|
152 |
|
---|
153 | /**
|
---|
154 | * This queries David Earl's server. Needless to say, stuff should be configurable, and
|
---|
155 | * error handling improved.
|
---|
156 | */
|
---|
157 | public void queryServer(final JComponent component)
|
---|
158 | {
|
---|
159 | final Cursor oldCursor = component.getCursor();
|
---|
160 |
|
---|
161 | // had to put this in a thread as it wouldn't update the cursor properly before.
|
---|
162 | Runnable r = new Runnable() {
|
---|
163 | public void run() {
|
---|
164 | try
|
---|
165 | {
|
---|
166 | String searchtext = searchTerm.getText();
|
---|
167 | if(searchtext.length()==0)
|
---|
168 | {
|
---|
169 | OptionPaneUtil.showMessageDialog(
|
---|
170 | Main.parent,
|
---|
171 | tr("Please enter a search string"),
|
---|
172 | tr("Information"),
|
---|
173 | JOptionPane.INFORMATION_MESSAGE
|
---|
174 | );
|
---|
175 | }
|
---|
176 | else
|
---|
177 | {
|
---|
178 | component.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
|
---|
179 | component.repaint();
|
---|
180 | URL url = new URL("http://gazetteer.openstreetmap.org/namefinder/search.xml?find="
|
---|
181 | +java.net.URLEncoder.encode(searchTerm.getText(), "UTF-8"));
|
---|
182 | HttpURLConnection activeConnection = (HttpURLConnection)url.openConnection();
|
---|
183 | //System.out.println("got return: "+activeConnection.getResponseCode());
|
---|
184 | activeConnection.setConnectTimeout(15000);
|
---|
185 | InputStream inputStream = activeConnection.getInputStream();
|
---|
186 | InputSource inputSource = new InputSource(new InputStreamReader(inputStream, "UTF-8"));
|
---|
187 | SAXParserFactory.newInstance().newSAXParser().parse(inputSource, new Parser());
|
---|
188 | }
|
---|
189 | }
|
---|
190 | catch (Exception x)
|
---|
191 | {
|
---|
192 | JOptionPane.showMessageDialog(Main.parent,tr("Cannot read place search results from server"));
|
---|
193 | x.printStackTrace();
|
---|
194 | }
|
---|
195 | component.setCursor(oldCursor);
|
---|
196 | }
|
---|
197 | };
|
---|
198 | new Thread(r).start();
|
---|
199 | }
|
---|
200 |
|
---|
201 | /**
|
---|
202 | * Adds a new tab to the download dialog in JOSM.
|
---|
203 | *
|
---|
204 | * This method is, for all intents and purposes, the constructor for this class.
|
---|
205 | */
|
---|
206 | public void addGui(final DownloadDialog gui) {
|
---|
207 | JPanel panel = new JPanel();
|
---|
208 | panel.setLayout(new GridBagLayout());
|
---|
209 |
|
---|
210 | // this is manually tuned so that it looks nice on a GNOME
|
---|
211 | // desktop - maybe needs some cross platform proofing.
|
---|
212 | panel.add(new JLabel(tr("Enter a place name to search for:")), GBC.eol().insets(5, 5, 5, 5));
|
---|
213 | panel.add(searchTerm, GBC.std().fill(GBC.HORIZONTAL).insets(5, 0, 5, 4));
|
---|
214 | panel.add(submitSearch, GBC.eol().insets(5, 0, 5, 5));
|
---|
215 | Dimension btnSize = submitSearch.getPreferredSize();
|
---|
216 | btnSize.setSize(btnSize.width, btnSize.height * 0.8);
|
---|
217 | submitSearch.setPreferredSize(btnSize);
|
---|
218 |
|
---|
219 | GBC c = GBC.std().fill().insets(5, 0, 5, 5);
|
---|
220 | c.gridwidth = 2;
|
---|
221 | JScrollPane scrollPane = new JScrollPane(searchResultDisplay);
|
---|
222 | scrollPane.setPreferredSize(new Dimension(200,200));
|
---|
223 | panel.add(scrollPane, c);
|
---|
224 | gui.tabpane.add(panel, tr("Places"));
|
---|
225 |
|
---|
226 | scrollPane.setPreferredSize(scrollPane.getPreferredSize());
|
---|
227 |
|
---|
228 | // when the button is clicked
|
---|
229 | submitSearch.addActionListener(new ActionListener() {
|
---|
230 | public void actionPerformed(ActionEvent e) {
|
---|
231 | queryServer(gui);
|
---|
232 | }
|
---|
233 | });
|
---|
234 |
|
---|
235 | searchTerm.addActionListener(new ActionListener() {
|
---|
236 | public void actionPerformed(ActionEvent e) {
|
---|
237 | queryServer(gui);
|
---|
238 | }
|
---|
239 | });
|
---|
240 |
|
---|
241 | searchResults.addColumn(tr("name"));
|
---|
242 | searchResults.addColumn(tr("type"));
|
---|
243 | searchResults.addColumn(tr("near"));
|
---|
244 | searchResults.addColumn(tr("zoom"));
|
---|
245 |
|
---|
246 | // TODO - this is probably not the coolest way to set relative sizes?
|
---|
247 | searchResultDisplay.getColumn(tr("name")).setPreferredWidth(200);
|
---|
248 | searchResultDisplay.getColumn(tr("type")).setPreferredWidth(100);
|
---|
249 | searchResultDisplay.getColumn(tr("near")).setPreferredWidth(100);
|
---|
250 | searchResultDisplay.getColumn(tr("zoom")).setPreferredWidth(50);
|
---|
251 | searchResultDisplay.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
---|
252 |
|
---|
253 | // display search results in a table. for simplicity, the table contains
|
---|
254 | // the same SearchResult object in each of the four columns, but it is rendered
|
---|
255 | // differently depending on the column.
|
---|
256 | searchResultDisplay.setDefaultRenderer(Object.class, new DefaultTableCellRenderer() {
|
---|
257 | @Override public Component getTableCellRendererComponent(JTable table, Object value,
|
---|
258 | boolean isSelected, boolean hasFocus, int row, int column) {
|
---|
259 | super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
|
---|
260 | if (value != null) {
|
---|
261 | SearchResult sr = (SearchResult) value;
|
---|
262 | switch(column) {
|
---|
263 | case 0:
|
---|
264 | setText(sr.name);
|
---|
265 | break;
|
---|
266 | case 1:
|
---|
267 | setText(sr.type);
|
---|
268 | break;
|
---|
269 | case 2:
|
---|
270 | setText(sr.nearestPlace);
|
---|
271 | break;
|
---|
272 | case 3:
|
---|
273 | setText(Integer.toString(sr.zoom));
|
---|
274 | break;
|
---|
275 | }
|
---|
276 | setToolTipText("<html>"+((SearchResult)value).description+"</html>");
|
---|
277 | }
|
---|
278 | return this;
|
---|
279 | }
|
---|
280 | });
|
---|
281 |
|
---|
282 | // if item is selected in list, notify dialog
|
---|
283 | searchResultDisplay.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
|
---|
284 | public void valueChanged(ListSelectionEvent lse) {
|
---|
285 | if (lse.getValueIsAdjusting()) return;
|
---|
286 | SearchResult r = null;
|
---|
287 | try
|
---|
288 | {
|
---|
289 | r = (SearchResult) searchResults.getValueAt(lse.getFirstIndex(), 0);
|
---|
290 | }
|
---|
291 | catch (Exception x)
|
---|
292 | {
|
---|
293 | // Ignore
|
---|
294 | }
|
---|
295 | if (r != null)
|
---|
296 | {
|
---|
297 | double size = 180.0 / Math.pow(2, r.zoom);
|
---|
298 | gui.minlat = r.lat - size / 2;
|
---|
299 | gui.maxlat = r.lat + size / 2;
|
---|
300 | gui.minlon = r.lon - size;
|
---|
301 | gui.maxlon = r.lon + size;
|
---|
302 | updatingSelf = true;
|
---|
303 | gui.boundingBoxChanged(null);
|
---|
304 | updatingSelf = false;
|
---|
305 | }
|
---|
306 | }
|
---|
307 | });
|
---|
308 |
|
---|
309 | // TODO - we'd like to finish the download dialog upon double-click but
|
---|
310 | // don't know how to bypass the JOptionPane in which the whole thing is
|
---|
311 | // displayed.
|
---|
312 | searchResultDisplay.addMouseListener(new MouseAdapter() {
|
---|
313 | @Override public void mouseClicked(MouseEvent e) {
|
---|
314 | if (e.getClickCount() > 1) {
|
---|
315 | if (searchResultDisplay.getSelectionModel().getMinSelectionIndex() > -1) {
|
---|
316 | // add sensible action here.
|
---|
317 | }
|
---|
318 | }
|
---|
319 | }
|
---|
320 | });
|
---|
321 |
|
---|
322 | }
|
---|
323 |
|
---|
324 | // if bounding box selected on other tab, de-select item
|
---|
325 | public void boundingBoxChanged(DownloadDialog gui) {
|
---|
326 | if (!updatingSelf) {
|
---|
327 | searchResultDisplay.clearSelection();
|
---|
328 | }
|
---|
329 | }
|
---|
330 | }
|
---|