diff --git a/client/src/org/compiere/grid/ed/AutoCompletion.java b/client/src/org/compiere/grid/ed/AutoCompletion.java new file mode 100644 index 0000000000..d66210c723 --- /dev/null +++ b/client/src/org/compiere/grid/ed/AutoCompletion.java @@ -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()); + } + +} \ No newline at end of file diff --git a/client/src/org/compiere/grid/ed/VLookup.java b/client/src/org/compiere/grid/ed/VLookup.java index f702cbb25e..a068784142 100644 --- a/client/src/org/compiere/grid/ed/VLookup.java +++ b/client/src/org/compiere/grid/ed/VLookup.java @@ -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) {