001    // License: GPL. For details, see LICENSE file.
002    package org.openstreetmap.josm.gui.conflict.tags;
003    
004    import static org.openstreetmap.josm.tools.I18n.tr;
005    
006    import java.awt.Component;
007    import java.awt.Font;
008    import java.awt.event.FocusAdapter;
009    import java.awt.event.FocusEvent;
010    import java.awt.event.KeyEvent;
011    import java.util.concurrent.CopyOnWriteArrayList;
012    
013    import javax.swing.AbstractCellEditor;
014    import javax.swing.DefaultComboBoxModel;
015    import javax.swing.JLabel;
016    import javax.swing.JList;
017    import javax.swing.JTable;
018    import javax.swing.ListCellRenderer;
019    import javax.swing.UIManager;
020    import javax.swing.table.TableCellEditor;
021    
022    import 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     */
035    public 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                    } 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        public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
132            MultiValueResolutionDecision decision = (MultiValueResolutionDecision)value;
133            initEditor(decision);
134            editor.requestFocus();
135            return editor;
136        }
137    
138        public Object getCellEditorValue() {
139            return editor.getSelectedItem();
140        }
141    
142        /**
143         * The cell renderer used in the combo box
144         *
145         */
146        static private class EditorCellRenderer extends JLabel implements ListCellRenderer {
147    
148            public EditorCellRenderer() {
149                setOpaque(true);
150            }
151    
152            protected void renderColors(boolean selected) {
153                if (selected) {
154                    setForeground(UIManager.getColor("ComboBox.selectionForeground"));
155                    setBackground(UIManager.getColor("ComboBox.selectionBackground"));
156                } else {
157                    setForeground(UIManager.getColor("ComboBox.foreground"));
158                    setBackground(UIManager.getColor("ComboBox.background"));
159                }
160            }
161    
162            protected void renderValue(Object value) {
163                setFont(UIManager.getFont("ComboBox.font"));
164                if (String.class.isInstance(value)) {
165                    setText(String.class.cast(value));
166                } else if (MultiValueDecisionType.class.isInstance(value)) {
167                    switch(MultiValueDecisionType.class.cast(value)) {
168                    case KEEP_NONE:
169                        setText(tr("none"));
170                        setFont(UIManager.getFont("ComboBox.font").deriveFont(Font.ITALIC + Font.BOLD));
171                        break;
172                    case KEEP_ALL:
173                        setText(tr("all"));
174                        setFont(UIManager.getFont("ComboBox.font").deriveFont(Font.ITALIC + Font.BOLD));
175                        break;
176                    default:
177                        // don't display other values
178                    }
179                }
180            }
181    
182            public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected,
183                    boolean cellHasFocus) {
184                renderColors(isSelected);
185                renderValue(value);
186                return this;
187            }
188        }
189    }