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

Last change on this file since 16418 was 16418, checked in by simon04, 4 years ago

PlaceSelection: use GridBagLayout for better spacing

  • Property svn:eol-style set to native
File size: 16.4 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.event.ActionEvent;
11import java.awt.event.MouseAdapter;
12import java.awt.event.MouseEvent;
13import java.io.IOException;
14import java.io.Reader;
15import java.net.URL;
16import java.text.DecimalFormat;
17import java.util.ArrayList;
18import java.util.Collections;
19import java.util.List;
20import java.util.StringTokenizer;
21
22import javax.swing.AbstractAction;
23import javax.swing.BorderFactory;
24import javax.swing.DefaultListSelectionModel;
25import javax.swing.JButton;
26import javax.swing.JLabel;
27import javax.swing.JOptionPane;
28import javax.swing.JPanel;
29import javax.swing.JScrollPane;
30import javax.swing.JTable;
31import javax.swing.ListSelectionModel;
32import javax.swing.UIManager;
33import javax.swing.event.DocumentEvent;
34import javax.swing.event.DocumentListener;
35import javax.swing.event.ListSelectionEvent;
36import javax.swing.event.ListSelectionListener;
37import javax.swing.table.DefaultTableColumnModel;
38import javax.swing.table.DefaultTableModel;
39import javax.swing.table.TableCellRenderer;
40import javax.swing.table.TableColumn;
41import javax.xml.parsers.ParserConfigurationException;
42
43import org.openstreetmap.josm.data.Bounds;
44import org.openstreetmap.josm.gui.ExceptionDialogUtil;
45import org.openstreetmap.josm.gui.HelpAwareOptionPane;
46import org.openstreetmap.josm.gui.MainApplication;
47import org.openstreetmap.josm.gui.PleaseWaitRunnable;
48import org.openstreetmap.josm.gui.util.GuiHelper;
49import org.openstreetmap.josm.gui.widgets.HistoryComboBox;
50import org.openstreetmap.josm.gui.widgets.JosmComboBox;
51import org.openstreetmap.josm.io.NameFinder;
52import org.openstreetmap.josm.io.NameFinder.SearchResult;
53import org.openstreetmap.josm.io.OsmTransferException;
54import org.openstreetmap.josm.spi.preferences.Config;
55import org.openstreetmap.josm.tools.GBC;
56import org.openstreetmap.josm.tools.HttpClient;
57import org.openstreetmap.josm.tools.ImageProvider;
58import org.openstreetmap.josm.tools.Logging;
59import org.openstreetmap.josm.tools.Utils;
60import org.xml.sax.SAXException;
61import org.xml.sax.SAXParseException;
62
63/**
64 * Place selector.
65 * @since 1329
66 */
67public class PlaceSelection implements DownloadSelection {
68 private static final String HISTORY_KEY = "download.places.history";
69
70 private HistoryComboBox cbSearchExpression;
71 private NamedResultTableModel model;
72 private NamedResultTableColumnModel columnmodel;
73 private JTable tblSearchResults;
74 private DownloadDialog parent;
75 private static final Server[] SERVERS = {
76 new Server("Nominatim", NameFinder.NOMINATIM_URL, tr("Class Type"), tr("Bounds"))
77 };
78 private final JosmComboBox<Server> server = new JosmComboBox<>(SERVERS);
79
80 private static class Server {
81 public final String name;
82 public final String url;
83 public final String thirdcol;
84 public final String fourthcol;
85
86 Server(String n, String u, String t, String f) {
87 name = n;
88 url = u;
89 thirdcol = t;
90 fourthcol = f;
91 }
92
93 @Override
94 public String toString() {
95 return name;
96 }
97 }
98
99 protected JPanel buildSearchPanel() {
100 JPanel lpanel = new JPanel(new GridBagLayout());
101 JPanel panel = new JPanel(new GridBagLayout());
102
103 lpanel.add(new JLabel(tr("Choose the server for searching:")), GBC.std(0, 0).weight(0, 0).insets(0, 0, 5, 0));
104 lpanel.add(server, GBC.std(1, 0).fill(GBC.HORIZONTAL));
105 String s = Config.getPref().get("namefinder.server", SERVERS[0].name);
106 for (int i = 0; i < SERVERS.length; ++i) {
107 if (SERVERS[i].name.equals(s)) {
108 server.setSelectedIndex(i);
109 }
110 }
111 lpanel.add(new JLabel(tr("Enter a place name to search for:")), GBC.std(0, 1).weight(0, 0).insets(0, 0, 5, 0));
112
113 cbSearchExpression = new HistoryComboBox();
114 cbSearchExpression.setToolTipText(tr("Enter a place name to search for"));
115 cbSearchExpression.setPossibleItemsTopDown(Config.getPref().getList(HISTORY_KEY, Collections.emptyList()));
116 lpanel.add(cbSearchExpression, GBC.std(1, 1).fill(GBC.HORIZONTAL));
117
118 panel.add(lpanel, GBC.std().fill(GBC.HORIZONTAL).insets(5, 5, 0, 5));
119 SearchAction searchAction = new SearchAction();
120 JButton btnSearch = new JButton(searchAction);
121 cbSearchExpression.getEditorComponent().getDocument().addDocumentListener(searchAction);
122 cbSearchExpression.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 @Override
135 public void addGui(final DownloadDialog gui) {
136 JPanel panel = new JPanel(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 if (gui != null)
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
156 public void mouseClicked(MouseEvent e) {
157 if (e.getClickCount() > 1) {
158 SearchResult sr = model.getSelectedSearchResult();
159 if (sr != null) {
160 parent.startDownload(sr.getDownloadArea());
161 }
162 }
163 }
164 });
165 parent = gui;
166 }
167
168 @Override
169 public void setDownloadArea(Bounds area) {
170 tblSearchResults.clearSelection();
171 }
172
173 class SearchAction extends AbstractAction implements DocumentListener {
174
175 SearchAction() {
176 putValue(NAME, tr("Search..."));
177 new ImageProvider("dialogs", "search").getResource().attachImageIcon(this, true);
178 putValue(SHORT_DESCRIPTION, tr("Click to start searching for places"));
179 updateEnabledState();
180 }
181
182 @Override
183 public void actionPerformed(ActionEvent e) {
184 if (!isEnabled() || cbSearchExpression.getText().trim().isEmpty())
185 return;
186 cbSearchExpression.addCurrentItemToHistory();
187 Config.getPref().putList(HISTORY_KEY, cbSearchExpression.getHistory());
188 NameQueryTask task = new NameQueryTask(cbSearchExpression.getText());
189 MainApplication.worker.submit(task);
190 }
191
192 protected final void updateEnabledState() {
193 setEnabled(!cbSearchExpression.getText().trim().isEmpty());
194 }
195
196 @Override
197 public void changedUpdate(DocumentEvent e) {
198 updateEnabledState();
199 }
200
201 @Override
202 public void insertUpdate(DocumentEvent e) {
203 updateEnabledState();
204 }
205
206 @Override
207 public void removeUpdate(DocumentEvent e) {
208 updateEnabledState();
209 }
210 }
211
212 class NameQueryTask extends PleaseWaitRunnable {
213
214 private final String searchExpression;
215 private HttpClient connection;
216 private List<SearchResult> data;
217 private boolean canceled;
218 private final Server useserver;
219 private Exception lastException;
220
221 NameQueryTask(String searchExpression) {
222 super(tr("Querying name server"), false /* don't ignore exceptions */);
223 this.searchExpression = searchExpression;
224 useserver = (Server) server.getSelectedItem();
225 Config.getPref().put("namefinder.server", useserver.name);
226 }
227
228 @Override
229 protected void cancel() {
230 this.canceled = true;
231 synchronized (this) {
232 if (connection != null) {
233 connection.disconnect();
234 }
235 }
236 }
237
238 @Override
239 protected void finish() {
240 if (canceled)
241 return;
242 if (lastException != null) {
243 ExceptionDialogUtil.explainException(lastException);
244 return;
245 }
246 columnmodel.setHeadlines(useserver.thirdcol, useserver.fourthcol);
247 model.setData(this.data);
248 }
249
250 @Override
251 protected void realRun() throws SAXException, IOException, OsmTransferException {
252 String urlString = useserver.url+Utils.encodeUrl(searchExpression);
253
254 try {
255 getProgressMonitor().indeterminateSubTask(tr("Querying name server ..."));
256 URL url = new URL(urlString);
257 synchronized (this) {
258 connection = HttpClient.create(url);
259 connection.connect();
260 }
261 try (Reader reader = connection.getResponse().getContentReader()) {
262 data = NameFinder.parseSearchResults(reader);
263 }
264 } catch (SAXParseException e) {
265 if (!canceled) {
266 // Nominatim sometimes returns garbage, see #5934, #10643
267 Logging.log(Logging.LEVEL_WARN, tr("Error occurred with query ''{0}'': ''{1}''", urlString, e.getMessage()), e);
268 GuiHelper.runInEDTAndWait(() -> HelpAwareOptionPane.showOptionDialog(
269 MainApplication.getMainFrame(),
270 tr("Name server returned invalid data. Please try again."),
271 tr("Bad response"),
272 JOptionPane.WARNING_MESSAGE, null
273 ));
274 }
275 } catch (IOException | ParserConfigurationException e) {
276 if (!canceled) {
277 OsmTransferException ex = new OsmTransferException(e);
278 ex.setUrl(urlString);
279 lastException = ex;
280 }
281 }
282 }
283 }
284
285 static class NamedResultTableModel extends DefaultTableModel {
286 private transient List<SearchResult> data;
287 private final transient ListSelectionModel selectionModel;
288
289 NamedResultTableModel(ListSelectionModel selectionModel) {
290 data = new ArrayList<>();
291 this.selectionModel = selectionModel;
292 }
293
294 @Override
295 public int getRowCount() {
296 return data != null ? data.size() : 0;
297 }
298
299 @Override
300 public Object getValueAt(int row, int column) {
301 return data != null ? data.get(row) : null;
302 }
303
304 public void setData(List<SearchResult> data) {
305 if (data == null) {
306 this.data.clear();
307 } else {
308 this.data = new ArrayList<>(data);
309 }
310 fireTableDataChanged();
311 }
312
313 @Override
314 public boolean isCellEditable(int row, int column) {
315 return false;
316 }
317
318 public SearchResult getSelectedSearchResult() {
319 if (selectionModel.getMinSelectionIndex() < 0)
320 return null;
321 return data.get(selectionModel.getMinSelectionIndex());
322 }
323 }
324
325 static class NamedResultTableColumnModel extends DefaultTableColumnModel {
326 private TableColumn col3;
327 private TableColumn col4;
328
329 NamedResultTableColumnModel() {
330 createColumns();
331 }
332
333 protected final void createColumns() {
334 TableColumn col;
335 NamedResultCellRenderer renderer = new NamedResultCellRenderer();
336
337 // column 0 - Name
338 col = new TableColumn(0);
339 col.setHeaderValue(tr("Name"));
340 col.setResizable(true);
341 col.setPreferredWidth(200);
342 col.setCellRenderer(renderer);
343 addColumn(col);
344
345 // column 1 - Version
346 col = new TableColumn(1);
347 col.setHeaderValue(tr("Type"));
348 col.setResizable(true);
349 col.setPreferredWidth(100);
350 col.setCellRenderer(renderer);
351 addColumn(col);
352
353 // column 2 - Near
354 col3 = new TableColumn(2);
355 col3.setHeaderValue(SERVERS[0].thirdcol);
356 col3.setResizable(true);
357 col3.setPreferredWidth(100);
358 col3.setCellRenderer(renderer);
359 addColumn(col3);
360
361 // column 3 - Zoom
362 col4 = new TableColumn(3);
363 col4.setHeaderValue(SERVERS[0].fourthcol);
364 col4.setResizable(true);
365 col4.setPreferredWidth(50);
366 col4.setCellRenderer(renderer);
367 addColumn(col4);
368 }
369
370 public void setHeadlines(String third, String fourth) {
371 col3.setHeaderValue(third);
372 col4.setHeaderValue(fourth);
373 fireColumnMarginChanged();
374 }
375 }
376
377 class ListSelectionHandler implements ListSelectionListener {
378 @Override
379 public void valueChanged(ListSelectionEvent lse) {
380 SearchResult r = model.getSelectedSearchResult();
381 if (r != null) {
382 parent.boundingBoxChanged(r.getDownloadArea(), PlaceSelection.this);
383 }
384 }
385 }
386
387 static class NamedResultCellRenderer extends JLabel implements TableCellRenderer {
388
389 /**
390 * Constructs a new {@code NamedResultCellRenderer}.
391 */
392 NamedResultCellRenderer() {
393 setOpaque(true);
394 setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2));
395 }
396
397 protected void reset() {
398 setText("");
399 setIcon(null);
400 }
401
402 protected void renderColor(boolean selected) {
403 if (selected) {
404 setForeground(UIManager.getColor("Table.selectionForeground"));
405 setBackground(UIManager.getColor("Table.selectionBackground"));
406 } else {
407 setForeground(UIManager.getColor("Table.foreground"));
408 setBackground(UIManager.getColor("Table.background"));
409 }
410 }
411
412 protected String lineWrapDescription(String description) {
413 StringBuilder ret = new StringBuilder();
414 StringBuilder line = new StringBuilder();
415 StringTokenizer tok = new StringTokenizer(description, " ");
416 while (tok.hasMoreElements()) {
417 String t = tok.nextToken();
418 if (line.length() == 0) {
419 line.append(t);
420 } else if (line.length() < 80) {
421 line.append(' ').append(t);
422 } else {
423 line.append(' ').append(t).append("<br>");
424 ret.append(line);
425 line = new StringBuilder();
426 }
427 }
428 ret.insert(0, "<html>");
429 ret.append("</html>");
430 return ret.toString();
431 }
432
433 @Override
434 public Component getTableCellRendererComponent(JTable table, Object value,
435 boolean isSelected, boolean hasFocus, int row, int column) {
436
437 reset();
438 renderColor(isSelected);
439
440 if (value == null)
441 return this;
442 SearchResult sr = (SearchResult) value;
443 switch(column) {
444 case 0:
445 setText(sr.getName());
446 break;
447 case 1:
448 setText(sr.getInfo());
449 break;
450 case 2:
451 setText(sr.getNearestPlace());
452 break;
453 case 3:
454 if (sr.getBounds() != null) {
455 setText(sr.getBounds().toShortString(new DecimalFormat("0.000")));
456 } else {
457 setText(sr.getZoom() != 0 ? Integer.toString(sr.getZoom()) : tr("unknown"));
458 }
459 break;
460 default: // Do nothing
461 }
462 setToolTipText(lineWrapDescription(sr.getDescription()));
463 return this;
464 }
465 }
466}
Note: See TracBrowser for help on using the repository browser.