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