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}