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.data.preferences.NamedColorProperty; 031import org.openstreetmap.josm.spi.preferences.ListListSetting; 032import org.openstreetmap.josm.spi.preferences.ListSetting; 033import org.openstreetmap.josm.spi.preferences.MapListSetting; 034import org.openstreetmap.josm.spi.preferences.Setting; 035import org.openstreetmap.josm.spi.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 boolean ok = false; 108 if (stg instanceof StringSetting) { 109 editCellAt(getSelectedRow(), 1); 110 Component editor = getEditorComponent(); 111 if (editor != null) { 112 editor.requestFocus(); 113 } 114 } else if (stg instanceof ListSetting) { 115 ok = doEditList(gui, e, (ListSetting) stg); 116 } else if (stg instanceof ListListSetting) { 117 ok = doEditListList(gui, e, (ListListSetting) stg); 118 } else if (stg instanceof MapListSetting) { 119 ok = doEditMapList(gui, e, (MapListSetting) stg); 120 } 121 return ok; 122 } 123 124 private static boolean doEditList(final JComponent gui, final PrefEntry e, ListSetting lSetting) { 125 ListEditor lEditor = new ListEditor(gui, e, lSetting); 126 lEditor.showDialog(); 127 if (lEditor.getValue() == 1) { 128 List<String> data = lEditor.getData(); 129 if (!lSetting.equalVal(data)) { 130 e.setValue(new ListSetting(data)); 131 return true; 132 } 133 } 134 return false; 135 } 136 137 private static boolean doEditListList(final JComponent gui, final PrefEntry e, ListListSetting llSetting) { 138 ListListEditor llEditor = new ListListEditor(gui, e, llSetting); 139 llEditor.showDialog(); 140 if (llEditor.getValue() == 1) { 141 List<List<String>> data = llEditor.getData(); 142 if (!llSetting.equalVal(data)) { 143 e.setValue(new ListListSetting(data)); 144 return true; 145 } 146 } 147 return false; 148 } 149 150 private static boolean doEditMapList(final JComponent gui, final PrefEntry e, MapListSetting mlSetting) { 151 MapListEditor mlEditor = new MapListEditor(gui, e, mlSetting); 152 mlEditor.showDialog(); 153 if (mlEditor.getValue() == 1) { 154 List<Map<String, String>> data = mlEditor.getData(); 155 if (!mlSetting.equalVal(data)) { 156 e.setValue(new MapListSetting(data)); 157 return true; 158 } 159 } 160 return false; 161 } 162 163 /** 164 * Add new preference to the table 165 * @param gui - parent component for asking dialogs 166 * @return newly created entry or null if adding was cancelled 167 */ 168 public PrefEntry addPreference(final JComponent gui) { 169 JPanel p = new JPanel(new GridBagLayout()); 170 p.add(new JLabel(tr("Key")), GBC.std().insets(0, 0, 5, 0)); 171 JosmTextField tkey = new JosmTextField("", 50); 172 p.add(tkey, GBC.eop().insets(5, 0, 0, 0).fill(GBC.HORIZONTAL)); 173 174 p.add(new JLabel(tr("Select Setting Type:")), GBC.eol().insets(5, 15, 5, 0)); 175 176 JRadioButton rbString = new JRadioButton(tr("Simple")); 177 JRadioButton rbList = new JRadioButton(tr("List")); 178 JRadioButton rbListList = new JRadioButton(tr("List of lists")); 179 JRadioButton rbMapList = new JRadioButton(tr("List of maps")); 180 181 ButtonGroup group = new ButtonGroup(); 182 group.add(rbString); 183 group.add(rbList); 184 group.add(rbListList); 185 group.add(rbMapList); 186 187 p.add(rbString, GBC.eol()); 188 p.add(rbList, GBC.eol()); 189 p.add(rbListList, GBC.eol()); 190 p.add(rbMapList, GBC.eol()); 191 192 rbString.setSelected(true); 193 194 PrefEntry pe = null; 195 boolean ok = false; 196 if (askAddSetting(gui, p)) { 197 if (rbString.isSelected()) { 198 StringSetting sSetting = new StringSetting(null); 199 pe = new PrefEntry(tkey.getText(), sSetting, sSetting, false); 200 ok = doAddSimple(gui, pe, sSetting); 201 } else if (rbList.isSelected()) { 202 ListSetting lSetting = new ListSetting(null); 203 pe = new PrefEntry(tkey.getText(), lSetting, lSetting, false); 204 ok = doAddList(gui, pe, lSetting); 205 } else if (rbListList.isSelected()) { 206 ListListSetting llSetting = new ListListSetting(null); 207 pe = new PrefEntry(tkey.getText(), llSetting, llSetting, false); 208 ok = doAddListList(gui, pe, llSetting); 209 } else if (rbMapList.isSelected()) { 210 MapListSetting mlSetting = new MapListSetting(null); 211 pe = new PrefEntry(tkey.getText(), mlSetting, mlSetting, false); 212 ok = doAddMapList(gui, pe, mlSetting); 213 } 214 } 215 return ok ? pe : null; 216 } 217 218 private static boolean askAddSetting(JComponent gui, JPanel p) { 219 return new ExtendedDialog(gui, tr("Add setting"), tr("OK"), tr("Cancel")) 220 .setContent(p).setButtonIcons("ok", "cancel").showDialog().getValue() == 1; 221 } 222 223 private static boolean doAddSimple(final JComponent gui, PrefEntry pe, StringSetting sSetting) { 224 StringEditor sEditor = new StringEditor(gui, pe, sSetting); 225 sEditor.showDialog(); 226 if (sEditor.getValue() == 1) { 227 String data = sEditor.getData(); 228 if (!Objects.equals(sSetting.getValue(), data)) { 229 pe.setValue(new StringSetting(data)); 230 return true; 231 } 232 } 233 return false; 234 } 235 236 private static boolean doAddList(final JComponent gui, PrefEntry pe, ListSetting lSetting) { 237 ListEditor lEditor = new ListEditor(gui, pe, lSetting); 238 lEditor.showDialog(); 239 if (lEditor.getValue() == 1) { 240 List<String> data = lEditor.getData(); 241 if (!lSetting.equalVal(data)) { 242 pe.setValue(new ListSetting(data)); 243 return true; 244 } 245 } 246 return false; 247 } 248 249 private static boolean doAddListList(final JComponent gui, PrefEntry pe, ListListSetting llSetting) { 250 ListListEditor llEditor = new ListListEditor(gui, pe, llSetting); 251 llEditor.showDialog(); 252 if (llEditor.getValue() == 1) { 253 List<List<String>> data = llEditor.getData(); 254 if (!llSetting.equalVal(data)) { 255 pe.setValue(new ListListSetting(data)); 256 return true; 257 } 258 } 259 return false; 260 } 261 262 private static boolean doAddMapList(final JComponent gui, PrefEntry pe, MapListSetting mlSetting) { 263 MapListEditor mlEditor = new MapListEditor(gui, pe, mlSetting); 264 mlEditor.showDialog(); 265 if (mlEditor.getValue() == 1) { 266 List<Map<String, String>> data = mlEditor.getData(); 267 if (!mlSetting.equalVal(data)) { 268 pe.setValue(new MapListSetting(data)); 269 return true; 270 } 271 } 272 return false; 273 } 274 275 /** 276 * Reset selected preferences to their default values 277 * @param gui - parent component to display warning messages 278 */ 279 public void resetPreferences(final JComponent gui) { 280 if (getSelectedRowCount() == 0) { 281 JOptionPane.showMessageDialog( 282 gui, 283 tr("Please select the row to delete."), 284 tr("Warning"), 285 JOptionPane.WARNING_MESSAGE 286 ); 287 return; 288 } 289 for (int row : getSelectedRows()) { 290 PrefEntry e = displayData.get(row); 291 e.reset(); 292 } 293 fireDataChanged(); 294 } 295 296 final class AllSettingsTableModel extends DefaultTableModel { 297 298 AllSettingsTableModel() { 299 setColumnIdentifiers(new String[]{tr("Key"), tr("Value")}); 300 } 301 302 @Override 303 public boolean isCellEditable(int row, int column) { 304 return column == 1 && (displayData.get(row).getValue() instanceof StringSetting); 305 } 306 307 @Override 308 public int getRowCount() { 309 return displayData.size(); 310 } 311 312 @Override 313 public Object getValueAt(int row, int column) { 314 if (column == 0) 315 return displayData.get(row).getKey(); 316 else 317 return displayData.get(row); 318 } 319 320 @Override 321 public void setValueAt(Object o, int row, int column) { 322 PrefEntry pe = displayData.get(row); 323 String s = (String) o; 324 if (!s.equals(pe.getValue().getValue())) { 325 pe.setValue(new StringSetting(s)); 326 fireTableCellUpdated(row, column); 327 } 328 } 329 } 330 331 static final class SettingCellRenderer extends DefaultTableCellRenderer { 332 private final Color backgroundColor = UIManager.getColor("Table.background"); 333 private final Color changedColor = new NamedColorProperty( 334 marktr("Advanced Background: Changed"), 335 new Color(200, 255, 200)).get(); 336 private final Color nonDefaultColor = new NamedColorProperty( 337 marktr("Advanced Background: NonDefault"), 338 new Color(255, 255, 200)).get(); 339 340 @Override 341 public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { 342 if (value == null) 343 return this; 344 PrefEntry pe = (PrefEntry) value; 345 Setting<?> setting = pe.getValue(); 346 Object val = setting.getValue(); 347 String display = val != null ? val.toString() : "<html><i><"+tr("unset")+"></i></html>"; 348 349 JLabel label = (JLabel) super.getTableCellRendererComponent(table, 350 display, isSelected, hasFocus, row, column); 351 352 GuiHelper.setBackgroundReadable(label, backgroundColor); 353 if (pe.isChanged()) { 354 GuiHelper.setBackgroundReadable(label, changedColor); 355 } else if (!pe.isDefault()) { 356 GuiHelper.setBackgroundReadable(label, nonDefaultColor); 357 } 358 359 if (!pe.isDefault()) { 360 label.setFont(label.getFont().deriveFont(Font.BOLD)); 361 } 362 val = pe.getDefaultValue().getValue(); 363 if (val != null) { 364 if (pe.isDefault()) { 365 label.setToolTipText(tr("Current value is default.")); 366 } else { 367 label.setToolTipText(tr("Default value is ''{0}''.", val)); 368 } 369 } else { 370 label.setToolTipText(tr("Default value currently unknown (setting has not been used yet).")); 371 } 372 return label; 373 } 374 } 375 376 static final class SettingCellEditor extends DefaultCellEditor { 377 SettingCellEditor() { 378 super(new JosmTextField()); 379 } 380 381 @Override 382 public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) { 383 PrefEntry pe = (PrefEntry) value; 384 StringSetting stg = (StringSetting) pe.getValue(); 385 String s = stg.getValue() == null ? "" : stg.getValue(); 386 return super.getTableCellEditorComponent(table, s, isSelected, row, column); 387 } 388 } 389}