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.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 private EntryListModel entryModel; 045 private final transient PrefEntry entry; 046 047 private JList<String> entryList; 048 private JTable table; 049 private MapTableModel tableModel; 050 051 private final List<List<String>> dataKeys; 052 private final List<List<String>> dataValues; 053 private 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", Boolean.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 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 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}