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.List; 011import java.util.Locale; 012 013import javax.swing.JPanel; 014import javax.swing.JScrollPane; 015import javax.swing.JTable; 016import javax.swing.event.DocumentEvent; 017import javax.swing.event.DocumentListener; 018import javax.swing.event.ListSelectionEvent; 019import javax.swing.event.ListSelectionListener; 020import javax.swing.table.AbstractTableModel; 021 022import org.openstreetmap.josm.data.projection.Projections; 023import org.openstreetmap.josm.gui.preferences.projection.CodeProjectionChoice.CodeComparator; 024import org.openstreetmap.josm.gui.widgets.JosmTextField; 025import org.openstreetmap.josm.tools.GBC; 026 027/** 028 * Panel allowing to select a projection by code. 029 * @since 13544 (extracted from {@link CodeProjectionChoice}) 030 */ 031public class CodeSelectionPanel extends JPanel implements ListSelectionListener, DocumentListener { 032 033 private final JosmTextField filter = new JosmTextField(30); 034 private final ProjectionCodeModel model = new ProjectionCodeModel(); 035 private JTable table; 036 private final List<String> data; 037 private final List<String> filteredData; 038 private static final String DEFAULT_CODE = "EPSG:3857"; 039 private String lastCode = DEFAULT_CODE; 040 private final transient ActionListener listener; 041 042 /** 043 * Constructs a new {@code CodeSelectionPanel}. 044 * @param initialCode projection code initially selected 045 * @param listener listener notified of selection change events 046 */ 047 public CodeSelectionPanel(String initialCode, ActionListener listener) { 048 this.listener = listener; 049 data = new ArrayList<>(Projections.getAllProjectionCodes()); 050 data.sort(new CodeComparator()); 051 filteredData = new ArrayList<>(data); 052 build(); 053 setCode(initialCode != null ? initialCode : DEFAULT_CODE); 054 table.getSelectionModel().addListSelectionListener(this); 055 } 056 057 /** 058 * List model for the filtered view on the list of all codes. 059 */ 060 private class ProjectionCodeModel extends AbstractTableModel { 061 @Override 062 public int getRowCount() { 063 return filteredData.size(); 064 } 065 066 @Override 067 public String getValueAt(int index, int column) { 068 if (index >= 0 && index < filteredData.size()) { 069 String code = filteredData.get(index); 070 switch (column) { 071 case 0: return code; 072 case 1: return Projections.getProjectionByCode(code).toString(); 073 default: break; 074 } 075 } 076 return null; 077 } 078 079 @Override 080 public int getColumnCount() { 081 return 2; 082 } 083 084 @Override 085 public String getColumnName(int column) { 086 switch (column) { 087 case 0: return tr("Projection code"); 088 case 1: return tr("Projection name"); 089 default: return super.getColumnName(column); 090 } 091 } 092 } 093 094 private void build() { 095 filter.setColumns(40); 096 filter.getDocument().addDocumentListener(this); 097 098 table = new JTable(model); 099 table.setAutoCreateRowSorter(true); 100 JScrollPane scroll = new JScrollPane(table); 101 scroll.setPreferredSize(new Dimension(200, 214)); 102 103 this.setLayout(new GridBagLayout()); 104 this.add(filter, GBC.eol().weight(1.0, 0.0)); 105 this.add(scroll, GBC.eol().fill(GBC.HORIZONTAL)); 106 } 107 108 /** 109 * Returns selected projection code. 110 * @return selected projection code 111 */ 112 public String getCode() { 113 int idx = table.getSelectedRow(); 114 if (idx == -1) 115 return lastCode; 116 return filteredData.get(table.convertRowIndexToModel(table.getSelectedRow())); 117 } 118 119 /** 120 * Sets selected projection code. 121 * @param code projection code to select 122 */ 123 public final void setCode(String code) { 124 int idx = filteredData.indexOf(code); 125 if (idx != -1) { 126 selectRow(idx); 127 } 128 } 129 130 private void selectRow(int idx) { 131 table.setRowSelectionInterval(idx, idx); 132 ensureRowIsVisible(idx); 133 } 134 135 private void ensureRowIsVisible(int idx) { 136 table.scrollRectToVisible(table.getCellRect(idx, 0, true)); 137 } 138 139 @Override 140 public void valueChanged(ListSelectionEvent e) { 141 listener.actionPerformed(null); 142 lastCode = getCode(); 143 } 144 145 @Override 146 public void insertUpdate(DocumentEvent e) { 147 updateFilter(); 148 } 149 150 @Override 151 public void removeUpdate(DocumentEvent e) { 152 updateFilter(); 153 } 154 155 @Override 156 public void changedUpdate(DocumentEvent e) { 157 updateFilter(); 158 } 159 160 private void updateFilter() { 161 filteredData.clear(); 162 String filterTxt = filter.getText().trim().toLowerCase(Locale.ENGLISH); 163 for (String code : data) { 164 if (code.toLowerCase(Locale.ENGLISH).contains(filterTxt) 165 || Projections.getProjectionByCode(code).toString().toLowerCase(Locale.ENGLISH).contains(filterTxt)) { 166 filteredData.add(code); 167 } 168 } 169 model.fireTableDataChanged(); 170 int idx = filteredData.indexOf(lastCode); 171 if (idx == -1) { 172 table.clearSelection(); 173 if (table.getModel().getRowCount() > 0) { 174 ensureRowIsVisible(0); 175 } 176 } else { 177 selectRow(idx); 178 } 179 } 180}