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