001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.tagging.ac; 003 004import java.awt.Component; 005import java.awt.event.FocusAdapter; 006import java.awt.event.FocusEvent; 007import java.awt.event.KeyAdapter; 008import java.awt.event.KeyEvent; 009import java.util.EventObject; 010 011import javax.swing.ComboBoxEditor; 012import javax.swing.JTable; 013import javax.swing.event.CellEditorListener; 014import javax.swing.table.TableCellEditor; 015import javax.swing.text.AttributeSet; 016import javax.swing.text.BadLocationException; 017import javax.swing.text.Document; 018import javax.swing.text.PlainDocument; 019import javax.swing.text.StyleConstants; 020 021import org.openstreetmap.josm.Main; 022import org.openstreetmap.josm.gui.util.CellEditorSupport; 023import org.openstreetmap.josm.gui.widgets.JosmTextField; 024 025/** 026 * AutoCompletingTextField is a text field with autocompletion behaviour. It 027 * can be used as table cell editor in {@link JTable}s. 028 * 029 * Autocompletion is controlled by a list of {@link AutoCompletionListItem}s 030 * managed in a {@link AutoCompletionList}. 031 * 032 * @since 1762 033 */ 034public class AutoCompletingTextField extends JosmTextField implements ComboBoxEditor, TableCellEditor { 035 036 private Integer maxChars; 037 038 /** 039 * The document model for the editor 040 */ 041 class AutoCompletionDocument extends PlainDocument { 042 043 @Override 044 public void insertString(int offs, String str, AttributeSet a) throws BadLocationException { 045 046 // If a maximum number of characters is specified, avoid to exceed it 047 if (maxChars != null && str != null && getLength() + str.length() > maxChars) { 048 int allowedLength = maxChars-getLength(); 049 if (allowedLength > 0) { 050 str = str.substring(0, allowedLength); 051 } else { 052 return; 053 } 054 } 055 056 if (autoCompletionList == null) { 057 super.insertString(offs, str, a); 058 return; 059 } 060 061 // input method for non-latin characters (e.g. scim) 062 if (a != null && a.isDefined(StyleConstants.ComposedTextAttribute)) { 063 super.insertString(offs, str, a); 064 return; 065 } 066 067 // if the current offset isn't at the end of the document we don't autocomplete. 068 // If a highlighted autocompleted suffix was present and we get here Swing has 069 // already removed it from the document. getLength() therefore doesn't include the 070 // autocompleted suffix. 071 // 072 if (offs < getLength()) { 073 super.insertString(offs, str, a); 074 return; 075 } 076 077 String currentText = getText(0, getLength()); 078 // if the text starts with a number we don't autocomplete 079 if (Main.pref.getBoolean("autocomplete.dont_complete_numbers", true)) { 080 try { 081 Long.parseLong(str); 082 if (currentText.length() == 0) { 083 // we don't autocomplete on numbers 084 super.insertString(offs, str, a); 085 return; 086 } 087 Long.parseLong(currentText); 088 super.insertString(offs, str, a); 089 return; 090 } catch(NumberFormatException e) { 091 // either the new text or the current text isn't a number. We continue with 092 // autocompletion 093 } 094 } 095 String prefix = currentText.substring(0, offs); 096 autoCompletionList.applyFilter(prefix+str); 097 if (autoCompletionList.getFilteredSize()>0) { 098 // there are matches. Insert the new text and highlight the 099 // auto completed suffix 100 // 101 String matchingString = autoCompletionList.getFilteredItem(0).getValue(); 102 remove(0,getLength()); 103 super.insertString(0,matchingString,a); 104 105 // highlight from insert position to end position to put the caret at the end 106 setCaretPosition(offs + str.length()); 107 moveCaretPosition(getLength()); 108 } else { 109 // there are no matches. Insert the new text, do not highlight 110 // 111 String newText = prefix + str; 112 remove(0,getLength()); 113 super.insertString(0,newText,a); 114 setCaretPosition(getLength()); 115 116 } 117 } 118 } 119 120 /** the auto completion list user input is matched against */ 121 protected AutoCompletionList autoCompletionList = null; 122 123 @Override 124 protected Document createDefaultModel() { 125 return new AutoCompletionDocument(); 126 } 127 128 protected final void init() { 129 addFocusListener( 130 new FocusAdapter() { 131 @Override public void focusGained(FocusEvent e) { 132 selectAll(); 133 applyFilter(getText()); 134 } 135 } 136 ); 137 138 addKeyListener( 139 new KeyAdapter() { 140 141 @Override 142 public void keyReleased(KeyEvent e) { 143 if (getText().isEmpty()) { 144 applyFilter(""); 145 } 146 } 147 } 148 ); 149 tableCellEditorSupport = new CellEditorSupport(this); 150 } 151 152 /** 153 * Constructs a new {@code AutoCompletingTextField}. 154 */ 155 public AutoCompletingTextField() { 156 this(0); 157 } 158 159 /** 160 * Constructs a new {@code AutoCompletingTextField}. 161 * @param columns the number of columns to use to calculate the preferred width; 162 * if columns is set to zero, the preferred width will be whatever naturally results from the component implementation 163 */ 164 public AutoCompletingTextField(int columns) { 165 this(columns, true); 166 } 167 168 /** 169 * Constructs a new {@code AutoCompletingTextField}. 170 * @param columns the number of columns to use to calculate the preferred width; 171 * if columns is set to zero, the preferred width will be whatever naturally results from the component implementation 172 * @param undoRedo Enables or not Undo/Redo feature. Not recommended for table cell editors, unless each cell provides its own editor 173 */ 174 public AutoCompletingTextField(int columns, boolean undoRedo) { 175 super(null, null, columns, undoRedo); 176 init(); 177 } 178 179 protected void applyFilter(String filter) { 180 if (autoCompletionList != null) { 181 autoCompletionList.applyFilter(filter); 182 } 183 } 184 185 /** 186 * Returns the auto completion list. 187 * @return the auto completion list; may be null, if no auto completion list is set 188 */ 189 public AutoCompletionList getAutoCompletionList() { 190 return autoCompletionList; 191 } 192 193 /** 194 * Sets the auto completion list. 195 * @param autoCompletionList the auto completion list; if null, auto completion is 196 * disabled 197 */ 198 public void setAutoCompletionList(AutoCompletionList autoCompletionList) { 199 this.autoCompletionList = autoCompletionList; 200 } 201 202 @Override 203 public Component getEditorComponent() { 204 return this; 205 } 206 207 @Override 208 public Object getItem() { 209 return getText(); 210 } 211 212 @Override 213 public void setItem(Object anObject) { 214 if (anObject == null) { 215 setText(""); 216 } else { 217 setText(anObject.toString()); 218 } 219 } 220 221 /** 222 * Sets the maximum number of characters allowed. 223 * @param max maximum number of characters allowed 224 * @since 5579 225 */ 226 public void setMaxChars(Integer max) { 227 maxChars = max; 228 } 229 230 /* ------------------------------------------------------------------------------------ */ 231 /* TableCellEditor interface */ 232 /* ------------------------------------------------------------------------------------ */ 233 234 private CellEditorSupport tableCellEditorSupport; 235 private String originalValue; 236 237 @Override 238 public void addCellEditorListener(CellEditorListener l) { 239 tableCellEditorSupport.addCellEditorListener(l); 240 } 241 242 protected void rememberOriginalValue(String value) { 243 this.originalValue = value; 244 } 245 246 protected void restoreOriginalValue() { 247 setText(originalValue); 248 } 249 250 @Override 251 public void removeCellEditorListener(CellEditorListener l) { 252 tableCellEditorSupport.removeCellEditorListener(l); 253 } 254 255 @Override 256 public void cancelCellEditing() { 257 restoreOriginalValue(); 258 tableCellEditorSupport.fireEditingCanceled(); 259 } 260 261 @Override 262 public Object getCellEditorValue() { 263 return getText(); 264 } 265 266 @Override 267 public boolean isCellEditable(EventObject anEvent) { 268 return true; 269 } 270 271 @Override 272 public boolean shouldSelectCell(EventObject anEvent) { 273 return true; 274 } 275 276 @Override 277 public boolean stopCellEditing() { 278 tableCellEditorSupport.fireEditingStopped(); 279 return true; 280 } 281 282 @Override 283 public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) { 284 setText( value == null ? "" : value.toString()); 285 rememberOriginalValue(getText()); 286 return this; 287 } 288}