001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.preferences.advanced; 003 004import static org.openstreetmap.josm.tools.I18n.marktr; 005import static org.openstreetmap.josm.tools.I18n.tr; 006 007import java.awt.Color; 008import java.awt.Component; 009import java.awt.Font; 010import java.awt.GridBagLayout; 011import java.awt.event.MouseAdapter; 012import java.awt.event.MouseEvent; 013import java.util.ArrayList; 014import java.util.List; 015import java.util.Map; 016import java.util.Objects; 017 018import javax.swing.ButtonGroup; 019import javax.swing.DefaultCellEditor; 020import javax.swing.JComponent; 021import javax.swing.JLabel; 022import javax.swing.JOptionPane; 023import javax.swing.JPanel; 024import javax.swing.JRadioButton; 025import javax.swing.JTable; 026import javax.swing.UIManager; 027import javax.swing.table.DefaultTableCellRenderer; 028import javax.swing.table.DefaultTableModel; 029 030import org.openstreetmap.josm.Main; 031import org.openstreetmap.josm.data.Preferences.ListListSetting; 032import org.openstreetmap.josm.data.Preferences.ListSetting; 033import org.openstreetmap.josm.data.Preferences.MapListSetting; 034import org.openstreetmap.josm.data.Preferences.Setting; 035import org.openstreetmap.josm.data.Preferences.StringSetting; 036import org.openstreetmap.josm.gui.ExtendedDialog; 037import org.openstreetmap.josm.gui.util.GuiHelper; 038import org.openstreetmap.josm.gui.widgets.JosmTextField; 039import org.openstreetmap.josm.tools.GBC; 040 041/** 042 * Component for editing list of preferences as a table. 043 * @since 6021 044 */ 045public class PreferencesTable extends JTable { 046 private final AllSettingsTableModel model; 047 private final transient List<PrefEntry> displayData; 048 049 /** 050 * Constructs a new {@code PreferencesTable}. 051 * @param displayData The list of preferences entries to display 052 */ 053 public PreferencesTable(List<PrefEntry> displayData) { 054 this.displayData = displayData; 055 model = new AllSettingsTableModel(); 056 setModel(model); 057 putClientProperty("terminateEditOnFocusLost", Boolean.TRUE); 058 getColumnModel().getColumn(1).setCellRenderer(new SettingCellRenderer()); 059 getColumnModel().getColumn(1).setCellEditor(new SettingCellEditor()); 060 061 addMouseListener(new MouseAdapter() { 062 @Override public void mouseClicked(MouseEvent e) { 063 if (e.getClickCount() == 2) { 064 editPreference(PreferencesTable.this); 065 } 066 } 067 }); 068 } 069 070 /** 071 * This method should be called when displayed data was changed form external code 072 */ 073 public void fireDataChanged() { 074 model.fireTableDataChanged(); 075 } 076 077 /** 078 * The list of currently selected rows 079 * @return newly created list of PrefEntry 080 */ 081 public List<PrefEntry> getSelectedItems() { 082 List<PrefEntry> entries = new ArrayList<>(); 083 for (int row : getSelectedRows()) { 084 PrefEntry p = (PrefEntry) model.getValueAt(row, -1); 085 entries.add(p); 086 } 087 return entries; 088 } 089 090 /** 091 * Call this to edit selected row in preferences table 092 * @param gui - parent component for messagebox 093 * @return true if editing was actually performed during this call 094 */ 095 public boolean editPreference(final JComponent gui) { 096 if (getSelectedRowCount() != 1) { 097 JOptionPane.showMessageDialog( 098 gui, 099 tr("Please select the row to edit."), 100 tr("Warning"), 101 JOptionPane.WARNING_MESSAGE 102 ); 103 return false; 104 } 105 final PrefEntry e = (PrefEntry) model.getValueAt(getSelectedRow(), 1); 106 Setting<?> stg = e.getValue(); 107 if (stg instanceof StringSetting) { 108 editCellAt(getSelectedRow(), 1); 109 Component editor = getEditorComponent(); 110 if (editor != null) { 111 editor.requestFocus(); 112 } 113 } else if (stg instanceof ListSetting) { 114 ListSetting lSetting = (ListSetting) stg; 115 ListEditor lEditor = new ListEditor(gui, e, lSetting); 116 lEditor.showDialog(); 117 if (lEditor.getValue() == 1) { 118 List<String> data = lEditor.getData(); 119 if (!lSetting.equalVal(data)) { 120 e.setValue(new ListSetting(data)); 121 return true; 122 } 123 } 124 } else if (stg instanceof ListListSetting) { 125 ListListSetting llSetting = (ListListSetting) stg; 126 ListListEditor llEditor = new ListListEditor(gui, e, llSetting); 127 llEditor.showDialog(); 128 if (llEditor.getValue() == 1) { 129 List<List<String>> data = llEditor.getData(); 130 if (!llSetting.equalVal(data)) { 131 e.setValue(new ListListSetting(data)); 132 return true; 133 } 134 } 135 } else if (stg instanceof MapListSetting) { 136 MapListSetting mlSetting = (MapListSetting) stg; 137 MapListEditor mlEditor = new MapListEditor(gui, e, mlSetting); 138 mlEditor.showDialog(); 139 if (mlEditor.getValue() == 1) { 140 List<Map<String, String>> data = mlEditor.getData(); 141 if (!mlSetting.equalVal(data)) { 142 e.setValue(new MapListSetting(data)); 143 return true; 144 } 145 } 146 } 147 return false; 148 } 149 150 /** 151 * Add new preference to the table 152 * @param gui - parent component for asking dialogs 153 * @return newly created entry or null if adding was cancelled 154 */ 155 public PrefEntry addPreference(final JComponent gui) { 156 JPanel p = new JPanel(new GridBagLayout()); 157 p.add(new JLabel(tr("Key")), GBC.std().insets(0, 0, 5, 0)); 158 JosmTextField tkey = new JosmTextField("", 50); 159 p.add(tkey, GBC.eop().insets(5, 0, 0, 0).fill(GBC.HORIZONTAL)); 160 161 p.add(new JLabel(tr("Select Setting Type:")), GBC.eol().insets(5, 15, 5, 0)); 162 163 JRadioButton rbString = new JRadioButton(tr("Simple")); 164 JRadioButton rbList = new JRadioButton(tr("List")); 165 JRadioButton rbListList = new JRadioButton(tr("List of lists")); 166 JRadioButton rbMapList = new JRadioButton(tr("List of maps")); 167 168 ButtonGroup group = new ButtonGroup(); 169 group.add(rbString); 170 group.add(rbList); 171 group.add(rbListList); 172 group.add(rbMapList); 173 174 p.add(rbString, GBC.eol()); 175 p.add(rbList, GBC.eol()); 176 p.add(rbListList, GBC.eol()); 177 p.add(rbMapList, GBC.eol()); 178 179 rbString.setSelected(true); 180 181 ExtendedDialog dlg = new ExtendedDialog(gui, tr("Add setting"), new String[] {tr("OK"), tr("Cancel")}); 182 dlg.setButtonIcons(new String[] {"ok.png", "cancel.png"}); 183 dlg.setContent(p); 184 dlg.showDialog(); 185 186 PrefEntry pe = null; 187 boolean ok = false; 188 if (dlg.getValue() == 1) { 189 if (rbString.isSelected()) { 190 StringSetting sSetting = new StringSetting(null); 191 pe = new PrefEntry(tkey.getText(), sSetting, sSetting, false); 192 StringEditor sEditor = new StringEditor(gui, pe, sSetting); 193 sEditor.showDialog(); 194 if (sEditor.getValue() == 1) { 195 String data = sEditor.getData(); 196 if (!Objects.equals(sSetting.getValue(), data)) { 197 pe.setValue(new StringSetting(data)); 198 ok = true; 199 } 200 } 201 } else if (rbList.isSelected()) { 202 ListSetting lSetting = new ListSetting(null); 203 pe = new PrefEntry(tkey.getText(), lSetting, lSetting, false); 204 ListEditor lEditor = new ListEditor(gui, pe, lSetting); 205 lEditor.showDialog(); 206 if (lEditor.getValue() == 1) { 207 List<String> data = lEditor.getData(); 208 if (!lSetting.equalVal(data)) { 209 pe.setValue(new ListSetting(data)); 210 ok = true; 211 } 212 } 213 } else if (rbListList.isSelected()) { 214 ListListSetting llSetting = new ListListSetting(null); 215 pe = new PrefEntry(tkey.getText(), llSetting, llSetting, false); 216 ListListEditor llEditor = new ListListEditor(gui, pe, llSetting); 217 llEditor.showDialog(); 218 if (llEditor.getValue() == 1) { 219 List<List<String>> data = llEditor.getData(); 220 if (!llSetting.equalVal(data)) { 221 pe.setValue(new ListListSetting(data)); 222 ok = true; 223 } 224 } 225 } else if (rbMapList.isSelected()) { 226 MapListSetting mlSetting = new MapListSetting(null); 227 pe = new PrefEntry(tkey.getText(), mlSetting, mlSetting, false); 228 MapListEditor mlEditor = new MapListEditor(gui, pe, mlSetting); 229 mlEditor.showDialog(); 230 if (mlEditor.getValue() == 1) { 231 List<Map<String, String>> data = mlEditor.getData(); 232 if (!mlSetting.equalVal(data)) { 233 pe.setValue(new MapListSetting(data)); 234 ok = true; 235 } 236 } 237 } 238 } 239 if (ok) 240 return pe; 241 else 242 return null; 243 } 244 245 /** 246 * Reset selected preferences to their default values 247 * @param gui - parent component to display warning messages 248 */ 249 public void resetPreferences(final JComponent gui) { 250 if (getSelectedRowCount() == 0) { 251 JOptionPane.showMessageDialog( 252 gui, 253 tr("Please select the row to delete."), 254 tr("Warning"), 255 JOptionPane.WARNING_MESSAGE 256 ); 257 return; 258 } 259 for (int row : getSelectedRows()) { 260 PrefEntry e = displayData.get(row); 261 e.reset(); 262 } 263 fireDataChanged(); 264 } 265 266 private class AllSettingsTableModel extends DefaultTableModel { 267 268 AllSettingsTableModel() { 269 setColumnIdentifiers(new String[]{tr("Key"), tr("Value")}); 270 } 271 272 @Override 273 public boolean isCellEditable(int row, int column) { 274 return column == 1 && (displayData.get(row).getValue() instanceof StringSetting); 275 } 276 277 @Override 278 public int getRowCount() { 279 return displayData.size(); 280 } 281 282 @Override 283 public Object getValueAt(int row, int column) { 284 if (column == 0) 285 return displayData.get(row).getKey(); 286 else 287 return displayData.get(row); 288 } 289 290 @Override 291 public void setValueAt(Object o, int row, int column) { 292 PrefEntry pe = displayData.get(row); 293 String s = (String) o; 294 if (!s.equals(pe.getValue().getValue())) { 295 pe.setValue(new StringSetting(s)); 296 fireTableCellUpdated(row, column); 297 } 298 } 299 } 300 301 private static class SettingCellRenderer extends DefaultTableCellRenderer { 302 private final Color backgroundColor = UIManager.getColor("Table.background"); 303 private final Color changedColor = Main.pref.getColor( 304 marktr("Advanced Background: Changed"), 305 new Color(200, 255, 200)); 306 private final Color nonDefaultColor = Main.pref.getColor( 307 marktr("Advanced Background: NonDefault"), 308 new Color(255, 255, 200)); 309 310 @Override 311 public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { 312 if (value == null) 313 return this; 314 PrefEntry pe = (PrefEntry) value; 315 Setting<?> setting = pe.getValue(); 316 Object val = setting.getValue(); 317 String display = val != null ? val.toString() : "<html><i><"+tr("unset")+"></i></html>"; 318 319 JLabel label = (JLabel) super.getTableCellRendererComponent(table, 320 display, isSelected, hasFocus, row, column); 321 322 GuiHelper.setBackgroundReadable(label, backgroundColor); 323 if (pe.isChanged()) { 324 GuiHelper.setBackgroundReadable(label, changedColor); 325 } else if (!pe.isDefault()) { 326 GuiHelper.setBackgroundReadable(label, nonDefaultColor); 327 } 328 329 if (!pe.isDefault()) { 330 label.setFont(label.getFont().deriveFont(Font.BOLD)); 331 } 332 val = pe.getDefaultValue().getValue(); 333 if (val != null) { 334 if (pe.isDefault()) { 335 label.setToolTipText(tr("Current value is default.")); 336 } else { 337 label.setToolTipText(tr("Default value is ''{0}''.", val)); 338 } 339 } else { 340 label.setToolTipText(tr("Default value currently unknown (setting has not been used yet).")); 341 } 342 return label; 343 } 344 } 345 346 private static class SettingCellEditor extends DefaultCellEditor { 347 SettingCellEditor() { 348 super(new JosmTextField()); 349 } 350 351 @Override 352 public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) { 353 PrefEntry pe = (PrefEntry) value; 354 StringSetting stg = (StringSetting) pe.getValue(); 355 String s = stg.getValue() == null ? "" : stg.getValue(); 356 return super.getTableCellEditorComponent(table, s, isSelected, row, column); 357 } 358 } 359}