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