当前位置: 首页 > 知识库问答 >
问题:

使用RichTextFX的JavaFX拼写检查器如何创建右键单击建议

叶经略
2023-03-14

我这里有一个拼写检查器演示,在视觉上它正是我想要的(不正确的单词用红色下划线),但我在创建右键单击上下文菜单以应用建议时遇到了问题。

我能够在Text对象上获得上下文菜单,但我无法找到文本在框中的位置以使用预测进行替换。

这是代码:

pom.xml

    <dependency>
        <groupId>org.fxmisc.richtext</groupId>
        <artifactId>richtextfx</artifactId>
        <version>0.10.6</version>
    </dependency>
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-text</artifactId>
        <version>1.9</version>
        <type>jar</type>
    </dependency>

拼写检查演示。Java语言

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.text.BreakIterator;
import java.time.Duration;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

import org.fxmisc.flowless.VirtualizedScrollPane;
import org.fxmisc.richtext.StyleClassedTextArea;
import org.fxmisc.richtext.model.StyleSpans;
import org.fxmisc.richtext.model.StyleSpansBuilder;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.MenuItem;
import javafx.scene.input.ContextMenuEvent;
import javafx.scene.layout.StackPane;
import javafx.scene.text.Text;
import javafx.stage.Stage;
import org.apache.commons.text.similarity.JaroWinklerDistance;
import org.reactfx.Subscription;

public class SpellCheckingDemo extends Application
{

    private static final Set<String> dictionary = new HashSet<String>();
    private final static double JAROWINKLERDISTANCE_THRESHOLD = .80;

    public static void main(String[] args)
    {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage)
    {
        StyleClassedTextArea textArea = new StyleClassedTextArea();
        textArea.setWrapText(true);

        Subscription cleanupWhenFinished = textArea.multiPlainChanges()
                .successionEnds(Duration.ofMillis(500))
                .subscribe(change ->
                {
                    textArea.setStyleSpans(0, computeHighlighting(textArea.getText()));
                });
        // call when no longer need it: `cleanupWhenFinished.unsubscribe();`

        textArea.setOnContextMenuRequested((ContextMenuEvent event) ->
        {
            if (event.getTarget() instanceof Text)
            {
                Text text = (Text) event.getTarget();
                ContextMenu context = new ContextMenu();
                JaroWinklerDistance distance = new JaroWinklerDistance();
                for (String word : dictionary)
                {
                    if (distance.apply(text.getText(), word) >= JAROWINKLERDISTANCE_THRESHOLD)
                    {
                        MenuItem item = new MenuItem(word);
                        item.setOnAction(a ->
                        {
                            // how do I find the position of the Text object ?                    
                            textArea.replaceText(25, 25 + text.getText().length(), word);
                        });
                        context.getItems().add(item);

                    }

                }

                context.show(primaryStage, event.getScreenX(), event.getScreenY());

            }
        });

        // load the dictionary
        try (InputStream input = SpellCheckingDemo.class.getResourceAsStream("/spellchecking.dict");
                BufferedReader br = new BufferedReader(new InputStreamReader(input)))
        {
            String line;
            while ((line = br.readLine()) != null)
            {
                dictionary.add(line);
            }
        } catch (IOException e)
        {
            e.printStackTrace();
        }

        // load the sample document
        InputStream input2 = SpellCheckingDemo.class.getResourceAsStream("/spellchecking.txt");
        try (java.util.Scanner s = new java.util.Scanner(input2))
        {
            String document = s.useDelimiter("\\A").hasNext() ? s.next() : "";
            textArea.replaceText(0, 0, document);
        }

        Scene scene = new Scene(new StackPane(new VirtualizedScrollPane<>(textArea)), 600, 400);
        scene.getStylesheets().add(SpellCheckingDemo.class.getResource("/spellchecking.css").toExternalForm());
        primaryStage.setScene(scene);
        primaryStage.setTitle("Spell Checking Demo");
        primaryStage.show();
    }

    private static StyleSpans<Collection<String>> computeHighlighting(String text)
    {

        StyleSpansBuilder<Collection<String>> spansBuilder = new StyleSpansBuilder<>();

        BreakIterator wb = BreakIterator.getWordInstance();
        wb.setText(text);

        int lastIndex = wb.first();
        int lastKwEnd = 0;
        while (lastIndex != BreakIterator.DONE)
        {
            int firstIndex = lastIndex;
            lastIndex = wb.next();

            if (lastIndex != BreakIterator.DONE
                    && Character.isLetterOrDigit(text.charAt(firstIndex)))
            {
                String word = text.substring(firstIndex, lastIndex).toLowerCase();
                if (!dictionary.contains(word))
                {
                    spansBuilder.add(Collections.emptyList(), firstIndex - lastKwEnd);
                    spansBuilder.add(Collections.singleton("underlined"), lastIndex - firstIndex);
                    lastKwEnd = lastIndex;
                }
                System.err.println();
            }
        }
        spansBuilder.add(Collections.emptyList(), text.length() - lastKwEnd);

        return spansBuilder.create();
    }
}

以下文件进入资源文件夹:

拼写检查。css

.underlined {
    -rtfx-background-color: #f0f0f0;
    -rtfx-underline-color: red;
    -rtfx-underline-dash-array: 2 2;
    -rtfx-underline-width: 1;
    -rtfx-underline-cap: butt;
}

拼写检查。字典

a
applied
basic
brown
but
could
document
dog
fox
here
if
is
its
jumps
lazy
no
over
quick
rendering
sample
see
styling
the
there
this
were
you

拼写检查。txt文件

The quik brown fox jumps over the lazy dog.
Ths is a sample dokument.
There is no styling aplied, but if there were, you could see its basic rndering here.

共有1个答案

陆绪
2023-03-14

我知道怎么做了。通过使用插入符号位置,我可以选择一个单词并替换它。问题是,右键单击并没有移动插入符号。因此,为了移动插入符号,需要添加一个侦听器。

textArea.setOnMouseClicked((MouseEvent mouseEvent) ->
{
    if (mouseEvent.getButton().equals(MouseButton.SECONDARY))
    {
        if (mouseEvent.getClickCount() == 1)
        {
            CharacterHit hit = textArea.hit(mouseEvent.getX(), mouseEvent.getY());
            int characterPosition = hit.getInsertionIndex();

            // move the caret to that character's position
            textArea.moveTo(characterPosition, SelectionPolicy.CLEAR);
        }
    }
});

编辑1:

为了提高性能,添加了索引和并发。上下文菜单现在是即时的。

编辑2:

修复了上下文菜单的macOS问题

完整代码:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.text.BreakIterator;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.animation.PauseTransition;

import org.fxmisc.flowless.VirtualizedScrollPane;
import org.fxmisc.richtext.StyleClassedTextArea;
import org.fxmisc.richtext.model.StyleSpans;
import org.fxmisc.richtext.model.StyleSpansBuilder;

import javafx.application.Application;
import javafx.concurrent.Task;
import javafx.event.ActionEvent;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.MenuItem;
import javafx.scene.control.SeparatorMenuItem;
import javafx.scene.input.Clipboard;
import javafx.scene.input.ClipboardContent;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.StackPane;
import javafx.scene.text.Text;
import javafx.stage.Stage;
import javafx.util.Duration;
import org.apache.commons.lang3.CharUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.apache.commons.text.WordUtils;
import org.apache.commons.text.similarity.JaroWinklerDistance;
import org.fxmisc.richtext.CharacterHit;
import org.fxmisc.richtext.NavigationActions.SelectionPolicy;

public class SpellCheckingDemo extends Application
{

    private static final int NUMBER_OF_SUGGESTIONS = 5;
    private static final Set<String> DICTIONARY = ConcurrentHashMap.newKeySet();
    private static final Set<String> INCORRECT_WORDS = ConcurrentHashMap.newKeySet();
    private static final Map<String, List<String>> SUGGESTIONS = new ConcurrentHashMap<>();
    private static final JaroWinklerDistance JARO_WINKLER_ALGORITHM = new JaroWinklerDistance();

    public static void main(String[] args)
    {
        launch(args);

    }

    @Override
    public void start(Stage primaryStage)
    {
        StyleClassedTextArea textArea = new StyleClassedTextArea();
        textArea.setWrapText(true);
        textArea.requestFollowCaret();
        //wait a bit before typing has stopped to compute the highlighting
        PauseTransition textAreaDelay = new PauseTransition(Duration.millis(250));

        textArea.textProperty().addListener((observable, oldValue, newValue) ->
        {
            textAreaDelay.setOnFinished(event ->
            {
                textArea.setStyleSpans(0, computeHighlighting(textArea.getText()));

                //have a new thread index all incorrect words, and pre-populate suggestions
                Task task = new Task<Void>()
                {

                    @Override
                    public Void call()
                    {
                        //iterating over entire list is ok because after the first time, it will hit the index anyway
                        for (String word : SpellCheckingDemo.INCORRECT_WORDS)
                        {
                            SpellCheckingDemo.getClosestWords(word);
                            SpellCheckingDemo.getClosestWords(StringUtils.trim(word));

                        }

                        return null;
                    }
                };
                new Thread(task).start();
            });
            textAreaDelay.playFromStart();
        });

        textArea.setOnMouseClicked((MouseEvent mouseEvent) ->
        {
            if (mouseEvent.getButton().equals(MouseButton.SECONDARY))
            {
                if (mouseEvent.getClickCount() == 1)
                {
                    CharacterHit hit = textArea.hit(mouseEvent.getX(), mouseEvent.getY());
                    int characterPosition = hit.getInsertionIndex();

                    // move the caret to that character's position
                    if (StringUtils.isEmpty(textArea.getSelectedText()))
                    {
                        textArea.moveTo(characterPosition, SelectionPolicy.CLEAR);

                    }

                    if (mouseEvent.getTarget() instanceof Text && StringUtils.isEmpty(textArea.getSelectedText()))
                    {

                        textArea.selectWord();

                        //When selecting right next to puncuation and spaces, the replacements elimantes these values. This avoids the issue by moving the caret towards the middle
                        if (!StringUtils.isEmpty(textArea.getSelectedText()) && !CharUtils.isAsciiAlphanumeric(textArea.getSelectedText().charAt(textArea.getSelectedText().length() - 1)))
                        {
                            textArea.moveTo(textArea.getCaretPosition() - 2);
                            textArea.selectWord();

                        }

                        String referenceWord = textArea.getSelectedText();

                        textArea.deselect();

                        if (!NumberUtils.isParsable(referenceWord) && !DICTIONARY.contains(StringUtils.trim(StringUtils.lowerCase(referenceWord))))
                        {
                            ContextMenu context = new ContextMenu();

                            for (String word : SpellCheckingDemo.getClosestWords(referenceWord))
                            {

                                MenuItem item = new MenuItem(word);
                                item.setOnAction((ActionEvent a) ->
                                {

                                    textArea.selectWord();
                                    textArea.replaceSelection(word);
                                    textArea.deselect();

                                });
                                context.getItems().add(item);

                            }

                            if (!context.getItems().isEmpty())
                            {
                                textArea.moveTo(textArea.getCaretPosition() - 1);

                                context.show((Node) mouseEvent.getTarget(), mouseEvent.getScreenX(), mouseEvent.getScreenY());
                                ((Node) mouseEvent.getTarget()).addEventHandler(MouseEvent.MOUSE_PRESSED, event -> context.hide());

                            } else
                            {
                                ContextMenu copyPasteMenu = getCopyPasteMenu(textArea);
                                copyPasteMenu.show((Node) mouseEvent.getTarget(), mouseEvent.getScreenX(), mouseEvent.getScreenY());
                                ((Node) mouseEvent.getTarget()).addEventHandler(MouseEvent.MOUSE_PRESSED, event -> copyPasteMenu.hide());

                            }
                        } else
                        {
                            ContextMenu copyPasteMenu = getCopyPasteMenu(textArea);
                            copyPasteMenu.show((Node) mouseEvent.getTarget(), mouseEvent.getScreenX(), mouseEvent.getScreenY());
                            ((Node) mouseEvent.getTarget()).addEventHandler(MouseEvent.MOUSE_PRESSED, event -> copyPasteMenu.hide());

                        }

                    } else
                    {
                        ContextMenu copyPasteMenu = getCopyPasteMenu(textArea);
                        copyPasteMenu.show((Node) mouseEvent.getTarget(), mouseEvent.getScreenX(), mouseEvent.getScreenY());
                        ((Node) mouseEvent.getTarget()).addEventHandler(MouseEvent.MOUSE_PRESSED, event -> copyPasteMenu.hide());

                    }
                }
            }
        });

        // load the dictionary
        try (InputStream input = SpellCheckingDemo.class.getResourceAsStream("/spellchecking.dict");
                BufferedReader br = new BufferedReader(new InputStreamReader(input)))
        {
            String line;
            while ((line = br.readLine()) != null)
            {
                DICTIONARY.add(line);
            }
        } catch (IOException ex)
        {
            Logger.getLogger(SpellCheckingDemo.class.getName()).log(Level.SEVERE, null, ex);
        }

        // load the sample document
        InputStream input2 = SpellCheckingDemo.class.getResourceAsStream("/spellchecking.txt");
        try (java.util.Scanner s = new java.util.Scanner(input2))
        {
            String document = s.useDelimiter("\\A").hasNext() ? s.next() : "";
            textArea.replaceText(0, 0, document);
        }

        Scene scene = new Scene(new StackPane(new VirtualizedScrollPane<>(textArea)), 600, 400);
        scene.getStylesheets().add(SpellCheckingDemo.class.getResource("/spellchecking.css").toExternalForm());
        primaryStage.setScene(scene);
        primaryStage.setTitle("Spell Checking Demo");
        primaryStage.show();
    }

    private static StyleSpans<Collection<String>> computeHighlighting(String text)
    {

        StyleSpansBuilder<Collection<String>> spansBuilder = new StyleSpansBuilder<>();

        BreakIterator wb = BreakIterator.getWordInstance();
        wb.setText(text);

        int lastIndex = wb.first();
        int lastKwEnd = 0;
        while (lastIndex != BreakIterator.DONE)
        {
            int firstIndex = lastIndex;
            lastIndex = wb.next();

            if (lastIndex != BreakIterator.DONE && Character.isLetterOrDigit(text.charAt(firstIndex)))
            {
                String word = text.substring(firstIndex, lastIndex).toLowerCase();

                if (!NumberUtils.isParsable(word) && !DICTIONARY.contains(word))
                {
                    spansBuilder.add(Collections.emptyList(), firstIndex - lastKwEnd);
                    spansBuilder.add(Collections.singleton("underlined"), lastIndex - firstIndex);
                    lastKwEnd = lastIndex;
                    SpellCheckingDemo.INCORRECT_WORDS.add(word);
                }
                //System.err.println();
            }
        }
        spansBuilder.add(Collections.emptyList(), text.length() - lastKwEnd);

        return spansBuilder.create();
    }

    public static List<String> getClosestWords(String word)
    {

        //check to see if an suggestions for this word have already been indexed
        if (SpellCheckingDemo.SUGGESTIONS.containsKey(word) && SpellCheckingDemo.SUGGESTIONS.get(word) != null && !SpellCheckingDemo.SUGGESTIONS.get(word).isEmpty())
        {
            return SpellCheckingDemo.SUGGESTIONS.get(word);
        }

        List<StringDistancePair> allWordDistances = new ArrayList<>(DICTIONARY.size());

        String lowerCaseWord = StringUtils.lowerCase(word);

        for (String checkWord : DICTIONARY)
        {
            allWordDistances.add(new StringDistancePair(JARO_WINKLER_ALGORITHM.apply(lowerCaseWord, checkWord), checkWord));

        }

        allWordDistances.sort(Comparator.comparingDouble(StringDistancePair::getDistance));

        List<String> closestWords = new ArrayList<>(NUMBER_OF_SUGGESTIONS);

        System.out.println(word);
        for (StringDistancePair pair : allWordDistances.subList(allWordDistances.size() - NUMBER_OF_SUGGESTIONS, allWordDistances.size()))
        {
            // 0 is not a match at all, so no point adding to list
            if (pair.getDistance() == 0.0)
            {
                continue;
            }
            String addWord;
            if (StringUtils.isAllUpperCase(word))
            {
                addWord = StringUtils.upperCase(pair.getWord());
            } else if (CharUtils.isAsciiAlphaUpper(word.charAt(0)))
            {
                addWord = WordUtils.capitalize(pair.getWord());
            } else
            {
                addWord = StringUtils.lowerCase(pair.getWord());
            }
            System.out.println(pair);
            closestWords.add(addWord);
        }
        System.out.println();
        Collections.reverse(closestWords);

        //add the suggestion list to index to allow future pulls
        SpellCheckingDemo.SUGGESTIONS.put(word, closestWords);

        return closestWords;

    }

    public static ContextMenu getCopyPasteMenu(StyleClassedTextArea textArea)
    {
        ContextMenu context = new ContextMenu();
        MenuItem cutItem = new MenuItem("Cut");
        cutItem.setOnAction((ActionEvent a) ->
        {

            Clipboard clipboard = Clipboard.getSystemClipboard();
            ClipboardContent content = new ClipboardContent();
            content.putString(textArea.getSelectedText());
            clipboard.setContent(content);
            textArea.replaceSelection("");

        });

        context.getItems().add(cutItem);

        MenuItem copyItem = new MenuItem("Copy");
        copyItem.setOnAction((ActionEvent a) ->
        {

            Clipboard clipboard = Clipboard.getSystemClipboard();
            ClipboardContent content = new ClipboardContent();
            content.putString(textArea.getSelectedText());
            clipboard.setContent(content);

        });

        context.getItems().add(copyItem);

        MenuItem pasteItem = new MenuItem("Paste");
        pasteItem.setOnAction((ActionEvent a) ->
        {

            Clipboard clipboard = Clipboard.getSystemClipboard();
            if (!StringUtils.isEmpty(textArea.getSelectedText()))
            {
                textArea.replaceSelection(clipboard.getString());
            } else
            {
                textArea.insertText(textArea.getCaretPosition(), clipboard.getString());
            }
        });
        context.getItems().add(pasteItem);
        context.getItems().add(new SeparatorMenuItem());

        MenuItem selectAllItem = new MenuItem("Select All");
        selectAllItem.setOnAction((ActionEvent a) ->
        {

            textArea.selectAll();
        });
        context.getItems().add(selectAllItem);

        if (StringUtils.isEmpty(textArea.getSelectedText()))
        {
            cutItem.setDisable(true);
            copyItem.setDisable(true);
        }

        return context;
    }

    private static class StringDistancePair
    {

        private final double x;
        private final String y;

        public StringDistancePair(double x, String y)
        {
            this.x = x;
            this.y = y;
        }

        public String getWord()
        {
            return y;
        }

        public double getDistance()
        {
            return x;
        }

        @Override
        public String toString()
        {
            return StringUtils.join(String.valueOf(getDistance()), " : ", String.valueOf(getWord()));
        }
    }
}

在此处下载完整的英语词典:https://github.com/dwyl/english-words/blob/master/words_alpha.txt

 类似资料:
  • 问题内容: 我正在尝试使用selenium进行右键单击,对此有任何想法吗? 问题答案: 我已经尝试过ActionSequence,而且效果很好。 找不到ContextClick函数,应使用click。 因此,应如下所示: 元素是您的Web元素,2表示右键。 要大致模拟JavaScript中的右键单击,请查看JavaScript模拟代码中的右键单击。

  • 问题内容: 我当前正在通过右键单击实例化并将其位置设置为鼠标位置的位置来创建右键单击上下文菜单。是否有更好的方法? 问题答案: 您可能正在手动调用菜单。这会导致菜单中出现一些令人讨厌的越野车行为。 该方法处理所有需要发生的事情(在鼠标悬停时突出显示事情,并在必要时关闭弹出窗口),其中使用只是显示菜单而无需添加任何其他行为。 要进行右键单击弹出菜单,只需创建一个。 然后,您所要做的就是向要弹出菜单的

  • 自 Electron 8 以来已内置支持 Chromium 拼写检查器。 On Windows and Linux this is powered by Hunspell dictionaries, and on macOS it makes use of the native spellchecker APIs. How to enable the spellchecker? 对于 Electr

  • 问题内容: 对于Windows和Linux,我能够检测到右键单击。但是对于Mac,我不知道如何检测右键单击。 如何编写Java程序以检测Mac OS的右键单击 感谢Sunil KUmar Sahoo 问题答案: 这与检测Windows或Linux上的右键单击相同–您调用给定的MouseEvent的方法来检查是否被单击。例如,看一下示例MouseListener的以下代码片段: 但是,这仅在用户实际

  • 我对Python和NLTK相当陌生。我正忙于一个可以执行拼写检查的应用程序(用正确的单词替换拼写错误的单词)。我目前正在使用Python 2.7上的附魔库、PyEnchant和NLTK库。下面的代码是一个处理更正/替换的类。 我编写了一个函数,它接收单词列表,对每个单词执行replace(),然后返回这些单词的列表,但拼写正确。 现在,我真的不喜欢这个,因为它不是很准确,我正在寻找一种方法来实现拼

  • 本文向大家介绍如何使用JavaFX创建标签?,包括了如何使用JavaFX创建标签?的使用技巧和注意事项,需要的朋友参考一下 现场演示 -> 输出结果