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