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