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