how would be implements autosugesion in JTextArea swing

前端 未结 2 439
南旧
南旧 2020-12-10 09:42

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.<

相关标签:
2条回答
  • 2020-12-10 10:11

    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);
                }
            });
    
        }
    }
    
    0 讨论(0)
  • 2020-12-10 10:22

    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).

    0 讨论(0)
提交回复
热议问题