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