001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.conflict.tags;
003
004import static org.openstreetmap.josm.tools.I18n.tr;
005
006import java.awt.Component;
007import java.awt.Font;
008import java.awt.event.FocusAdapter;
009import java.awt.event.FocusEvent;
010import java.awt.event.KeyEvent;
011import java.util.concurrent.CopyOnWriteArrayList;
012
013import javax.swing.AbstractCellEditor;
014import javax.swing.DefaultComboBoxModel;
015import javax.swing.JLabel;
016import javax.swing.JList;
017import javax.swing.JTable;
018import javax.swing.ListCellRenderer;
019import javax.swing.UIManager;
020import javax.swing.table.TableCellEditor;
021
022import org.openstreetmap.josm.gui.widgets.JosmComboBox;
023
024/**
025 * This is a table cell editor for selecting a possible tag value from a list of
026 * proposed tag values. The editor also allows to select all proposed valued or
027 * to remove the tag.
028 *
029 * The editor responds intercepts some keys and interprets them as navigation keys. It
030 * forwards navigation events to {@link NavigationListener}s registred with this editor.
031 * You should register the parent table using this editor as {@link NavigationListener}.
032 *
033 * {@link KeyEvent#VK_ENTER} and {@link KeyEvent#VK_TAB} trigger a {@link NavigationListener#gotoNextDecision()}.
034 */
035public class MultiValueCellEditor extends AbstractCellEditor implements TableCellEditor{
036
037    public static interface NavigationListener {
038        void gotoNextDecision();
039        void gotoPreviousDecision();
040    }
041
042    /** the combo box used as editor */
043    private JosmComboBox editor;
044    private DefaultComboBoxModel editorModel;
045    private CopyOnWriteArrayList<NavigationListener> listeners;
046
047    public void addNavigationListeners(NavigationListener listener) {
048        if (listener != null) {
049            listeners.addIfAbsent(listener);
050        }
051    }
052
053    public void removeavigationListeners(NavigationListener listener) {
054        listeners.remove(listener);
055    }
056
057    protected void fireGotoNextDecision() {
058        for (NavigationListener l: listeners) {
059            l.gotoNextDecision();
060        }
061    }
062
063    protected void fireGotoPreviousDecision() {
064        for (NavigationListener l: listeners) {
065            l.gotoPreviousDecision();
066        }
067    }
068
069    public MultiValueCellEditor() {
070        editorModel = new DefaultComboBoxModel();
071        editor = new JosmComboBox(editorModel) {
072            @Override
073            public void processKeyEvent(KeyEvent e) {
074                if (e.getID() == KeyEvent.KEY_PRESSED && e.getKeyCode() == KeyEvent.VK_ENTER) {
075                    fireGotoNextDecision();
076                } else if (e.getID() == KeyEvent.KEY_PRESSED && e.getKeyCode() == KeyEvent.VK_TAB) {
077                    if (e.isShiftDown()) {
078                        fireGotoPreviousDecision();
079                    } else {
080                        fireGotoNextDecision();
081                    }
082                } else if ( e.getID() == KeyEvent.KEY_PRESSED && e.getKeyCode() == KeyEvent.VK_DELETE  || e.getKeyCode() == KeyEvent.VK_BACK_SPACE) {
083                    if (editorModel.getIndexOf(MultiValueDecisionType.KEEP_NONE) > 0) {
084                        editorModel.setSelectedItem(MultiValueDecisionType.KEEP_NONE);
085                        fireGotoNextDecision();
086                    }
087                } else if (e.getID() == KeyEvent.KEY_PRESSED && e.getKeyCode() == KeyEvent.VK_ESCAPE) {
088                    cancelCellEditing();
089                }
090                super.processKeyEvent(e);
091            }
092        };
093        editor.addFocusListener(
094                new FocusAdapter() {
095                    @Override
096                    public void focusGained(FocusEvent e) {
097                        editor.showPopup();
098                    }
099                }
100        );
101        editor.setRenderer(new EditorCellRenderer());
102        listeners = new CopyOnWriteArrayList<NavigationListener>();
103    }
104
105    protected void initEditor(MultiValueResolutionDecision decision) {
106        editorModel.removeAllElements();
107        for (String value: decision.getValues()) {
108            editorModel.addElement(value);
109        }
110        if (decision.canKeepNone()) {
111            editorModel.addElement(MultiValueDecisionType.KEEP_NONE);
112        }
113        if (decision.canKeepAll()) {
114            editorModel.addElement(MultiValueDecisionType.KEEP_ALL);
115        }
116        switch(decision.getDecisionType()) {
117        case UNDECIDED:
118            editor.setSelectedIndex(0);
119            break;
120        case KEEP_ONE:
121            editor.setSelectedItem(decision.getChosenValue());
122            break;
123        case KEEP_NONE:
124            editor.setSelectedItem(MultiValueDecisionType.KEEP_NONE);
125            break;
126        case KEEP_ALL:
127            editor.setSelectedItem(MultiValueDecisionType.KEEP_ALL);
128        }
129    }
130
131    @Override
132    public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
133        MultiValueResolutionDecision decision = (MultiValueResolutionDecision)value;
134        initEditor(decision);
135        editor.requestFocus();
136        return editor;
137    }
138
139    @Override
140    public Object getCellEditorValue() {
141        return editor.getSelectedItem();
142    }
143
144    /**
145     * The cell renderer used in the combo box
146     *
147     */
148    static private class EditorCellRenderer extends JLabel implements ListCellRenderer {
149
150        public EditorCellRenderer() {
151            setOpaque(true);
152        }
153
154        protected void renderColors(boolean selected) {
155            if (selected) {
156                setForeground(UIManager.getColor("ComboBox.selectionForeground"));
157                setBackground(UIManager.getColor("ComboBox.selectionBackground"));
158            } else {
159                setForeground(UIManager.getColor("ComboBox.foreground"));
160                setBackground(UIManager.getColor("ComboBox.background"));
161            }
162        }
163
164        protected void renderValue(Object value) {
165            setFont(UIManager.getFont("ComboBox.font"));
166            if (String.class.isInstance(value)) {
167                setText(String.class.cast(value));
168            } else if (MultiValueDecisionType.class.isInstance(value)) {
169                switch(MultiValueDecisionType.class.cast(value)) {
170                case KEEP_NONE:
171                    setText(tr("none"));
172                    setFont(UIManager.getFont("ComboBox.font").deriveFont(Font.ITALIC + Font.BOLD));
173                    break;
174                case KEEP_ALL:
175                    setText(tr("all"));
176                    setFont(UIManager.getFont("ComboBox.font").deriveFont(Font.ITALIC + Font.BOLD));
177                    break;
178                default:
179                    // don't display other values
180                }
181            }
182        }
183
184        @Override
185        public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected,
186                boolean cellHasFocus) {
187            renderColors(isSelected);
188            renderValue(value);
189            return this;
190        }
191    }
192}