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