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}