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}