[ 1730452 ] Autocompletion on comboboxes
This commit is contained in:
parent
acd88e6e6e
commit
8461f47dfb
|
@ -0,0 +1,193 @@
|
|||
package org.compiere.grid.ed;
|
||||
import java.awt.event.*;
|
||||
import java.beans.PropertyChangeEvent;
|
||||
import java.beans.PropertyChangeListener;
|
||||
import javax.swing.*;
|
||||
import javax.swing.text.*;
|
||||
|
||||
|
||||
// phib: this is from http://www.orbital-computer.de/JComboBox
|
||||
// with some minor revisions for Adempiere
|
||||
|
||||
/* This work is hereby released into the Public Domain.
|
||||
* To view a copy of the public domain dedication, visit
|
||||
* http://creativecommons.org/licenses/publicdomain/
|
||||
*/
|
||||
public class AutoCompletion extends PlainDocument {
|
||||
VComboBox comboBox;
|
||||
ComboBoxModel model;
|
||||
JTextComponent editor;
|
||||
// flag to indicate if setSelectedItem has been called
|
||||
// subsequent calls to remove/insertString should be ignored
|
||||
boolean selecting=false;
|
||||
boolean hidePopupOnFocusLoss;
|
||||
boolean hitBackspace=false;
|
||||
boolean hitBackspaceOnSelection;
|
||||
|
||||
KeyListener editorKeyListener;
|
||||
FocusListener editorFocusListener;
|
||||
|
||||
public AutoCompletion(final VComboBox comboBox) {
|
||||
this.comboBox = comboBox;
|
||||
model = comboBox.getModel();
|
||||
comboBox.addActionListener(new ActionListener() {
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
if (!selecting) highlightCompletedText(0);
|
||||
}
|
||||
});
|
||||
comboBox.addPropertyChangeListener(new PropertyChangeListener() {
|
||||
public void propertyChange(PropertyChangeEvent e) {
|
||||
if (e.getPropertyName().equals("editor")) configureEditor((ComboBoxEditor) e.getNewValue());
|
||||
if (e.getPropertyName().equals("model")) model = (ComboBoxModel) e.getNewValue();
|
||||
}
|
||||
});
|
||||
editorKeyListener = new KeyAdapter() {
|
||||
public void keyPressed(KeyEvent e) {
|
||||
if (comboBox.isDisplayable()) comboBox.setPopupVisible(true);
|
||||
hitBackspace=false;
|
||||
switch (e.getKeyCode()) {
|
||||
// determine if the pressed key is backspace (needed by the remove method)
|
||||
case KeyEvent.VK_BACK_SPACE : hitBackspace=true;
|
||||
hitBackspaceOnSelection=editor.getSelectionStart()!=editor.getSelectionEnd();
|
||||
break;
|
||||
// ignore delete key
|
||||
case KeyEvent.VK_DELETE : e.consume();
|
||||
UIManager.getLookAndFeel().provideErrorFeedback(comboBox);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
// Bug 5100422 on Java 1.5: Editable JComboBox won't hide popup when tabbing out
|
||||
hidePopupOnFocusLoss=System.getProperty("java.version").startsWith("1.5");
|
||||
// Highlight whole text when gaining focus
|
||||
editorFocusListener = new FocusAdapter() {
|
||||
public void focusGained(FocusEvent e) {
|
||||
highlightCompletedText(0);
|
||||
}
|
||||
public void focusLost(FocusEvent e) {
|
||||
// Workaround for Bug 5100422 - Hide Popup on focus loss
|
||||
if (hidePopupOnFocusLoss) comboBox.setPopupVisible(false);
|
||||
}
|
||||
};
|
||||
configureEditor(comboBox.getEditor());
|
||||
// Handle initially selected object
|
||||
Object selected = comboBox.getSelectedItem();
|
||||
if (selected!=null) setText(selected.toString());
|
||||
highlightCompletedText(0);
|
||||
}
|
||||
|
||||
public static void enable(VComboBox comboBox) {
|
||||
// has to be editable
|
||||
comboBox.setEditable(true);
|
||||
// change the editor's document
|
||||
new AutoCompletion(comboBox);
|
||||
}
|
||||
|
||||
void configureEditor(ComboBoxEditor newEditor) {
|
||||
if (editor != null) {
|
||||
editor.removeKeyListener(editorKeyListener);
|
||||
editor.removeFocusListener(editorFocusListener);
|
||||
}
|
||||
|
||||
if (newEditor != null) {
|
||||
editor = (JTextComponent) newEditor.getEditorComponent();
|
||||
editor.addKeyListener(editorKeyListener);
|
||||
editor.addFocusListener(editorFocusListener);
|
||||
editor.setDocument(this);
|
||||
}
|
||||
}
|
||||
|
||||
public void remove(int offs, int len) throws BadLocationException {
|
||||
// return immediately when selecting an item
|
||||
if (selecting) return;
|
||||
if (hitBackspace) {
|
||||
// user hit backspace => move the selection backwards
|
||||
// old item keeps being selected
|
||||
if (offs>0) {
|
||||
if (hitBackspaceOnSelection) offs--;
|
||||
} else {
|
||||
// User hit backspace with the cursor positioned on the start => beep
|
||||
UIManager.getLookAndFeel().provideErrorFeedback(comboBox);
|
||||
}
|
||||
highlightCompletedText(offs);
|
||||
} else {
|
||||
super.remove(offs, len);
|
||||
}
|
||||
}
|
||||
|
||||
public void insertString(int offs, String str, AttributeSet a) throws BadLocationException {
|
||||
|
||||
if (selecting) return;
|
||||
|
||||
super.insertString(offs, str, a);
|
||||
|
||||
// lookup and select a matching item
|
||||
Object item = lookupItem(getText(0, getLength()));
|
||||
|
||||
if (item != null) {
|
||||
setSelectedItem(item);
|
||||
} else {
|
||||
if ( offs == 0 )
|
||||
setSelectedItem(null); //null is valid for non-mandatory fields
|
||||
//so if cursor is at start of line allow it
|
||||
// otherwise keep old item selected if there is no better match
|
||||
else
|
||||
item = comboBox.getSelectedItem();
|
||||
// undo the insertion as there isn't a valid match
|
||||
offs = offs-str.length();
|
||||
UIManager.getLookAndFeel().provideErrorFeedback(comboBox);
|
||||
}
|
||||
|
||||
if (item != null)
|
||||
setText(item.toString());
|
||||
else setText("");
|
||||
// select the completed part so it can be overwritten easily
|
||||
highlightCompletedText(offs+str.length());
|
||||
}
|
||||
|
||||
private void setText(String text) {
|
||||
try {
|
||||
// remove all text and insert the completed string
|
||||
super.remove(0, getLength());
|
||||
super.insertString(0, text, null);
|
||||
} catch (BadLocationException e) {
|
||||
throw new RuntimeException(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private void highlightCompletedText(int start) {
|
||||
editor.setCaretPosition(getLength());
|
||||
editor.moveCaretPosition(start);
|
||||
}
|
||||
|
||||
private void setSelectedItem(Object item) {
|
||||
selecting = true;
|
||||
model.setSelectedItem(item);
|
||||
selecting = false;
|
||||
}
|
||||
|
||||
private Object lookupItem(String pattern) {
|
||||
Object selectedItem = model.getSelectedItem();
|
||||
// only search for a different item if the currently selected does not match
|
||||
if (selectedItem != null && startsWithIgnoreCase(selectedItem.toString(), pattern)) {
|
||||
return selectedItem;
|
||||
} else {
|
||||
// iterate over all items
|
||||
for (int i=0, n=model.getSize(); i < n; i++) {
|
||||
Object currentItem = model.getElementAt(i);
|
||||
// current item starts with the pattern?
|
||||
if (currentItem != null && startsWithIgnoreCase(currentItem.toString(), pattern)) {
|
||||
return currentItem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// checks if str1 starts with str2 - ignores case
|
||||
private boolean startsWithIgnoreCase(String str1, String str2) {
|
||||
return str1.toUpperCase().startsWith(str2.toUpperCase());
|
||||
}
|
||||
|
||||
}
|
|
@ -152,6 +152,7 @@ public class VLookup extends JComponent
|
|||
m_columnName = columnName;
|
||||
setMandatory(mandatory);
|
||||
m_lookup = lookup;
|
||||
m_lookup.setMandatory(mandatory);
|
||||
//
|
||||
setLayout(new BorderLayout());
|
||||
VLookup_mouseAdapter mouse = new VLookup_mouseAdapter(this); // popup
|
||||
|
@ -176,13 +177,15 @@ public class VLookup extends JComponent
|
|||
if (m_lookup != null && m_lookup.getDisplayType() != DisplayType.Search) // No Search
|
||||
{
|
||||
// Memory Leak after executing the next two lines ??
|
||||
m_lookup.fillComboBox (isMandatory(), false, false, false);
|
||||
m_lookup.fillComboBox (isMandatory(), true, true, false);
|
||||
m_combo.setModel(m_lookup);
|
||||
//
|
||||
AutoCompletion.enable(m_combo);
|
||||
m_combo.addActionListener(this); // Selection
|
||||
m_combo.addMouseListener(mouse); // popup
|
||||
// FocusListener to refresh selection before opening
|
||||
m_combo.addFocusListener(this);
|
||||
m_combo.getEditor().getEditorComponent().addFocusListener(this);
|
||||
}
|
||||
|
||||
setUI (true);
|
||||
|
@ -232,6 +235,7 @@ public class VLookup extends JComponent
|
|||
m_lookup = null;
|
||||
m_mField = null;
|
||||
//
|
||||
m_combo.getEditor().getEditorComponent().removeFocusListener(this);
|
||||
m_combo.removeFocusListener(this);
|
||||
m_combo.removeActionListener(this);
|
||||
m_combo.setModel(new DefaultComboBoxModel()); // remove reference
|
||||
|
@ -430,12 +434,12 @@ public class VLookup extends JComponent
|
|||
m_value = value;
|
||||
|
||||
// Set both for switching
|
||||
m_combo.setValue (value);
|
||||
if (value == null)
|
||||
{
|
||||
m_text.setText (null);
|
||||
m_lastDisplay = "";
|
||||
m_settingValue = false;
|
||||
m_combo.setValue (value);
|
||||
return;
|
||||
}
|
||||
if (m_lookup == null)
|
||||
|
@ -443,10 +447,15 @@ public class VLookup extends JComponent
|
|||
m_text.setText (value.toString());
|
||||
m_lastDisplay = value.toString();
|
||||
m_settingValue = false;
|
||||
m_combo.setValue (value);
|
||||
return;
|
||||
}
|
||||
|
||||
//must call m_combo.setvalue after m_lookup as
|
||||
//loading of combo data might happen in m_lookup.getDisplay
|
||||
m_lastDisplay = m_lookup.getDisplay(value);
|
||||
m_combo.setValue (value);
|
||||
|
||||
if (m_lastDisplay.equals("<-1>"))
|
||||
{
|
||||
m_lastDisplay = "";
|
||||
|
@ -1188,10 +1197,7 @@ public class VLookup extends JComponent
|
|||
Object obj = m_combo.getSelectedItem();
|
||||
log.info(m_columnName + " #" + m_lookup.getSize() + ", Selected=" + obj);
|
||||
m_lookup.refresh();
|
||||
if (m_lookup.isValidated())
|
||||
m_lookup.fillComboBox(isMandatory(), false, false, false);
|
||||
else
|
||||
m_lookup.fillComboBox(isMandatory(), true, false, false);
|
||||
m_lookup.fillComboBox(isMandatory(), true, true, false);
|
||||
m_combo.setSelectedItem(obj);
|
||||
// m_combo.revalidate();
|
||||
//
|
||||
|
@ -1207,8 +1213,11 @@ public class VLookup extends JComponent
|
|||
*/
|
||||
public void focusGained (FocusEvent e)
|
||||
{
|
||||
if (e.getSource() != m_combo || e.isTemporary() || m_haveFocus || m_lookup == null)
|
||||
if ((e.getSource() != m_combo && e.getSource() != m_combo.getEditor().getEditorComponent())
|
||||
|| e.isTemporary() || m_haveFocus || m_lookup == null)
|
||||
return;
|
||||
|
||||
//avoid repeated query
|
||||
if (m_lookup.isValidated() && !m_lookup.hasInactive())
|
||||
{
|
||||
m_haveFocus = true;
|
||||
|
@ -1223,7 +1232,7 @@ public class VLookup extends JComponent
|
|||
+ " - Start Count=" + m_combo.getItemCount() + ", Selected=" + obj);
|
||||
// log.fine( "VLookupHash=" + this.hashCode());
|
||||
boolean popupVisible = m_combo.isPopupVisible();
|
||||
m_lookup.fillComboBox(isMandatory(), true, true, true); // only validated & active & temporary
|
||||
m_lookup.fillComboBox(isMandatory(), true, true, false); // only validated & active
|
||||
if (popupVisible)
|
||||
{
|
||||
//refresh
|
||||
|
@ -1266,7 +1275,7 @@ public class VLookup extends JComponent
|
|||
return;
|
||||
}
|
||||
// Combo lost focus
|
||||
if (e.getSource() != m_combo)
|
||||
if (e.getSource() != m_combo && e.getSource() != m_combo.getEditor().getEditorComponent())
|
||||
return;
|
||||
if (m_lookup.isValidated() && !m_lookup.hasInactive())
|
||||
{
|
||||
|
@ -1278,9 +1287,11 @@ public class VLookup extends JComponent
|
|||
//
|
||||
log.config(m_columnName + " = " + m_combo.getSelectedItem());
|
||||
Object obj = m_combo.getSelectedItem();
|
||||
/*
|
||||
// set original model
|
||||
if (!m_lookup.isValidated())
|
||||
m_lookup.fillComboBox(true); // previous selection
|
||||
*/
|
||||
// Set value
|
||||
if (obj != null)
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue