source: josm/trunk/src/org/openstreetmap/josm/gui/preferences/projection/CodeProjectionChoice.java@ 13515

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

fix #16081 - improve selection of projections by EPSG code:

  • display projection name in a new two-column table
  • allow to sort the table by code or name
  • allow to search by projection code and name
  • Property svn:eol-style set to native
File size: 8.7 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.preferences.projection;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.Dimension;
7import java.awt.GridBagLayout;
8import java.awt.event.ActionListener;
9import java.io.Serializable;
10import java.util.ArrayList;
11import java.util.Collection;
12import java.util.Collections;
13import java.util.Comparator;
14import java.util.List;
15import java.util.Locale;
16import java.util.regex.Matcher;
17import java.util.regex.Pattern;
18
19import javax.swing.JPanel;
20import javax.swing.JScrollPane;
21import javax.swing.JTable;
22import javax.swing.event.DocumentEvent;
23import javax.swing.event.DocumentListener;
24import javax.swing.event.ListSelectionEvent;
25import javax.swing.event.ListSelectionListener;
26import javax.swing.table.AbstractTableModel;
27
28import org.openstreetmap.josm.data.projection.Projection;
29import org.openstreetmap.josm.data.projection.Projections;
30import org.openstreetmap.josm.gui.widgets.JosmTextField;
31import org.openstreetmap.josm.tools.GBC;
32
33/**
34 * Projection choice that lists all known projects by code.
35 * @since 5634
36 */
37public class CodeProjectionChoice extends AbstractProjectionChoice implements SubPrefsOptions {
38
39 private String code;
40
41 /**
42 * Constructs a new {@code CodeProjectionChoice}.
43 */
44 public CodeProjectionChoice() {
45 super(tr("By Code (EPSG)"), /* NO-ICON */ "core:code");
46 }
47
48 private static class CodeSelectionPanel extends JPanel implements ListSelectionListener, DocumentListener {
49
50 private final JosmTextField filter = new JosmTextField(30);
51 private final ProjectionCodeModel model = new ProjectionCodeModel();
52 private JTable table;
53 private final List<String> data;
54 private final List<String> filteredData;
55 private static final String DEFAULT_CODE = "EPSG:3857";
56 private String lastCode = DEFAULT_CODE;
57 private final transient ActionListener listener;
58
59 CodeSelectionPanel(String initialCode, ActionListener listener) {
60 this.listener = listener;
61 data = new ArrayList<>(Projections.getAllProjectionCodes());
62 data.sort(new CodeComparator());
63 filteredData = new ArrayList<>(data);
64 build();
65 setCode(initialCode != null ? initialCode : DEFAULT_CODE);
66 table.getSelectionModel().addListSelectionListener(this);
67 }
68
69 /**
70 * List model for the filtered view on the list of all codes.
71 */
72 private class ProjectionCodeModel extends AbstractTableModel {
73 @Override
74 public int getRowCount() {
75 return filteredData.size();
76 }
77
78 @Override
79 public String getValueAt(int index, int column) {
80 if (index >= 0 && index < filteredData.size()) {
81 String code = filteredData.get(index);
82 switch (column) {
83 case 0: return code;
84 case 1: return Projections.getProjectionByCode(code).toString();
85 default: break;
86 }
87 }
88 return null;
89 }
90
91 @Override
92 public int getColumnCount() {
93 return 2;
94 }
95
96 @Override
97 public String getColumnName(int column) {
98 switch (column) {
99 case 0: return tr("Projection code");
100 case 1: return tr("Projection name");
101 default: return super.getColumnName(column);
102 }
103 }
104 }
105
106 private void build() {
107 filter.setColumns(40);
108 filter.getDocument().addDocumentListener(this);
109
110 table = new JTable(model);
111 table.setAutoCreateRowSorter(true);
112 JScrollPane scroll = new JScrollPane(table);
113 scroll.setPreferredSize(new Dimension(200, 214));
114
115 this.setLayout(new GridBagLayout());
116 this.add(filter, GBC.eol().weight(1.0, 0.0));
117 this.add(scroll, GBC.eol().fill(GBC.HORIZONTAL));
118 }
119
120 public String getCode() {
121 int idx = table.getSelectedRow();
122 if (idx == -1)
123 return lastCode;
124 return filteredData.get(table.convertRowIndexToModel(table.getSelectedRow()));
125 }
126
127 public final void setCode(String code) {
128 int idx = filteredData.indexOf(code);
129 if (idx != -1) {
130 selectRow(idx);
131 }
132 }
133
134 private void selectRow(int idx) {
135 table.setRowSelectionInterval(idx, idx);
136 ensureRowIsVisible(idx);
137 }
138
139 private void ensureRowIsVisible(int idx) {
140 table.scrollRectToVisible(table.getCellRect(idx, 0, true));
141 }
142
143 @Override
144 public void valueChanged(ListSelectionEvent e) {
145 listener.actionPerformed(null);
146 lastCode = getCode();
147 }
148
149 @Override
150 public void insertUpdate(DocumentEvent e) {
151 updateFilter();
152 }
153
154 @Override
155 public void removeUpdate(DocumentEvent e) {
156 updateFilter();
157 }
158
159 @Override
160 public void changedUpdate(DocumentEvent e) {
161 updateFilter();
162 }
163
164 private void updateFilter() {
165 filteredData.clear();
166 String filterTxt = filter.getText().trim().toLowerCase(Locale.ENGLISH);
167 for (String code : data) {
168 if (code.toLowerCase(Locale.ENGLISH).contains(filterTxt)
169 || Projections.getProjectionByCode(code).toString().toLowerCase(Locale.ENGLISH).contains(filterTxt)) {
170 filteredData.add(code);
171 }
172 }
173 model.fireTableDataChanged();
174 int idx = filteredData.indexOf(lastCode);
175 if (idx == -1) {
176 table.clearSelection();
177 if (table.getModel().getRowCount() > 0) {
178 ensureRowIsVisible(0);
179 }
180 } else {
181 selectRow(idx);
182 }
183 }
184 }
185
186 /**
187 * Comparator that compares the number part of the code numerically.
188 */
189 public static class CodeComparator implements Comparator<String>, Serializable {
190 private static final long serialVersionUID = 1L;
191 private final Pattern codePattern = Pattern.compile("([a-zA-Z]+):(\\d+)");
192
193 @Override
194 public int compare(String c1, String c2) {
195 Matcher matcher1 = codePattern.matcher(c1);
196 Matcher matcher2 = codePattern.matcher(c2);
197 if (matcher1.matches()) {
198 if (matcher2.matches()) {
199 int cmp1 = matcher1.group(1).compareTo(matcher2.group(1));
200 if (cmp1 != 0)
201 return cmp1;
202 int num1 = Integer.parseInt(matcher1.group(2));
203 int num2 = Integer.parseInt(matcher2.group(2));
204 return Integer.compare(num1, num2);
205 } else
206 return -1;
207 } else if (matcher2.matches())
208 return 1;
209 return c1.compareTo(c2);
210 }
211 }
212
213 @Override
214 public Projection getProjection() {
215 return Projections.getProjectionByCode(code);
216 }
217
218 @Override
219 public String getCurrentCode() {
220 // not needed - getProjection() is overridden
221 throw new UnsupportedOperationException();
222 }
223
224 @Override
225 public String getProjectionName() {
226 // not needed - getProjection() is overridden
227 throw new UnsupportedOperationException();
228 }
229
230 @Override
231 public void setPreferences(Collection<String> args) {
232 if (args != null && !args.isEmpty()) {
233 code = args.iterator().next();
234 }
235 }
236
237 @Override
238 public JPanel getPreferencePanel(ActionListener listener) {
239 return new CodeSelectionPanel(code, listener);
240 }
241
242 @Override
243 public Collection<String> getPreferences(JPanel panel) {
244 if (!(panel instanceof CodeSelectionPanel)) {
245 throw new IllegalArgumentException("Unsupported panel: "+panel);
246 }
247 CodeSelectionPanel csPanel = (CodeSelectionPanel) panel;
248 return Collections.singleton(csPanel.getCode());
249 }
250
251 /* don't return all possible codes - this projection choice it too generic */
252 @Override
253 public String[] allCodes() {
254 return new String[0];
255 }
256
257 /* not needed since allCodes() returns empty array */
258 @Override
259 public Collection<String> getPreferencesFromCode(String code) {
260 return null;
261 }
262
263 @Override
264 public boolean showProjectionCode() {
265 return true;
266 }
267
268 @Override
269 public boolean showProjectionName() {
270 return true;
271 }
272
273}
Note: See TracBrowser for help on using the repository browser.