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}