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