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