001// License: GPL. See LICENSE file for details.
002package org.openstreetmap.josm.gui.preferences.advanced;
003
004import static org.openstreetmap.josm.tools.I18n.tr;
005
006import java.awt.Dimension;
007import java.awt.GridBagLayout;
008import java.awt.event.ActionEvent;
009import java.util.ArrayList;
010import java.util.Arrays;
011import java.util.Collections;
012import java.util.LinkedHashMap;
013import java.util.List;
014import java.util.Map;
015import java.util.Map.Entry;
016
017import javax.swing.AbstractAction;
018import javax.swing.AbstractListModel;
019import javax.swing.DefaultCellEditor;
020import javax.swing.JComponent;
021import javax.swing.JLabel;
022import javax.swing.JList;
023import javax.swing.JPanel;
024import javax.swing.JScrollPane;
025import javax.swing.JTable;
026import javax.swing.JToolBar;
027import javax.swing.event.ListSelectionEvent;
028import javax.swing.event.ListSelectionListener;
029import javax.swing.table.AbstractTableModel;
030import javax.swing.table.TableCellEditor;
031
032import org.openstreetmap.josm.data.Preferences.MapListSetting;
033import org.openstreetmap.josm.gui.ExtendedDialog;
034import org.openstreetmap.josm.gui.widgets.JosmTextField;
035import org.openstreetmap.josm.tools.GBC;
036import org.openstreetmap.josm.tools.ImageProvider;
037import org.openstreetmap.josm.tools.WindowGeometry;
038
039/**
040 * Editor for List of Maps preference entries.
041 */
042public class MapListEditor extends ExtendedDialog {
043
044    EntryListModel entryModel;
045    PrefEntry entry;
046
047    JList<String> entryList;
048    JTable table;
049    MapTableModel tableModel;
050
051    List<List<String>> dataKeys;
052    List<List<String>> dataValues;
053    Integer entryIdx;
054
055    public MapListEditor(JComponent gui, PrefEntry entry, MapListSetting setting) {
056        super(gui, tr("Change list of maps setting"), new String[] {tr("OK"), tr("Cancel")});
057        this.entry = entry;
058        List<Map<String, String>> orig = setting.getValue();
059
060        dataKeys = new ArrayList<>();
061        dataValues = new ArrayList<>();
062        if (orig != null) {
063            for (Map<String, String> m : orig) {
064                List<String> keys = new ArrayList<>();
065                List<String> values = new ArrayList<>();
066                for (Entry<String, String> e : m.entrySet()) {
067                    keys.add(e.getKey());
068                    values.add(e.getValue());
069                }
070                dataKeys.add(keys);
071                dataValues.add(values);
072            }
073        }
074        setButtonIcons(new String[] {"ok.png", "cancel.png"});
075        setRememberWindowGeometry(getClass().getName() + ".geometry", WindowGeometry.centerInWindow(gui, new Dimension(500, 350)));
076        setContent(build(), false);
077    }
078
079    /**
080     * Returns the data.
081     * @return the preference data
082     */
083    public List<Map<String,String>> getData() {
084        List<Map<String,String>> data = new ArrayList<>();
085        for (int i=0; i < dataKeys.size(); ++i) {
086            Map<String,String> m = new LinkedHashMap<>();
087            for (int j=0; j < dataKeys.get(i).size(); ++j) {
088                m.put(dataKeys.get(i).get(j), dataValues.get(i).get(j));
089            }
090            data.add(m);
091        }
092        return data;
093    }
094
095    protected final JPanel build() {
096        JPanel p = new JPanel(new GridBagLayout());
097        p.add(new JLabel(tr("Key: {0}", entry.getKey())), GBC.std(0,0).span(2).weight(1, 0).insets(0,0,5,10));
098
099        JPanel left = new JPanel(new GridBagLayout());
100
101        entryModel = new EntryListModel();
102        entryList = new JList<>(entryModel);
103        entryList.getSelectionModel().addListSelectionListener(new EntryListener());
104        JScrollPane scroll = new JScrollPane(entryList);
105        left.add(scroll, GBC.eol().fill());
106
107        JToolBar sideButtonTB = new JToolBar(JToolBar.HORIZONTAL);
108        sideButtonTB.setBorderPainted(false);
109        sideButtonTB.setOpaque(false);
110        sideButtonTB.add(new NewEntryAction());
111        RemoveEntryAction removeEntryAction = new RemoveEntryAction();
112        entryList.getSelectionModel().addListSelectionListener(removeEntryAction);
113        sideButtonTB.add(removeEntryAction);
114        left.add(sideButtonTB, GBC.eol());
115
116        left.setPreferredSize(new Dimension(80, 0));
117
118        p.add(left, GBC.std(0,1).fill().weight(0.3, 1.0));
119
120        tableModel = new MapTableModel();
121        table = new JTable(tableModel);
122        table.putClientProperty("terminateEditOnFocusLost", true);
123        table.getTableHeader().getColumnModel().getColumn(0).setHeaderValue(tr("Key"));
124        table.getTableHeader().getColumnModel().getColumn(1).setHeaderValue(tr("Value"));
125        DefaultCellEditor editor = new DefaultCellEditor(new JosmTextField());
126        editor.setClickCountToStart(1);
127        table.setDefaultEditor(table.getColumnClass(0), editor);
128
129        JScrollPane pane = new JScrollPane(table);
130        pane.setPreferredSize(new Dimension(140, 0));
131        p.add(pane, GBC.std(1,1).insets(5,0,0,0).fill().weight(0.7, 1.0));
132        return p;
133    }
134
135    class EntryListModel extends AbstractListModel<String> {
136        @Override
137        public String getElementAt(int index) {
138            return tr("Entry {0}", index+1);
139        }
140
141        @Override
142        public int getSize() {
143            return dataKeys.size();
144        }
145
146        public void add() {
147            dataKeys.add(new ArrayList<String>());
148            dataValues.add(new ArrayList<String>());
149            fireIntervalAdded(this, getSize() - 1, getSize() - 1);
150        }
151
152        public void remove(int idx) {
153            dataKeys.remove(idx);
154            dataValues.remove(idx);
155            fireIntervalRemoved(this, idx, idx);
156        }
157    }
158
159    class NewEntryAction extends AbstractAction {
160        public NewEntryAction() {
161            putValue(NAME, tr("New"));
162            putValue(SHORT_DESCRIPTION, tr("add entry"));
163            putValue(SMALL_ICON, ImageProvider.get("dialogs", "add"));
164        }
165
166        @Override
167        public void actionPerformed(ActionEvent evt) {
168            entryModel.add();
169        }
170    }
171
172    class RemoveEntryAction extends AbstractAction implements ListSelectionListener {
173        public RemoveEntryAction() {
174            putValue(NAME, tr("Remove"));
175            putValue(SHORT_DESCRIPTION, tr("Remove the selected entry"));
176            putValue(SMALL_ICON, ImageProvider.get("dialogs", "delete"));
177            updateEnabledState();
178        }
179
180        protected final void updateEnabledState() {
181            setEnabled(entryList.getSelectedIndices().length == 1);
182        }
183
184        @Override
185        public void valueChanged(ListSelectionEvent e) {
186            updateEnabledState();
187        }
188
189        @Override
190        public void actionPerformed(ActionEvent e) {
191            int idx = entryList.getSelectedIndices()[0];
192            entryModel.remove(idx);
193        }
194    }
195
196    class EntryListener implements ListSelectionListener {
197        @Override
198        public void valueChanged(ListSelectionEvent e) {
199            TableCellEditor editor = table.getCellEditor();
200            if (editor != null) {
201                ((DefaultCellEditor) editor).stopCellEditing();
202            }
203            if (entryList.getSelectedIndices().length != 1) {
204                entryIdx = null;
205                table.setEnabled(false);
206            } else {
207                entryIdx = entryList.getSelectedIndices()[0];
208                table.setEnabled(true);
209            }
210            tableModel.fireTableDataChanged();
211        }
212    }
213
214    class MapTableModel extends AbstractTableModel {
215        private List<List<String>> data() {
216            if (entryIdx == null) return Collections.emptyList();
217            return Arrays.asList(dataKeys.get(entryIdx), dataValues.get(entryIdx));
218        }
219
220        private int size() {
221            if (entryIdx == null) return 0;
222            return dataKeys.get(entryIdx).size();
223        }
224
225        @Override
226        public int getRowCount() {
227            return entryIdx == null ? 0 : size() + 1;
228        }
229
230        @Override
231        public int getColumnCount() {
232            return 2;
233        }
234
235        @Override
236        public String getColumnName(int column) {
237            return column == 0 ? tr("Key") : tr("Value");
238        }
239
240        @Override
241        public Object getValueAt(int row, int column) {
242            return size() == row ? "" : data().get(column).get(row);
243        }
244
245        @Override
246        public void setValueAt(Object o, int row, int column) {
247            String s = (String) o;
248            if (row == size()) {
249                data().get(0).add("");
250                data().get(1).add("");
251                data().get(column).set(row, s);
252                fireTableRowsInserted(row+1, row+1);
253            } else {
254                data().get(column).set(row, s);
255                fireTableCellUpdated(row, column);
256            }
257        }
258
259        @Override
260        public boolean isCellEditable(int row, int column) {
261            return true;
262        }
263    }
264
265}