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 }