// License: GPL. For details, see LICENSE file. package org.openstreetmap.josm.gui.tagging; import java.awt.Component; import java.awt.event.FocusAdapter; import java.awt.event.FocusEvent; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.util.EventObject; import java.util.logging.Logger; import javax.swing.ComboBoxEditor; import javax.swing.JTable; import javax.swing.JTextField; import javax.swing.event.CellEditorListener; import javax.swing.table.TableCellEditor; import javax.swing.text.AttributeSet; import javax.swing.text.BadLocationException; import javax.swing.text.Document; import javax.swing.text.PlainDocument; import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionList; import org.openstreetmap.josm.gui.util.TableCellEditorSupport; /** * AutoCompletingTextField is an text field with autocompletion behaviour. It * can be used as table cell editor in {@see JTable}s. * * Autocompletion is controlled by a list of {@see AutoCompletionListItem}s * managed in a {@see AutoCompletionList}. * * */ public class AutoCompletingTextField extends JTextField implements ComboBoxEditor, TableCellEditor { @SuppressWarnings("unused") static private Logger logger = Logger.getLogger(AutoCompletingTextField.class.getName()); /** * The document model for the editor */ class AutoCompletionDocument extends PlainDocument { /** * inserts a string at a specific position * */ @Override public void insertString(int offs, String str, AttributeSet a) throws BadLocationException { if (autoCompletionList == null) { super.insertString(offs, str, a); return; } // if the current offset isn't at the end of the document we don't autocomplete. // If a highlighted autocompleted suffix was present and we get here Swing has // already removed it from the document. getLength() therefore doesn't include the // autocompleted suffix. // if (offs < getLength()) { super.insertString(offs, str, a); return; } String currentText = getText(0, getLength()); // if the text starts with a number we don't autocomplete // try { Long.parseLong(str); if (currentText.length() == 0) { // we don't autocomplete on numbers super.insertString(offs, str, a); return; } Long.parseLong(currentText); super.insertString(offs, str, a); return; } catch(NumberFormatException e) { // either the new text or the current text isn't a number. We continue with // autocompletion } String prefix = currentText.substring(0, offs); autoCompletionList.applyFilter(prefix+str); if (autoCompletionList.getFilteredSize()>0) { // there are matches. Insert the new text and highlight the // auto completed suffix // String matchingString = autoCompletionList.getFilteredItem(0).getValue(); remove(0,getLength()); super.insertString(0,matchingString,a); // highlight from end to insert position // setCaretPosition(getLength()); moveCaretPosition(offs + str.length()); } else { // there are no matches. Insert the new text, do not highlight // String newText = prefix + str; remove(0,getLength()); super.insertString(0,newText,a); setCaretPosition(getLength()); } } } /** the auto completion list user input is matched against */ protected AutoCompletionList autoCompletionList = null; /** * creates the default document model for this editor * */ @Override protected Document createDefaultModel() { return new AutoCompletionDocument(); } protected void init() { addFocusListener( new FocusAdapter() { @Override public void focusGained(FocusEvent e) { selectAll(); applyFilter(getText()); } } ); addKeyListener( new KeyAdapter() { @Override public void keyReleased(KeyEvent e) { if (getText().equals("")) { applyFilter(""); } } } ); tableCellEditorSupport = new TableCellEditorSupport(this); } /** * constructor */ public AutoCompletingTextField() { init(); } public AutoCompletingTextField(int columns) { super(columns); init(); } protected void applyFilter(String filter) { if (autoCompletionList != null) { autoCompletionList.applyFilter(filter); } } /** * * @return the auto completion list; may be null, if no auto completion list is set */ public AutoCompletionList getAutoCompletionList() { return autoCompletionList; } /** * sets the auto completion list * @param autoCompletionList the auto completion list; if null, auto completion is * disabled */ public void setAutoCompletionList(AutoCompletionList autoCompletionList) { this.autoCompletionList = autoCompletionList; } public Component getEditorComponent() { return this; } public Object getItem() { return getText(); } public void setItem(Object anObject) { if (anObject == null) { setText(""); } else { setText(anObject.toString()); } } /* ------------------------------------------------------------------------------------ */ /* TableCellEditor interface */ /* ------------------------------------------------------------------------------------ */ private TableCellEditorSupport tableCellEditorSupport; private String originalValue; public void addCellEditorListener(CellEditorListener l) { tableCellEditorSupport.addCellEditorListener(l); } protected void rememberOriginalValue(String value) { this.originalValue = value; } protected void restoreOriginalValue() { setText(originalValue); } public void removeCellEditorListener(CellEditorListener l) { tableCellEditorSupport.removeCellEditorListener(l); } public void cancelCellEditing() { restoreOriginalValue(); tableCellEditorSupport.fireEditingCanceled(); } public Object getCellEditorValue() { return getText(); } public boolean isCellEditable(EventObject anEvent) { return true; } public boolean shouldSelectCell(EventObject anEvent) { return true; } public boolean stopCellEditing() { tableCellEditorSupport.fireEditingStopped(); return true; } public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) { setText( value == null ? "" : value.toString()); rememberOriginalValue(getText()); return this; } }