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

Last change on this file since 12634 was 12634, checked in by Don-vip, 4 months ago

see #15182 - deprecate Main.worker, replace it by gui.MainApplication.worker + code refactoring to make sure only editor packages use it

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