001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.preferences.projection; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005 006import java.awt.Dimension; 007import java.awt.GridBagLayout; 008import java.awt.event.ActionListener; 009import java.io.Serializable; 010import java.util.ArrayList; 011import java.util.Collection; 012import java.util.Collections; 013import java.util.Comparator; 014import java.util.List; 015import java.util.Locale; 016import java.util.regex.Matcher; 017import java.util.regex.Pattern; 018 019import javax.swing.AbstractListModel; 020import javax.swing.JList; 021import javax.swing.JPanel; 022import javax.swing.JScrollPane; 023import javax.swing.event.DocumentEvent; 024import javax.swing.event.DocumentListener; 025import javax.swing.event.ListSelectionEvent; 026import javax.swing.event.ListSelectionListener; 027 028import org.openstreetmap.josm.data.projection.Projection; 029import org.openstreetmap.josm.data.projection.Projections; 030import org.openstreetmap.josm.gui.widgets.JosmTextField; 031import org.openstreetmap.josm.tools.GBC; 032 033/** 034 * Projection choice that lists all known projects by code. 035 */ 036public class CodeProjectionChoice extends AbstractProjectionChoice implements SubPrefsOptions { 037 038 private String code; 039 040 /** 041 * Constructs a new {@code CodeProjectionChoice}. 042 */ 043 public CodeProjectionChoice() { 044 super(tr("By Code (EPSG)"), /* NO-ICON */ "core:code"); 045 } 046 047 private static class CodeSelectionPanel extends JPanel implements ListSelectionListener, DocumentListener { 048 049 public JosmTextField filter; 050 private ProjectionCodeListModel model; 051 public JList<String> selectionList; 052 private List<String> data; 053 private List<String> filteredData; 054 private static final String DEFAULT_CODE = "EPSG:3857"; 055 private String lastCode = DEFAULT_CODE; 056 private transient ActionListener listener; 057 058 CodeSelectionPanel(String initialCode, ActionListener listener) { 059 this.listener = listener; 060 data = new ArrayList<>(Projections.getAllProjectionCodes()); 061 Collections.sort(data, new CodeComparator()); 062 filteredData = new ArrayList<>(data); 063 build(); 064 setCode(initialCode != null ? initialCode : DEFAULT_CODE); 065 selectionList.addListSelectionListener(this); 066 } 067 068 /** 069 * Comparator that compares the number part of the code numerically. 070 */ 071 private static class CodeComparator implements Comparator<String>, Serializable { 072 private static final long serialVersionUID = 1L; 073 private final Pattern codePattern = Pattern.compile("([a-zA-Z]+):(\\d+)"); 074 075 @Override 076 public int compare(String c1, String c2) { 077 Matcher matcher1 = codePattern.matcher(c1); 078 Matcher matcher2 = codePattern.matcher(c2); 079 if (matcher1.matches()) { 080 if (matcher2.matches()) { 081 int cmp1 = matcher1.group(1).compareTo(matcher2.group(1)); 082 if (cmp1 != 0) return cmp1; 083 int num1 = Integer.parseInt(matcher1.group(2)); 084 int num2 = Integer.parseInt(matcher2.group(2)); 085 return Integer.compare(num1, num2); 086 } else 087 return -1; 088 } else if (matcher2.matches()) 089 return 1; 090 return c1.compareTo(c2); 091 } 092 } 093 094 /** 095 * List model for the filtered view on the list of all codes. 096 */ 097 private class ProjectionCodeListModel extends AbstractListModel<String> { 098 @Override 099 public int getSize() { 100 return filteredData.size(); 101 } 102 103 @Override 104 public String getElementAt(int index) { 105 if (index >= 0 && index < filteredData.size()) 106 return filteredData.get(index); 107 else 108 return null; 109 } 110 111 public void fireContentsChanged() { 112 fireContentsChanged(this, 0, this.getSize()-1); 113 } 114 } 115 116 private void build() { 117 filter = new JosmTextField(30); 118 filter.setColumns(10); 119 filter.getDocument().addDocumentListener(this); 120 121 selectionList = new JList<>(data.toArray(new String[0])); 122 selectionList.setModel(model = new ProjectionCodeListModel()); 123 JScrollPane scroll = new JScrollPane(selectionList); 124 scroll.setPreferredSize(new Dimension(200, 214)); 125 126 this.setLayout(new GridBagLayout()); 127 this.add(filter, GBC.eol().weight(1.0, 0.0)); 128 this.add(scroll, GBC.eol()); 129 } 130 131 public String getCode() { 132 int idx = selectionList.getSelectedIndex(); 133 if (idx == -1) return lastCode; 134 return filteredData.get(selectionList.getSelectedIndex()); 135 } 136 137 public final void setCode(String code) { 138 int idx = filteredData.indexOf(code); 139 if (idx != -1) { 140 selectionList.setSelectedIndex(idx); 141 selectionList.ensureIndexIsVisible(idx); 142 } 143 } 144 145 @Override 146 public void valueChanged(ListSelectionEvent e) { 147 listener.actionPerformed(null); 148 lastCode = getCode(); 149 } 150 151 @Override 152 public void insertUpdate(DocumentEvent e) { 153 updateFilter(); 154 } 155 156 @Override 157 public void removeUpdate(DocumentEvent e) { 158 updateFilter(); 159 } 160 161 @Override 162 public void changedUpdate(DocumentEvent e) { 163 updateFilter(); 164 } 165 166 private void updateFilter() { 167 filteredData.clear(); 168 String filterTxt = filter.getText().trim().toLowerCase(Locale.ENGLISH); 169 for (String code : data) { 170 if (code.toLowerCase(Locale.ENGLISH).contains(filterTxt)) { 171 filteredData.add(code); 172 } 173 } 174 model.fireContentsChanged(); 175 int idx = filteredData.indexOf(lastCode); 176 if (idx == -1) { 177 selectionList.clearSelection(); 178 if (selectionList.getModel().getSize() > 0) { 179 selectionList.ensureIndexIsVisible(0); 180 } 181 } else { 182 selectionList.setSelectedIndex(idx); 183 selectionList.ensureIndexIsVisible(idx); 184 } 185 } 186 } 187 188 @Override 189 public Projection getProjection() { 190 return Projections.getProjectionByCode(code); 191 } 192 193 @Override 194 public String getCurrentCode() { 195 // not needed - getProjection() is overridden 196 throw new UnsupportedOperationException(); 197 } 198 199 @Override 200 public String getProjectionName() { 201 // not needed - getProjection() is overridden 202 throw new UnsupportedOperationException(); 203 } 204 205 @Override 206 public void setPreferences(Collection<String> args) { 207 if (args != null && !args.isEmpty()) { 208 code = args.iterator().next(); 209 } 210 } 211 212 @Override 213 public JPanel getPreferencePanel(ActionListener listener) { 214 return new CodeSelectionPanel(code, listener); 215 } 216 217 @Override 218 public Collection<String> getPreferences(JPanel panel) { 219 if (!(panel instanceof CodeSelectionPanel)) { 220 throw new IllegalArgumentException("Unsupported panel: "+panel); 221 } 222 CodeSelectionPanel csPanel = (CodeSelectionPanel) panel; 223 return Collections.singleton(csPanel.getCode()); 224 } 225 226 /* don't return all possible codes - this projection choice it too generic */ 227 @Override 228 public String[] allCodes() { 229 return new String[0]; 230 } 231 232 /* not needed since allCodes() returns empty array */ 233 @Override 234 public Collection<String> getPreferencesFromCode(String code) { 235 return null; 236 } 237 238 @Override 239 public boolean showProjectionCode() { 240 return true; 241 } 242 243 @Override 244 public boolean showProjectionName() { 245 return true; 246 } 247 248}