Create a autocompleting textbox in Java with a dropdown list

Solution 1:

@syb0rg's answer is easier, as it uses an 3rd party library.

However I used an alternate approach:

It uses a custom class called AutoSuggestor which accepts a JTextField, its Window an ArrayList<String> of words to check typed words against, a background color and text color, and suggestion focus colour as well as an opacity value. By passing JTextField reference a DocumentListener is added which will do the work of checking what word is typed and whether to display suggestions or not and if so what suggestions to display. When a word is typed the DocumentListener will firewordTyped(String wordTyped) method with the current word being typed or (at least how much ever of the word has been typed), in wordTyped(..) the word will be checked against those in the AutoSuggestors classes dictionary which is a basic ArrayList of String this can be set on the fly as seen in the below example:

enter image description here

(For now you will have to use mouse and click the word you want to be auto completed, or use DOWN to transverse suggestions and the textfield and ENTER to select suggestion when traversing using down key. I have not yet implemented UP yet):

import java.awt.Color;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import javax.swing.AbstractAction;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.JWindow;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.border.LineBorder;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;

/**
 * @author David
 */
public class Test {

    public Test() {

        JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);


        JTextField f = new JTextField(10);

        AutoSuggestor autoSuggestor = new AutoSuggestor(f, frame, null, Color.WHITE.brighter(), Color.BLUE, Color.RED, 0.75f) {
            @Override
            boolean wordTyped(String typedWord) {

                //create list for dictionary this in your case might be done via calling a method which queries db and returns results as arraylist
                ArrayList<String> words = new ArrayList<>();
                words.add("hello");
                words.add("heritage");
                words.add("happiness");
                words.add("goodbye");
                words.add("cruel");
                words.add("car");
                words.add("war");
                words.add("will");
                words.add("world");
                words.add("wall");


                setDictionary(words);
                //addToDictionary("bye");//adds a single word

                return super.wordTyped(typedWord);//now call super to check for any matches against newest dictionary
            }
        };

        JPanel p = new JPanel();

        p.add(f);

        frame.add(p);

        frame.pack();
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                new Test();
            }
        });
    }
}

class AutoSuggestor {

    private final JTextField textField;
    private final Window container;
    private JPanel suggestionsPanel;
    private JWindow autoSuggestionPopUpWindow;
    private String typedWord;
    private final ArrayList<String> dictionary = new ArrayList<>();
    private int currentIndexOfSpace, tW, tH;
    private DocumentListener documentListener = new DocumentListener() {
        @Override
        public void insertUpdate(DocumentEvent de) {
            checkForAndShowSuggestions();
        }

        @Override
        public void removeUpdate(DocumentEvent de) {
            checkForAndShowSuggestions();
        }

        @Override
        public void changedUpdate(DocumentEvent de) {
            checkForAndShowSuggestions();
        }
    };
    private final Color suggestionsTextColor;
    private final Color suggestionFocusedColor;

    public AutoSuggestor(JTextField textField, Window mainWindow, ArrayList<String> words, Color popUpBackground, Color textColor, Color suggestionFocusedColor, float opacity) {
        this.textField = textField;
        this.suggestionsTextColor = textColor;
        this.container = mainWindow;
        this.suggestionFocusedColor = suggestionFocusedColor;
        this.textField.getDocument().addDocumentListener(documentListener);

        setDictionary(words);

        typedWord = "";
        currentIndexOfSpace = 0;
        tW = 0;
        tH = 0;

        autoSuggestionPopUpWindow = new JWindow(mainWindow);
        autoSuggestionPopUpWindow.setOpacity(opacity);

        suggestionsPanel = new JPanel();
        suggestionsPanel.setLayout(new GridLayout(0, 1));
        suggestionsPanel.setBackground(popUpBackground);

        addKeyBindingToRequestFocusInPopUpWindow();
    }

    private void addKeyBindingToRequestFocusInPopUpWindow() {
        textField.getInputMap(JComponent.WHEN_FOCUSED).put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0, true), "Down released");
        textField.getActionMap().put("Down released", new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent ae) {//focuses the first label on popwindow
                for (int i = 0; i < suggestionsPanel.getComponentCount(); i++) {
                    if (suggestionsPanel.getComponent(i) instanceof SuggestionLabel) {
                        ((SuggestionLabel) suggestionsPanel.getComponent(i)).setFocused(true);
                        autoSuggestionPopUpWindow.toFront();
                        autoSuggestionPopUpWindow.requestFocusInWindow();
                        suggestionsPanel.requestFocusInWindow();
                        suggestionsPanel.getComponent(i).requestFocusInWindow();
                        break;
                    }
                }
            }
        });
        suggestionsPanel.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0, true), "Down released");
        suggestionsPanel.getActionMap().put("Down released", new AbstractAction() {
            int lastFocusableIndex = 0;

            @Override
            public void actionPerformed(ActionEvent ae) {//allows scrolling of labels in pop window (I know very hacky for now :))

                ArrayList<SuggestionLabel> sls = getAddedSuggestionLabels();
                int max = sls.size();

                if (max > 1) {//more than 1 suggestion
                    for (int i = 0; i < max; i++) {
                        SuggestionLabel sl = sls.get(i);
                        if (sl.isFocused()) {
                            if (lastFocusableIndex == max - 1) {
                                lastFocusableIndex = 0;
                                sl.setFocused(false);
                                autoSuggestionPopUpWindow.setVisible(false);
                                setFocusToTextField();
                                checkForAndShowSuggestions();//fire method as if document listener change occured and fired it

                            } else {
                                sl.setFocused(false);
                                lastFocusableIndex = i;
                            }
                        } else if (lastFocusableIndex <= i) {
                            if (i < max) {
                                sl.setFocused(true);
                                autoSuggestionPopUpWindow.toFront();
                                autoSuggestionPopUpWindow.requestFocusInWindow();
                                suggestionsPanel.requestFocusInWindow();
                                suggestionsPanel.getComponent(i).requestFocusInWindow();
                                lastFocusableIndex = i;
                                break;
                            }
                        }
                    }
                } else {//only a single suggestion was given
                    autoSuggestionPopUpWindow.setVisible(false);
                    setFocusToTextField();
                    checkForAndShowSuggestions();//fire method as if document listener change occured and fired it
                }
            }
        });
    }

    private void setFocusToTextField() {
        container.toFront();
        container.requestFocusInWindow();
        textField.requestFocusInWindow();
    }

    public ArrayList<SuggestionLabel> getAddedSuggestionLabels() {
        ArrayList<SuggestionLabel> sls = new ArrayList<>();
        for (int i = 0; i < suggestionsPanel.getComponentCount(); i++) {
            if (suggestionsPanel.getComponent(i) instanceof SuggestionLabel) {
                SuggestionLabel sl = (SuggestionLabel) suggestionsPanel.getComponent(i);
                sls.add(sl);
            }
        }
        return sls;
    }

    private void checkForAndShowSuggestions() {
        typedWord = getCurrentlyTypedWord();

        suggestionsPanel.removeAll();//remove previos words/jlabels that were added

        //used to calcualte size of JWindow as new Jlabels are added
        tW = 0;
        tH = 0;

        boolean added = wordTyped(typedWord);

        if (!added) {
            if (autoSuggestionPopUpWindow.isVisible()) {
                autoSuggestionPopUpWindow.setVisible(false);
            }
        } else {
            showPopUpWindow();
            setFocusToTextField();
        }
    }

    protected void addWordToSuggestions(String word) {
        SuggestionLabel suggestionLabel = new SuggestionLabel(word, suggestionFocusedColor, suggestionsTextColor, this);

        calculatePopUpWindowSize(suggestionLabel);

        suggestionsPanel.add(suggestionLabel);
    }

    public String getCurrentlyTypedWord() {//get newest word after last white spaceif any or the first word if no white spaces
        String text = textField.getText();
        String wordBeingTyped = "";
        if (text.contains(" ")) {
            int tmp = text.lastIndexOf(" ");
            if (tmp >= currentIndexOfSpace) {
                currentIndexOfSpace = tmp;
                wordBeingTyped = text.substring(text.lastIndexOf(" "));
            }
        } else {
            wordBeingTyped = text;
        }
        return wordBeingTyped.trim();
    }

    private void calculatePopUpWindowSize(JLabel label) {
        //so we can size the JWindow correctly
        if (tW < label.getPreferredSize().width) {
            tW = label.getPreferredSize().width;
        }
        tH += label.getPreferredSize().height;
    }

    private void showPopUpWindow() {
        autoSuggestionPopUpWindow.getContentPane().add(suggestionsPanel);
        autoSuggestionPopUpWindow.setMinimumSize(new Dimension(textField.getWidth(), 30));
        autoSuggestionPopUpWindow.setSize(tW, tH);
        autoSuggestionPopUpWindow.setVisible(true);

        int windowX = 0;
        int windowY = 0;

        windowX = container.getX() + textField.getX() + 5;
        if (suggestionsPanel.getHeight() > autoSuggestionPopUpWindow.getMinimumSize().height) {
            windowY = container.getY() + textField.getY() + textField.getHeight() + autoSuggestionPopUpWindow.getMinimumSize().height;
        } else {
            windowY = container.getY() + textField.getY() + textField.getHeight() + autoSuggestionPopUpWindow.getHeight();
        }

        autoSuggestionPopUpWindow.setLocation(windowX, windowY);
        autoSuggestionPopUpWindow.setMinimumSize(new Dimension(textField.getWidth(), 30));
        autoSuggestionPopUpWindow.revalidate();
        autoSuggestionPopUpWindow.repaint();

    }

    public void setDictionary(ArrayList<String> words) {
        dictionary.clear();
        if (words == null) {
            return;//so we can call constructor with null value for dictionary without exception thrown
        }
        for (String word : words) {
            dictionary.add(word);
        }
    }

    public JWindow getAutoSuggestionPopUpWindow() {
        return autoSuggestionPopUpWindow;
    }

    public Window getContainer() {
        return container;
    }

    public JTextField getTextField() {
        return textField;
    }

    public void addToDictionary(String word) {
        dictionary.add(word);
    }

    boolean wordTyped(String typedWord) {

        if (typedWord.isEmpty()) {
            return false;
        }
        //System.out.println("Typed word: " + typedWord);

        boolean suggestionAdded = false;

        for (String word : dictionary) {//get words in the dictionary which we added
            boolean fullymatches = true;
            for (int i = 0; i < typedWord.length(); i++) {//each string in the word
                if (!typedWord.toLowerCase().startsWith(String.valueOf(word.toLowerCase().charAt(i)), i)) {//check for match
                    fullymatches = false;
                    break;
                }
            }
            if (fullymatches) {
                addWordToSuggestions(word);
                suggestionAdded = true;
            }
        }
        return suggestionAdded;
    }
}

class SuggestionLabel extends JLabel {

    private boolean focused = false;
    private final JWindow autoSuggestionsPopUpWindow;
    private final JTextField textField;
    private final AutoSuggestor autoSuggestor;
    private Color suggestionsTextColor, suggestionBorderColor;

    public SuggestionLabel(String string, final Color borderColor, Color suggestionsTextColor, AutoSuggestor autoSuggestor) {
        super(string);

        this.suggestionsTextColor = suggestionsTextColor;
        this.autoSuggestor = autoSuggestor;
        this.textField = autoSuggestor.getTextField();
        this.suggestionBorderColor = borderColor;
        this.autoSuggestionsPopUpWindow = autoSuggestor.getAutoSuggestionPopUpWindow();

        initComponent();
    }

    private void initComponent() {
        setFocusable(true);
        setForeground(suggestionsTextColor);

        addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent me) {
                super.mouseClicked(me);

                replaceWithSuggestedText();

                autoSuggestionsPopUpWindow.setVisible(false);
            }
        });

        getInputMap(JComponent.WHEN_FOCUSED).put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0, true), "Enter released");
        getActionMap().put("Enter released", new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent ae) {
                replaceWithSuggestedText();
                autoSuggestionsPopUpWindow.setVisible(false);
            }
        });
    }

    public void setFocused(boolean focused) {
        if (focused) {
            setBorder(new LineBorder(suggestionBorderColor));
        } else {
            setBorder(null);
        }
        repaint();
        this.focused = focused;
    }

    public boolean isFocused() {
        return focused;
    }

    private void replaceWithSuggestedText() {
        String suggestedWord = getText();
        String text = textField.getText();
        String typedWord = autoSuggestor.getCurrentlyTypedWord();
        String t = text.substring(0, text.lastIndexOf(typedWord));
        String tmp = t + text.substring(text.lastIndexOf(typedWord)).replace(typedWord, suggestedWord);
        textField.setText(tmp + " ");
    }
}

As it stands the only possible needed additions IMO is:

  • UP key Focus traversablity for items within the popup autosuggestions box so we can go in a upward direction.

If there are any kinks lemme know I'll see what I can do. But Seems to be running fine (touch wood).

Solution 2:

A really easy way to do this is to use the GlazedList implementation of auto-completion. It's very easy to get up and running. You can find it here.

You can install the auto-complete on a JComboBox with only one line of Glazed code, like this:

JComboBox comboBox = new JComboBox();
Object[] elements = new Object[] {"Cat", "Dog", "Lion", "Mouse"};
AutoCompleteSupport.install(comboBox, GlazedLists.eventListOf(elements));

Also SwingX supports auto-complete and might be easier to use than GlazedList. All you write with SwingX is AutoCompleteDecorator.decorate(comboBox);

Solution 3:

To use TextAutoCompleter class you need to download a jar file AutoCompleter.jar and add it to library folder of your project and here is the link to download: http://download1689.mediafire.com/4grrthscpsug/7pwzgefiomu392o/AutoCompleter.jar -Nawin

//In the Main class write the following code

package autocomplete;

import com.mxrck.autocompleter.TextAutoCompleter;
import java.sql.SQLException;
import javax.swing.JFrame;
import javax.swing.JTextField;


public class AutoComplete {
    JFrame f=new JFrame();
    JTextField t1;
AutoComplete() throws ClassNotFoundException, SQLException{

    f.setSize(500,500);
    f.setLocation(500,100);
    f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    f.setLayout(null);
    f.setVisible(true);


    t1=new JTextField();
    t1.setBounds(50,80,200,20);
    f.add(t1);


    TextAutoCompleter complete=new TextAutoCompleter(t1);
    DBConn conn=new DBConn();
        conn.connection();
        conn.retrieve();
    while(conn.rs.next()){

        complete.addItem(conn.rs.getString("number"));
    }


}


    public static void main(String[] args) throws ClassNotFoundException,
    SQLException{           

        new AutoComplete();
   }

}


//Create seperate class for database connection and write the following code


package autocomplete;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;


public class DBConn {

    Connection con; ResultSet rs;PreparedStatement stat;

public void connection() throws ClassNotFoundException, SQLException{
    String url="jdbc:mysql://localhost:3306/";
    String driver="com.mysql.jdbc.Driver";   
    String db="demo";
    String username="root";
    String password="root";
    stat =null; 

        Class.forName(driver);
       con=(Connection)DriverManager.getConnection
       (url+db,username,password);              
        System.out.println("Connecttion SuccessFul");
}  

public void retrieve() throws SQLException{

    Statement  stmt=con.createStatement();
    String query="select number from phone";
    rs = stmt.executeQuery(query);

    System.out.println("retrieve succesfully");

}

}

Solution 4:

Built on top of David's solution:

I added functionality for the UP key, as well added functionality for the ESC key in order to hide the popup window. In addition to that, you can specify a callback function when constructing a AutoSuggestor object, which will be called when a suggestion from the list is selected.

import javax.swing.border.LineBorder
import java.util.ArrayList
import javax.swing.event.DocumentListener

import java.awt.*
import java.awt.event.*
import javax.swing.*
import javax.swing.event.DocumentEvent

/**
 * Author of the original version: David @ https://stackoverflow.com/users/1133011/david-kroukamp
 */
class Test {
    init {

        val frame = JFrame()
        frame.defaultCloseOperation = JFrame.EXIT_ON_CLOSE


        val f = JTextField(10)

        val autoSuggestor = object : AutoSuggestor(f, frame, ArrayList(), Color.WHITE.brighter(), Color.BLUE, Color.RED, 0.75f) {
            override fun wordTyped(typedWord: String?): Boolean {

                //create list for dictionary this in your case might be done via calling a method which queries db and returns results as arraylist
                val words = ArrayList<String>()
                words.add("hello")
                words.add("heritage")
                words.add("happiness")
                words.add("goodbye")
                words.add("cruel")
                words.add("car")
                words.add("war")
                words.add("will")
                words.add("world")
                words.add("wall")


                setDictionary(words)
                //addToDictionary("bye");//adds a single word

                return super.wordTyped(typedWord)//now call super to check for any matches against newest dictionary
            }
        }

        val p = JPanel()

        p.add(f)

        frame.add(p)

        frame.pack()
        frame.isVisible = true
    }

    companion object {

        @JvmStatic
        fun main(args: Array<String>) {
            SwingUtilities.invokeLater { Test() }
        }
    }
}

internal open class AutoSuggestor(val textField: JTextField, val container: Window, words: ArrayList<String>, popUpBackground: Color, private val suggestionsTextColor: Color, private val suggestionFocusedColor: Color, opacity: Float, private val callback: (String) -> Unit = {}) {
    private val suggestionsPanel: JPanel
    val autoSuggestionPopUpWindow: JWindow
    private var typedWord: String? = null
    private val dictionary = ArrayList<String>()
    private var currentIndexOfSpace: Int = 0
    private var tW: Int = 0
    private var tH: Int = 0
    private val documentListener = object : DocumentListener {
        override fun insertUpdate(de: DocumentEvent) {
            checkForAndShowSuggestions()
        }

        override fun removeUpdate(de: DocumentEvent) {
            checkForAndShowSuggestions()
        }

        override fun changedUpdate(de: DocumentEvent) {
            checkForAndShowSuggestions()
        }
    }

    val addedSuggestionLabels: ArrayList<SuggestionLabel>
        get() {
            val sls = ArrayList<SuggestionLabel>()
            for (i in 0 until suggestionsPanel.componentCount) {
                if (suggestionsPanel.getComponent(i) is SuggestionLabel) {
                    val sl = suggestionsPanel.getComponent(i) as SuggestionLabel
                    sls.add(sl)
                }
            }
            return sls
        }

    //get newest word after last white space if any or the first word if no white spaces
    val currentlyTypedWord: String
        get() {
            val text = textField.text
            var wordBeingTyped = ""
            if (text.contains(" ")) {
                val tmp = text.lastIndexOf(" ")
                if (tmp >= currentIndexOfSpace) {
                    currentIndexOfSpace = tmp
                    wordBeingTyped = text.substring(text.lastIndexOf(" "))
                }
            } else {
                wordBeingTyped = text
            }
            return wordBeingTyped.trim { it <= ' ' }
        }

    init {
        this.textField.document.addDocumentListener(documentListener)

        setDictionary(words)

        typedWord = ""
        currentIndexOfSpace = 0
        tW = 0
        tH = 0

        autoSuggestionPopUpWindow = JWindow(container)
        autoSuggestionPopUpWindow.opacity = opacity

        suggestionsPanel = JPanel()
        suggestionsPanel.layout = GridLayout(0, 1)
        suggestionsPanel.background = popUpBackground

        addFocusListenersToHandleVisibilityOfPopUpWindow()
        addKeyBindingToRequestFocusInPopUpWindow()
    }

    private fun addFocusListenersToHandleVisibilityOfPopUpWindow() {
        textField.addFocusListener(object:FocusListener {
            override fun focusLost(e: FocusEvent?) {
                var focusOnPopUp = false
                for (i in 0 until suggestionsPanel.componentCount) {
                    if (suggestionsPanel.getComponent(i) is SuggestionLabel) {
                        val label = suggestionsPanel.getComponent(i) as SuggestionLabel
                        if (label.isFocused)
                            focusOnPopUp = true
                    }
                }

                if (!focusOnPopUp && !shouldShowPopUpWindow) {
                    autoSuggestionPopUpWindow.isVisible = false
                }
            }
            override fun focusGained(e: FocusEvent?) {
                shouldShowPopUpWindow = false
            }
        })
    }

    private fun addKeyBindingToRequestFocusInPopUpWindow() {
        textField.getInputMap(JComponent.WHEN_FOCUSED).put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0, true), "Escape released")
        textField.actionMap.put("Escape released", object : AbstractAction() {
            override fun actionPerformed(ae: ActionEvent) {// Hide the popwindow
                shouldShowPopUpWindow = false
                autoSuggestionPopUpWindow.isVisible = false
            }
        })


        textField.getInputMap(JComponent.WHEN_FOCUSED).put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0, true), "Down released")
        textField.actionMap.put("Down released", object : AbstractAction() {
            override fun actionPerformed(ae: ActionEvent) {//focuses the first label on popwindow
                for (i in 0 until suggestionsPanel.componentCount) {
                    if (suggestionsPanel.getComponent(i) is SuggestionLabel) {
                        (suggestionsPanel.getComponent(i) as SuggestionLabel).isFocused = true
                        autoSuggestionPopUpWindow.toFront()
                        autoSuggestionPopUpWindow.requestFocusInWindow()
                        suggestionsPanel.requestFocusInWindow()
                        suggestionsPanel.getComponent(i).requestFocusInWindow()
                        break
                    }
                }
            }
        })

        textField.getInputMap(JComponent.WHEN_FOCUSED).put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0, true), "Up released")
        textField.actionMap.put("Up released", object : AbstractAction() {
            override fun actionPerformed(ae: ActionEvent) {//focuses the last label on popwindow
                for (i in 0 until suggestionsPanel.componentCount) {
                    val reverseIndex = suggestionsPanel.componentCount-1 - i
                    if (suggestionsPanel.getComponent(reverseIndex) is SuggestionLabel) {
                        (suggestionsPanel.getComponent(reverseIndex) as SuggestionLabel).isFocused = true
                        autoSuggestionPopUpWindow.toFront()
                        autoSuggestionPopUpWindow.requestFocusInWindow()
                        suggestionsPanel.requestFocusInWindow()
                        suggestionsPanel.getComponent(reverseIndex).requestFocusInWindow()
                        break
                    }
                }
            }
        })

        suggestionsPanel.getInputMap(JComponent.WHEN_FOCUSED).put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0, true), "Escape released")
        suggestionsPanel.actionMap.put("Escape released", object : AbstractAction() {
            override fun actionPerformed(ae: ActionEvent) {// Hide the popwindow
                shouldShowPopUpWindow = false
                autoSuggestionPopUpWindow.isVisible = false
            }
        })
        suggestionsPanel.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0, true), "Up released")
        suggestionsPanel.actionMap.put("Up released", object : AbstractAction() {
            override fun actionPerformed(ae: ActionEvent) {//allows scrolling of labels in pop window (I know very hacky for now :))

                val sls = addedSuggestionLabels
                val max = sls.size

                var indexOfFocusedSuggestion = -1
                for (i in 0 until max) {
                    val sl = sls[i]
                    if ( sl.isFocused )
                        indexOfFocusedSuggestion = i
                }

                if (indexOfFocusedSuggestion - 1 < 0) {
                    sls[indexOfFocusedSuggestion].isFocused = false
                    autoSuggestionPopUpWindow.isVisible = false
                    setFocusToTextField()
                    checkForAndShowSuggestions()//fire method as if document listener change occured and fired it
                }
                else {
                    sls[indexOfFocusedSuggestion].isFocused = false
                    sls[indexOfFocusedSuggestion-1].isFocused = true
                    autoSuggestionPopUpWindow.toFront()
                    autoSuggestionPopUpWindow.requestFocusInWindow()
                    suggestionsPanel.requestFocusInWindow()
                    suggestionsPanel.getComponent(indexOfFocusedSuggestion-1).requestFocusInWindow()
                }

            }
        })
        suggestionsPanel.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0, true), "Down released")
        suggestionsPanel.actionMap.put("Down released", object : AbstractAction() {
            override fun actionPerformed(ae: ActionEvent) {//allows scrolling of labels in pop window (I know very hacky for now :))

                val sls = addedSuggestionLabels
                val max = sls.size

                var indexOfFocusedSuggestion = -1
                for (i in 0 until max) {
                    val sl = sls[i]
                    if ( sl.isFocused )
                        indexOfFocusedSuggestion = i
                }

                if (indexOfFocusedSuggestion + 1 >= max) {
                    sls[indexOfFocusedSuggestion].isFocused = false
                    autoSuggestionPopUpWindow.isVisible = false
                    setFocusToTextField()
                    checkForAndShowSuggestions()//fire method as if document listener change occured and fired it
                }
                else {
                    sls[indexOfFocusedSuggestion].isFocused = false
                    sls[indexOfFocusedSuggestion+1].isFocused = true
                    autoSuggestionPopUpWindow.toFront()
                    autoSuggestionPopUpWindow.requestFocusInWindow()
                    suggestionsPanel.requestFocusInWindow()
                    suggestionsPanel.getComponent(indexOfFocusedSuggestion+1).requestFocusInWindow()
                }
            }

        })
    }

    private fun setFocusToTextField() {
        container.toFront()
        container.requestFocusInWindow()
        textField.requestFocusInWindow()
    }

    var shouldShowPopUpWindow = false

    private fun checkForAndShowSuggestions() {
        typedWord = currentlyTypedWord

        suggestionsPanel.removeAll()//remove previos words/jlabels that were added

        //used to calcualte size of JWindow as new Jlabels are added
        tW = 0
        tH = 0

        val added = wordTyped(typedWord)

        if (!added) {
            if (autoSuggestionPopUpWindow.isVisible) {
                autoSuggestionPopUpWindow.isVisible = false
            }
        } else {
            shouldShowPopUpWindow = true
            showPopUpWindow()
            setFocusToTextField()
        }
    }

    protected fun addWordToSuggestions(word: String) {
        val suggestionLabel = SuggestionLabel(word, suggestionFocusedColor, suggestionsTextColor, this, callback)

        calculatePopUpWindowSize(suggestionLabel)

        suggestionsPanel.add(suggestionLabel)
    }

    private fun calculatePopUpWindowSize(label: JLabel) {
        //so we can size the JWindow correctly
        if (tW < label.preferredSize.width) {
            tW = label.preferredSize.width
        }
        tH += label.preferredSize.height
    }

    private fun showPopUpWindow() {
        autoSuggestionPopUpWindow.contentPane.add(suggestionsPanel)
        autoSuggestionPopUpWindow.minimumSize = Dimension(textField.width, 30)
        autoSuggestionPopUpWindow.setSize(tW, tH)
        autoSuggestionPopUpWindow.isVisible = true

        var windowX = 0
        var windowY = 0

        windowX = container.getX() + textField.x + 5
        if (suggestionsPanel.height > autoSuggestionPopUpWindow.minimumSize.height) {
            windowY = container.getY() + textField.y + textField.height + autoSuggestionPopUpWindow.minimumSize.height
        } else {
            windowY = container.getY() + textField.y + textField.height + autoSuggestionPopUpWindow.height
        }

        autoSuggestionPopUpWindow.setLocation(windowX, windowY)
        autoSuggestionPopUpWindow.minimumSize = Dimension(textField.width, 30)
        autoSuggestionPopUpWindow.revalidate()
        autoSuggestionPopUpWindow.repaint()

    }

    fun setDictionary(words: ArrayList<String>?) {
        dictionary.clear()
        if (words == null) {
            return //so we can call constructor with null value for dictionary without exception thrown
        }
        for (word in words) {
            dictionary.add(word)
        }
    }

    fun addToDictionary(word: String) {
        dictionary.add(word)
    }

    open fun wordTyped(typedWord: String?): Boolean {

        if (typedWord!!.isEmpty()) {
            return false
        }

        var suggestionAdded = false

        for (word in dictionary) {//get words in the dictionary which we added
            var fullyMatches = word.length >= typedWord.length
            for (i in 0 until typedWord.length) {//each string in the word
                if (word.length > i && !typedWord.toLowerCase().startsWith(word.toLowerCase()[i].toString(), i)) {//check for match
                    fullyMatches = false
                    break
                }
            }
            if (fullyMatches) {
                addWordToSuggestions(word)
                suggestionAdded = true
            }
        }
        return suggestionAdded
    }
}

internal class SuggestionLabel(string: String, private val suggestionBorderColor: Color, private val suggestionsTextColor: Color, private val autoSuggestor: AutoSuggestor, private val callback: (String) -> Unit) : JLabel(string) {

    var isFocused = false
        set(focused) {
            if (focused) {
                border = LineBorder(suggestionBorderColor)
            } else {
                border = null
            }
            repaint()
            field = focused
        }
    private val autoSuggestionsPopUpWindow: JWindow
    private val textField: JTextField

    init {
        this.textField = autoSuggestor.textField
        this.autoSuggestionsPopUpWindow = autoSuggestor.autoSuggestionPopUpWindow

        initComponent()
    }

    private fun initComponent() {
        isFocusable = true
        foreground = suggestionsTextColor

        addMouseListener(object : MouseAdapter() {
            override fun mouseClicked(me: MouseEvent) {
                super.mouseClicked(me)

                replaceWithSuggestedText()

                autoSuggestionsPopUpWindow.isVisible = false
            }
        })

        getInputMap(JComponent.WHEN_FOCUSED).put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0, true), "Enter released")
        actionMap.put("Enter released", object : AbstractAction() {
            override fun actionPerformed(ae: ActionEvent) {
                replaceWithSuggestedText()
                autoSuggestionsPopUpWindow.isVisible = false
            }
        })

        getInputMap(JComponent.WHEN_FOCUSED).put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0, true), "Escape released")
        actionMap.put("Escape released", object : AbstractAction() {
            override fun actionPerformed(ae: ActionEvent) {// Hide the popwindow
                autoSuggestionsPopUpWindow.isVisible = false
            }
        })
    }

    private fun replaceWithSuggestedText() {

        val suggestedWord = text
        val text = textField.text
        val typedWord = autoSuggestor.currentlyTypedWord
        val t = text.substring(0, text.lastIndexOf(typedWord))
        val tmp = t + text.substring(text.lastIndexOf(typedWord)).replace(typedWord, suggestedWord)
        textField.text = tmp
        callback(tmp)
    }
}

Note: The above is written in Kotlin, but if you really want Java code, you can easily convert it back to Java.

Solution 5:

I wanted auto-completion for the editor in my AVR assembler IDE so I wrote an implementation that works just like the auto-completion in Eclipse (CTRL-SPACE activation, drop-down list with scrollbars, cursor keys + mouse navigation). It has no external dependencies and is just a single class. It should work for all JTextComponent subclasses ; you can find a usage example in the src/test folder.