how would be implements autosugesion in JTextArea

2019-01-18 17:13发布

let me if have you anyone answer, this ans. basically required like as google search engine , when we press any key then it would be display suggestion related pressed key.

regard Satish Dhiman

2条回答
Animai°情兽
2楼-- · 2019-01-18 17:31

I can propose my own implementation. It is based on JList shown in JWindow. As I wanted to use this code for a combo box, I've disabled UP and DOWN keys with keyEvent.consume() call.

import javax.swing.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.JTextComponent;
import java.awt.*;
import java.awt.event.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class GAutoCompletionDecorator {

    private final JTextComponent textComponent;
    private final JWindow suggestionsPopup;
    private final JList completionList;
    private final DefaultListModel completionListModel;

    public static void decorate(JComboBox comboBox, JFrame parent) {
        comboBox.setEditable(true);
        final JTextComponent textComponent = (JTextComponent) comboBox.getEditor().getEditorComponent();
        decorate(textComponent, parent);
    }

    private static void decorate(JTextComponent textComponent, JFrame parent) {
        final GAutoCompletionDecorator autoCompletionDecorator = new GAutoCompletionDecorator(textComponent, parent);
        autoCompletionDecorator.decorate();
    }

    private GAutoCompletionDecorator(final JTextComponent textComponent, JFrame parent) {
        this.textComponent = textComponent;
        this.suggestionsPopup = new JWindow(parent);
        this.completionListModel = new DefaultListModel();
        this.completionList = new JList();
        this.completionList.setModel(completionListModel);

        this.suggestionsPopup.getContentPane().add(this.completionList);
    }

    private void decorate() {
        textComponent.getDocument().addDocumentListener(new DocumentListener() {
            public void insertUpdate(DocumentEvent documentEvent) {
                updateSuggestions();
            }

            public void removeUpdate(DocumentEvent documentEvent) {
                updateSuggestions();
            }

            public void changedUpdate(DocumentEvent documentEvent) {
                updateSuggestions();
            }
        });


        this.textComponent.addKeyListener(new KeyAdapter() {
            public void keyPressed(KeyEvent keyEvent) {
                if (keyEvent.getKeyCode() == KeyEvent.VK_DOWN || keyEvent.getKeyCode() == KeyEvent.VK_UP) {
                    updateSuggestions();
                    completionList.requestFocus();
                    completionList.dispatchEvent(keyEvent);
                    keyEvent.consume();
                }
            }

            public void keyReleased(KeyEvent keyEvent) {
                if (keyEvent.getKeyCode() == KeyEvent.VK_DOWN || keyEvent.getKeyCode() == KeyEvent.VK_UP) {
                    keyEvent.consume();
                }
            }
        });

        this.completionList.addKeyListener(new KeyAdapter() {
            public void keyPressed(KeyEvent keyEvent) {
                if (keyEvent.getKeyCode() == KeyEvent.VK_ESCAPE) {
                    textComponent.requestFocus();
                    hideSuggestionsPopup();
                } else if (keyEvent.getKeyCode() == KeyEvent.VK_UP) {
                    if (completionList.getSelectedIndex() == 0) {
                        completionList.setSelectedIndex(completionListModel.size() - 1);
                        keyEvent.consume();
                    }
                } else if (keyEvent.getKeyCode() == KeyEvent.VK_DOWN) {
                    if (completionList.getSelectedIndex() == completionListModel.size() - 1) {
                        completionList.setSelectedIndex(0);
                        keyEvent.consume();
                    }
                } else if (keyEvent.getKeyCode() == KeyEvent.VK_BACK_SPACE) {
                    textComponent.requestFocus();
                    hideSuggestionsPopup();
                    textComponent.dispatchEvent(keyEvent);
                } else if (keyEvent.getKeyCode() == KeyEvent.VK_ENTER) {
                    textComponent.requestFocus();
                    hideSuggestionsPopup();
                    final String selectedSuggestion = (String) completionList.getSelectedValue();
                    if (selectedSuggestion != null) {
                        try {
                            final int caretPosition = textComponent.getCaretPosition();
                            textComponent.getDocument().insertString(caretPosition, getCompletionString(selectedSuggestion, textComponent.getText(), caretPosition), null);
                        } catch (BadLocationException e) {
                            //ignore
                        }
                    }
                }
            }
        });
    }

    private String getCompletionString(String selectedSuggestion, String text, int caretPosition) {
        //we may insert selectedSuggestion fully of some part of it
        return selectedSuggestion;
    }

    private void updateSuggestions() {
        final String text = textComponent.getText();
        final int caretPosition = textComponent.getCaretPosition();
        final List<String> suggestions = getSuggestions(text, caretPosition);
        if (suggestions == null || suggestions.size() == 0) {
            //hide suggestions window
            hideSuggestionsPopup();
        } else {
            //show suggestions window
            showSuggestionsPopup(suggestions);
        }
    }

    private void hideSuggestionsPopup() {
        suggestionsPopup.setVisible(false);
    }

    private void showSuggestionsPopup(List<String> suggestions) {
        completionListModel.clear();
        for (String suggestion : suggestions) {
            completionListModel.addElement(suggestion);
        }

        final Point textComponentLocation = new Point(textComponent.getLocation());
        SwingUtilities.convertPointToScreen(textComponentLocation, textComponent);

        Point caretLocation = textComponent.getCaret().getMagicCaretPosition();
        if (caretLocation != null) {
            caretLocation = new Point(caretLocation);
            SwingUtilities.convertPointToScreen(caretLocation, textComponent);
        }
        suggestionsPopup.pack();
        suggestionsPopup.setLocation(caretLocation == null ? textComponentLocation.x : caretLocation.x,
                textComponentLocation.y + textComponent.getHeight());
        suggestionsPopup.setVisible(true);
    }

    private List<String> getSuggestions(String text, int caretPosition) {
        final List<String> words = new ArrayList<String>();
        words.add("suggestion 1");
        words.add("suggestion 2");
        words.add("suggestion 3");
        words.add("suggestion 4");
        words.add("suggestion 5");
        //make suggestions funny
        return text.length() < words.size() ? words.subList(0, words.size() - text.length()) : Collections.<String>emptyList();
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                final JFrame frame = new JFrame();
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

                final JComboBox comboBox = new JComboBox(new String[] {"Choice1", "Choice2"});
                comboBox.setEditable(true);
                GAutoCompletionDecorator.decorate(comboBox, frame);

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

    }
}
查看更多
闹够了就滚
3楼-- · 2019-01-18 17:42

From my comment/previous code see this update:

Using JTextField with AutoSuggestor:

enter image description here

Using JTextArea (or any other JTextComponent besides JTextField will result in Pop up window being shown under caret) with AutoSuggestor:

enter image description here

import java.awt.Color;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.Rectangle;
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.JEditorPane;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextArea;
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;
import javax.swing.text.BadLocationException;
import javax.swing.text.JTextComponent;

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

    public Test() {

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

        //JTextField f = new JTextField(10);
        JTextArea f = new JTextArea(10, 10);
        //JEditorPane f = new JEditorPane();

        //create words for dictionary could also use null as parameter for AutoSuggestor(..,..,null,..,..,..,..) and than call AutoSuggestor#setDictionary after AutoSuggestr insatnce has been created
        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");

        AutoSuggestor autoSuggestor = new AutoSuggestor(f, frame, words, Color.WHITE.brighter(), Color.BLUE, Color.RED, 0.75f) {
            @Override
            boolean wordTyped(String typedWord) {
                System.out.println(typedWord);
                return super.wordTyped(typedWord);//checks for a match in dictionary and returns true or false if found or not
            }
        };

        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 JTextComponent textComp;
    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(JTextComponent textComp, Window mainWindow, ArrayList<String> words, Color popUpBackground, Color textColor, Color suggestionFocusedColor, float opacity) {
        this.textComp = textComp;
        this.suggestionsTextColor = textColor;
        this.container = mainWindow;
        this.suggestionFocusedColor = suggestionFocusedColor;
        this.textComp.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() {
        textComp.getInputMap(JComponent.WHEN_FOCUSED).put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0, true), "Down released");
        textComp.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();
        textComp.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 = textComp.getText();
        String wordBeingTyped = "";
        text = text.replaceAll("(\\r|\\n)", " ");
        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(textComp.getWidth(), 30));
        autoSuggestionPopUpWindow.setSize(tW, tH);
        autoSuggestionPopUpWindow.setVisible(true);

        int windowX = 0;
        int windowY = 0;

        if (textComp instanceof JTextField) {//calculate x and y for JWindow at bottom of JTextField
            windowX = container.getX() + textComp.getX() + 5;
            if (suggestionsPanel.getHeight() > autoSuggestionPopUpWindow.getMinimumSize().height) {
                windowY = container.getY() + textComp.getY() + textComp.getHeight() + autoSuggestionPopUpWindow.getMinimumSize().height;
            } else {
                windowY = container.getY() + textComp.getY() + textComp.getHeight() + autoSuggestionPopUpWindow.getHeight();
            }
        } else {//calculate x and y for JWindow on any JTextComponent using the carets position
            Rectangle rect = null;
            try {
                rect = textComp.getUI().modelToView(textComp, textComp.getCaret().getDot());//get carets position
            } catch (BadLocationException ex) {
                ex.printStackTrace();
            }

            windowX = (int) (rect.getX() + 15);
            windowY = (int) (rect.getY() + (rect.getHeight() * 3));
        }

        //show the pop up
        autoSuggestionPopUpWindow.setLocation(windowX, windowY);
        autoSuggestionPopUpWindow.setMinimumSize(new Dimension(textComp.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 JTextComponent getTextField() {
        return textComp;
    }

    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 JTextComponent textComponent;
    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.textComponent = 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 = textComponent.getText();
        String typedWord = autoSuggestor.getCurrentlyTypedWord();
        String t = text.substring(0, text.lastIndexOf(typedWord));
        String tmp = t + text.substring(text.lastIndexOf(typedWord)).replace(typedWord, suggestedWord);
        textComponent.setText(tmp + " ");
    }
}

As you can see I changed the code by making its constructor accept a JTextComponent rather than a JTextField or JTextArea etc.

The problem we are left with is we have to show the pop up JWindow at a different position depending on the JTextComponent passed i.e a JTextField will have autosuggest window pop up at the bottom while JTextArea/JEditorPane etc would have the JWindow pop up under the caret/word.

Have a look at this specific method showPopUpWindow() in AutoSuggestor class:

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

    int windowX = 0;
    int windowY = 0;

    if (textComp instanceof JTextField) {//calculate x and y for JWindow at bottom of JTextField
        windowX = container.getX() + textComp.getX() + 5;
        if (suggestionsPanel.getHeight() > autoSuggestionPopUpWindow.getMinimumSize().height) {
            windowY = container.getY() + textComp.getY() + textComp.getHeight() + autoSuggestionPopUpWindow.getMinimumSize().height;
        } else {
            windowY = container.getY() + textComp.getY() + textComp.getHeight() + autoSuggestionPopUpWindow.getHeight();
        }
    } else {//calculate x and y for JWindow on any JTextComponent using the carets position
        Rectangle rect = null;
        try {
            rect = textComp.getUI().modelToView(textComp, textComp.getCaret().getDot());//get carets position
        } catch (BadLocationException ex) {
            ex.printStackTrace();
        }

        windowX = (int) (rect.getX() + 15);
        windowY = (int) (rect.getY() + (rect.getHeight() * 3));
    }

    //show the pop up
    autoSuggestionPopUpWindow.setLocation(windowX, windowY);
    autoSuggestionPopUpWindow.setMinimumSize(new Dimension(textComp.getWidth(), 30));
    autoSuggestionPopUpWindow.revalidate();
    autoSuggestionPopUpWindow.repaint();

}

As you can see we check to see what instance the JTextComponent is and if its not a JTextField simply get the caret position (via the Rectangle of the caret) of the JTextComponent and position JWindow pop up from there (underneath the caret in my case).

查看更多
登录 后发表回答