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