/* LanguageTool, a natural language style checker
* Copyright (C) 2005 Daniel Naber (http://www.danielnaber.de)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
* USA
*/
package org.languagetool.gui;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.Nullable;
import org.languagetool.JLanguageTool;
import org.languagetool.Language;
import org.languagetool.Languages;
import org.languagetool.MultiThreadedJLanguageTool;
import org.languagetool.language.LanguageIdentifier;
import org.languagetool.rules.ITSIssueType;
import org.languagetool.rules.Rule;
import org.languagetool.rules.RuleMatch;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.text.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.*;
import java.util.List;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import org.languagetool.rules.Category;
import org.languagetool.rules.CategoryId;
/**
* Support for associating a LanguageTool instance and a JTextComponent
*
* @author Panagiotis Minos
* @since 2.3
*/
class LanguageToolSupport {
static final String CONFIG_FILE = ".languagetool.cfg";
//maximum entries in the activate rule menu.
//If entries' number is bigger, create per category submenus
//can set to 0 to always create category submenus
private static final int MAX_RULES_NO_CATEGORY_MENU = 12;
//maximum rule menu entries, if more create a More submenu
private static final int MAX_RULES_PER_MENU = 12;
//maximum category menu entries, if more create a More submenu
private static final int MAX_CATEGORIES_PER_MENU = 12;
private final UndoRedoSupport undo;
private final LanguageIdentifier langIdentifier;
private final JFrame frame;
private final JTextComponent textComponent;
private final EventListenerList listenerList = new EventListenerList();
private final ResourceBundle messages;
private final List<RuleMatch> ruleMatches;
private final List<Span> documentSpans;
private MultiThreadedJLanguageTool languageTool;
private ScheduledExecutorService checkExecutor;
private MouseListener mouseListener;
private ActionListener actionListener;
private int millisecondDelay = 1500;
private AtomicInteger check;
private boolean popupMenuEnabled = true;
private boolean backgroundCheckEnabled = true;
private Configuration config;
private boolean mustDetectLanguage = false;
/**
* LanguageTool support for a JTextComponent
*/
LanguageToolSupport(JFrame frame, JTextComponent textComponent) {
this(frame, textComponent, null);
}
/**
* LanguageTool support for a JTextComponent
* @since 2.7
*/
LanguageToolSupport(JFrame frame, JTextComponent textComponent, UndoRedoSupport support) {
this.frame = frame;
this.textComponent = textComponent;
this.messages = JLanguageTool.getMessageBundle();
ruleMatches = new ArrayList<>();
documentSpans = new ArrayList<>();
this.undo = support;
this.langIdentifier = new LanguageIdentifier();
init();
}
void addLanguageToolListener(LanguageToolListener ltListener) {
listenerList.add(LanguageToolListener.class, ltListener);
}
void removeLanguageToolListener(LanguageToolListener ltListener) {
listenerList.remove(LanguageToolListener.class, ltListener);
}
private void fireEvent(LanguageToolEvent.Type type, Object caller, long elapsedTime) {
LanguageToolEvent event = new LanguageToolEvent(this, type, caller, elapsedTime);
fireEvent(event);
}
private void fireEvent(LanguageToolEvent.Type type, Object caller) {
LanguageToolEvent event = new LanguageToolEvent(this, type, caller);
fireEvent(event);
}
private void fireEvent(LanguageToolEvent event) {
// Guaranteed to return a non-null array
Object[] listeners = listenerList.getListenerList();
// Process the listeners last to first, notifying
// those that are interested in this event
for (int i = listeners.length - 2; i >= 0; i -= 2) {
if (listeners[i] == LanguageToolListener.class) {
// Lazily create the event:
((LanguageToolListener) listeners[i + 1]).languageToolEventOccurred(event);
}
}
}
JTextComponent getTextComponent() {
return textComponent;
}
List<RuleMatch> getMatches() {
return this.ruleMatches;
}
void reloadConfig() {
//FIXME
//if mother tongue changes then create new JLanguageTool instance
boolean update = false;
Set<String> disabledRules = config.getDisabledRuleIds();
if (disabledRules == null) {
disabledRules = Collections.emptySet();
}
Set<String> common = new HashSet<>(disabledRules);
common.retainAll(languageTool.getDisabledRules());
Set<String> toDisable = new HashSet<>(disabledRules);
toDisable.removeAll(common);
Set<String> toEnable = new HashSet<>(languageTool.getDisabledRules());
toEnable.removeAll(common);
for (String ruleId : toDisable) {
languageTool.disableRule(ruleId);
update = true;
}
for (String ruleId : toEnable) {
languageTool.enableRule(ruleId);
update = true;
}
Set<String> disabledCategoryNames = config.getDisabledCategoryNames();
if (disabledCategoryNames == null) {
disabledCategoryNames = Collections.emptySet();
}
Set<CategoryId> disabledCategories = new HashSet<>();
Map<CategoryId, Category> langCategories = languageTool.getCategories();
for (CategoryId id : langCategories.keySet()) {
String categoryName = langCategories.get(id).getName();
if (disabledCategoryNames.contains(categoryName)) {
disabledCategories.add(id);
}
}
Set<CategoryId> ltDisabledCategories = new HashSet<>();
for (CategoryId id : langCategories.keySet()) {
if (languageTool.isCategoryDisabled(id)) {
ltDisabledCategories.add(id);
}
}
Set<CategoryId> commonCat = new HashSet<>(disabledCategories);
commonCat.retainAll(ltDisabledCategories);
Set<CategoryId> toDisableCat = new HashSet<>(disabledCategories);
toDisableCat.removeAll(commonCat);
Set<CategoryId> toEnableCat = new HashSet<>(ltDisabledCategories);
toEnableCat.removeAll(commonCat);
for(CategoryId id : toDisableCat) {
languageTool.disableCategory(id);
}
for(CategoryId id : toEnableCat) {
languageTool.enableRuleCategory(id);
}
if (!toDisableCat.isEmpty() || !toEnableCat.isEmpty()) {
// ugly hack to trigger reInitSpellCheckIgnoreWords()
update = true;
}
Set<String> enabledRules = config.getEnabledRuleIds();
if (enabledRules == null) {
enabledRules = Collections.emptySet();
}
for (String ruleName : enabledRules) {
languageTool.enableRule(ruleName);
update = true;
}
if (update) {
//FIXME
//we could skip a full check if the user disabled but didn't enable rules
checkImmediately(null);
fireEvent(LanguageToolEvent.Type.RULE_ENABLED, null);
}
}
private void reloadLanguageTool(Language language) {
try {
//FIXME
//no need to read again the file
config = new Configuration(new File(System.getProperty("user.home")), CONFIG_FILE, language);
//config still contains old language, update it
this.config.setLanguage(language);
// Calling shutdown here may cause a RejectedExecutionException:
//if (languageTool != null) {
// languageTool.shutdownWhenDone();
//}
languageTool = new MultiThreadedJLanguageTool(language, config.getMotherTongue());
languageTool.setCleanOverlappingMatches(false);
Tools.configureFromRules(languageTool, config);
if (config.getNgramDirectory() != null) {
File ngramLangDir = new File(config.getNgramDirectory(), language.getShortCode());
if (ngramLangDir.exists()) {
try {
languageTool.activateLanguageModelRules(config.getNgramDirectory());
} catch (Exception e) {
JOptionPane.showMessageDialog(null, "Error while loading ngram database.\n" + e.getMessage());
}
} else {
// user might have set ngram directory to use it for e.g. English, but they
// might not have the data for other languages that supports ngram, so don't
// annoy them with an error dialog:
System.err.println("Not loading ngram data, directory does not exist: " + ngramLangDir);
}
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private void init() {
try {
config = new Configuration(new File(System.getProperty("user.home")), CONFIG_FILE, null);
} catch (IOException ex) {
throw new RuntimeException("Could not load configuration", ex);
}
Language defaultLanguage = config.getLanguage();
if (defaultLanguage == null) {
defaultLanguage = Languages.getLanguageForLocale(Locale.getDefault());
}
/**
* Warm-up: we have a lot of lazy init in LT, which causes the first check to
* be very slow (several seconds) for languages with a lot of data and a lot of
* rules. We just assume that the default language is the language that the user
* often uses and init the LT object for that now, not just when it's first used.
* This makes the first check feel much faster:
*/
reloadLanguageTool(defaultLanguage);
checkExecutor = new ScheduledThreadPoolExecutor(1, new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setDaemon(true);
t.setPriority(Thread.MIN_PRIORITY);
t.setName(t.getName() + "-lt-background");
return t;
}
});
check = new AtomicInteger(0);
this.textComponent.getDocument().addDocumentListener(new DocumentListener() {
@Override
public void insertUpdate(DocumentEvent e) {
mustDetectLanguage = config.getAutoDetect();
recalculateSpans(e.getOffset(), e.getLength(), false);
if (backgroundCheckEnabled) {
checkDelayed(null);
}
}
@Override
public void removeUpdate(DocumentEvent e) {
mustDetectLanguage = config.getAutoDetect();
recalculateSpans(e.getOffset(), e.getLength(), true);
if (backgroundCheckEnabled) {
checkDelayed(null);
}
}
@Override
public void changedUpdate(DocumentEvent e) {
mustDetectLanguage = config.getAutoDetect();
if (backgroundCheckEnabled) {
checkDelayed(null);
}
}
});
mouseListener = new MouseListener() {
@Override
public void mouseClicked(MouseEvent me) {
}
@Override
public void mousePressed(MouseEvent me) {
if (me.isPopupTrigger()) {
showPopup(me);
}
}
@Override
public void mouseReleased(MouseEvent me) {
if (me.isPopupTrigger()) {
showPopup(me);
}
}
@Override
public void mouseEntered(MouseEvent me) {}
@Override
public void mouseExited(MouseEvent me) {}
};
this.textComponent.addMouseListener(mouseListener);
actionListener = e -> _actionPerformed(e);
mustDetectLanguage = config.getAutoDetect();
if (!this.textComponent.getText().isEmpty() && backgroundCheckEnabled) {
checkImmediately(null);
}
}
public int getMillisecondDelay() {
return millisecondDelay;
}
/**
* The text checking delay in milliseconds.
*/
public void setMillisecondDelay(int millisecondDelay) {
this.millisecondDelay = millisecondDelay;
}
public boolean isPopupMenuEnabled() {
return popupMenuEnabled;
}
public void setPopupMenuEnabled(boolean popupMenuEnabled) {
if (this.popupMenuEnabled == popupMenuEnabled) {
return;
}
this.popupMenuEnabled = popupMenuEnabled;
if (popupMenuEnabled) {
textComponent.addMouseListener(mouseListener);
} else {
textComponent.removeMouseListener(mouseListener);
}
}
public boolean isBackgroundCheckEnabled() {
return backgroundCheckEnabled;
}
public void setBackgroundCheckEnabled(boolean backgroundCheckEnabled) {
if (this.backgroundCheckEnabled == backgroundCheckEnabled) {
return;
}
this.backgroundCheckEnabled = backgroundCheckEnabled;
if (backgroundCheckEnabled) {
checkImmediately(null);
}
}
public void setLanguage(Language language) {
reloadLanguageTool(language);
if (backgroundCheckEnabled) {
checkImmediately(null);
}
}
Language getLanguage() {
return this.languageTool.getLanguage();
}
public Configuration getConfig() {
return config;
}
// called from Main.showOptions() and Main.tagTextAndDisplayResults()
JLanguageTool getLanguageTool() {
return languageTool;
}
void disableRule(String ruleId) {
Rule rule = this.getRuleForId(ruleId);
if (rule == null) {
//System.err.println("No rule with id: <"+ruleId+">");
return;
}
if (rule.isDefaultOff()) {
config.getEnabledRuleIds().remove(ruleId);
} else {
config.getDisabledRuleIds().add(ruleId);
}
languageTool.disableRule(ruleId);
updateHighlights(ruleId);
fireEvent(LanguageToolEvent.Type.RULE_DISABLED, null);
}
void enableRule(String ruleId) {
Rule rule = this.getRuleForId(ruleId);
if (rule == null) {
//System.err.println("No rule with id: <"+ruleId+">");
return;
}
if (rule.isDefaultOff()) {
config.getEnabledRuleIds().add(ruleId);
} else {
config.getDisabledRuleIds().remove(ruleId);
}
languageTool.enableRule(ruleId);
fireEvent(LanguageToolEvent.Type.RULE_ENABLED, null);
checkImmediately(null);
}
@Nullable
private Span getSpan(int offset) {
for (Span cur : documentSpans) {
if (cur.end > cur.start && cur.start <= offset && offset < cur.end) {
return cur;
}
}
return null;
}
private void showPopup(MouseEvent event) {
if (documentSpans.isEmpty() && languageTool.getDisabledRules().isEmpty()) {
//No errors and no disabled Rules
return;
}
int offset = this.textComponent.viewToModel(event.getPoint());
Span span = getSpan(offset);
JPopupMenu popup = new JPopupMenu("Grammar Menu");
if (span != null) {
JLabel msgItem = new JLabel("<html>"
+ span.msg.replace("<suggestion>", "<b>").replace("</suggestion>", "</b>")
+ "</html>");
msgItem.setToolTipText(
span.desc.replace("<suggestion>", "").replace("</suggestion>", ""));
msgItem.setBorder(new JMenuItem().getBorder());
popup.add(msgItem);
popup.add(new JSeparator());
for (String r : span.replacement) {
ReplaceMenuItem item = new ReplaceMenuItem(r, span);
popup.add(item);
item.addActionListener(actionListener);
}
popup.add(new JSeparator());
JMenuItem moreItem = new JMenuItem(messages.getString("guiMore"));
moreItem.addActionListener(e -> showDialog(textComponent, span.msg, span.desc, span.rule));
popup.add(moreItem);
JMenuItem ignoreItem = new JMenuItem(messages.getString("guiTurnOffRule"));
ignoreItem.addActionListener(e -> disableRule(span.rule.getId()));
popup.add(ignoreItem);
popup.applyComponentOrientation(
ComponentOrientation.getOrientation(Locale.getDefault()));
}
List<Rule> disabledRules = getDisabledRules();
if (!disabledRules.isEmpty()) {
JMenu activateRuleMenu = new JMenu(messages.getString("guiActivateRule"));
addDisabledRulesToMenu(disabledRules, activateRuleMenu);
popup.add(activateRuleMenu);
}
if (span != null) {
textComponent.setCaretPosition(span.start);
textComponent.moveCaretPosition(span.end);
}
popup.addPopupMenuListener(new PopupMenuListener() {
@Override
public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
}
@Override
public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
}
@Override
public void popupMenuCanceled(PopupMenuEvent e) {
if (span != null) {
textComponent.setCaretPosition(span.start);
}
}
});
popup.show(textComponent, event.getPoint().x, event.getPoint().y);
}
private List<Rule> getDisabledRules() {
List<Rule> disabledRules = new ArrayList<>();
for (String ruleId : languageTool.getDisabledRules()) {
Rule rule = getRuleForId(ruleId);
if (rule == null || rule.isDefaultOff()) {
continue;
}
disabledRules.add(rule);
}
Collections.sort(disabledRules, (r1, r2) -> r1.getDescription().compareTo(r2.getDescription()));
return disabledRules;
}
private void addDisabledRulesToMenu(List<Rule> disabledRules, JMenu menu) {
if (disabledRules.size() <= MAX_RULES_NO_CATEGORY_MENU) {
createRulesMenu(menu, disabledRules);
return;
}
TreeMap<String, ArrayList<Rule>> categories = new TreeMap<>();
for (Rule rule : disabledRules) {
if (!categories.containsKey(rule.getCategory().getName())) {
categories.put(rule.getCategory().getName(), new ArrayList<>());
}
categories.get(rule.getCategory().getName()).add(rule);
}
JMenu parent = menu;
int count = 0;
for (String category : categories.keySet()) {
count++;
JMenu submenu = new JMenu(category);
parent.add(submenu);
createRulesMenu(submenu, categories.get(category));
if (categories.keySet().size() <= MAX_CATEGORIES_PER_MENU) {
continue;
}
//if menu contains MAX_CATEGORIES_PER_MENU-1, add a `more` menu
//but only if the remain entries are more than one
if ((count % (MAX_CATEGORIES_PER_MENU - 1) == 0)
&& (categories.keySet().size() - count > 1)) {
JMenu more = new JMenu(messages.getString("guiActivateRuleMoreCategories"));
parent.add(more);
parent = more;
}
}
}
private void createRulesMenu(JMenu parent, List<Rule> rules) {
JMenu menu = parent;
int count = 0;
for (Rule rule : rules) {
count++;
String id = rule.getId();
JMenuItem ruleItem = new JMenuItem(rule.getDescription());
ruleItem.addActionListener(e -> enableRule(id));
menu.add(ruleItem);
if (rules.size() <= MAX_RULES_PER_MENU) {
continue;
}
//if menu contains MAX_RULES_PER_MENU-1, add a `more` menu
//but only if the remain entries are more than one
if ((count % (MAX_RULES_PER_MENU - 1) == 0)
&& (rules.size() - count > 1)) {
JMenu more = new JMenu(messages.getString("guiActivateRuleMoreRules"));
menu.add(more);
menu = more;
}
}
}
@Nullable
Rule getRuleForId(String ruleId) {
List<Rule> allRules = languageTool.getAllRules();
for (Rule rule : allRules) {
if (rule.getId().equals(ruleId)) {
return rule;
}
}
return null;
}
private void _actionPerformed(ActionEvent e) {
ReplaceMenuItem src = (ReplaceMenuItem) e.getSource();
this.documentSpans.remove(src.span);
applySuggestion(e.getActionCommand(), src.span.start, src.span.end);
}
private void applySuggestion(String str, int start, int end) {
if (end < start) {
throw new IllegalArgumentException("end before start: " + end + " < " + start);
}
Document doc = this.textComponent.getDocument();
if (doc != null) {
try {
if (this.undo != null) {
this.undo.startCompoundEdit();
}
if (doc instanceof AbstractDocument) {
((AbstractDocument) doc).replace(start, end - start, str, null);
} else {
doc.remove(start, end - start);
doc.insertString(start, str, null);
}
} catch (BadLocationException e) {
throw new IllegalArgumentException(e);
} finally {
if (this.undo != null) {
this.undo.endCompoundEdit();
}
}
}
}
public void checkDelayed() {
checkDelayed(null);
}
public void checkDelayed(Object caller) {
check.getAndIncrement();
checkExecutor.schedule(new RunnableImpl(caller), millisecondDelay, TimeUnit.MILLISECONDS);
}
public void checkImmediately() {
checkImmediately(null);
}
public void checkImmediately(Object caller) {
check.getAndIncrement();
checkExecutor.schedule(new RunnableImpl(caller), 0, TimeUnit.MILLISECONDS);
}
Language autoDetectLanguage(String text) {
Language lang = langIdentifier.detectLanguage(text);
if (lang == null) {
lang = Languages.getLanguageForLocale(Locale.getDefault());
}
if (lang.hasVariant()) {
// UI only shows variants like "English (American)", not just "English", so use that:
lang = lang.getDefaultLanguageVariant();
}
return lang;
}
private synchronized List<RuleMatch> checkText(Object caller) throws IOException {
if (this.mustDetectLanguage) {
mustDetectLanguage = false;
if (!this.textComponent.getText().isEmpty()) {
Language detectedLanguage = autoDetectLanguage(this.textComponent.getText());
if (!detectedLanguage.equals(this.languageTool.getLanguage())) {
reloadLanguageTool(detectedLanguage);
if (SwingUtilities.isEventDispatchThread()) {
fireEvent(LanguageToolEvent.Type.LANGUAGE_CHANGED, caller);
} else {
try {
SwingUtilities.invokeAndWait(() -> fireEvent(LanguageToolEvent.Type.LANGUAGE_CHANGED, caller));
} catch (InterruptedException ex) {
//ignore
} catch (InvocationTargetException ex) {
throw new RuntimeException(ex);
}
}
}
}
}
if (SwingUtilities.isEventDispatchThread()) {
fireEvent(LanguageToolEvent.Type.CHECKING_STARTED, caller);
} else {
try {
SwingUtilities.invokeAndWait(() -> fireEvent(LanguageToolEvent.Type.CHECKING_STARTED, caller));
} catch (InterruptedException ex) {
//ignore
} catch (InvocationTargetException ex) {
throw new RuntimeException(ex);
}
}
long startTime = System.currentTimeMillis();
List<RuleMatch> matches = this.languageTool.check(this.textComponent.getText());
long elapsedTime = System.currentTimeMillis() - startTime;
int v = check.get();
if (v == 0) {
if (!SwingUtilities.isEventDispatchThread()) {
SwingUtilities.invokeLater(() -> {
updateHighlights(matches);
fireEvent(LanguageToolEvent.Type.CHECKING_FINISHED, caller, elapsedTime);
});
} else {
updateHighlights(matches);
fireEvent(LanguageToolEvent.Type.CHECKING_FINISHED, caller, elapsedTime);
}
}
return matches;
}
private void removeHighlights() {
for (Highlighter.Highlight hl : textComponent.getHighlighter().getHighlights()) {
if (hl.getPainter() instanceof HighlightPainter) {
textComponent.getHighlighter().removeHighlight(hl);
}
}
}
private void recalculateSpans(int offset, int length, boolean remove) {
if (length == 0) {
return;
}
for (Span span : this.documentSpans) {
if (offset >= span.end) {
continue;
}
if (!remove) {
if (offset <= span.start) {
span.start += length;
}
span.end += length;
} else {
if (offset + length <= span.end) {
if (offset > span.start) {
//
} else if (offset + length <= span.start) {
span.start -= length;
} else {
span.start = offset;
}
span.end -= length;
} else {
span.end -= Math.min(length, span.end - offset);
}
}
}
updateHighlights();
}
private void updateHighlights(String disabledRule) {
List<Span> spans = new ArrayList<>();
List<RuleMatch> matches = new ArrayList<>();
for (RuleMatch match : ruleMatches) {
if (match.getRule().getId().equals(disabledRule)) {
continue;
}
matches.add(match);
spans.add(new Span(match));
}
prepareUpdateHighlights(matches, spans);
}
private void updateHighlights(List<RuleMatch> matches) {
List<Span> spans = new ArrayList<>();
for (RuleMatch match : matches) {
spans.add(new Span(match));
}
prepareUpdateHighlights(matches, spans);
}
private void prepareUpdateHighlights(List<RuleMatch> matches, List<Span> spans) {
ruleMatches.clear();
documentSpans.clear();
ruleMatches.addAll(matches);
documentSpans.addAll(spans);
updateHighlights();
}
private void updateHighlights() {
removeHighlights();
Highlighter h = textComponent.getHighlighter();
for (Span span : documentSpans) {
if (span.start == span.end) {
continue;
}
try {
if (span.start < span.end) { //to avoid the BadLocationException
ITSIssueType issueType = span.rule.getLocQualityIssueType();
Color colorForIssueType = getConfig().getErrorColors().get(issueType);
Color bgColor = colorForIssueType != null ? colorForIssueType : null;
Color underlineColor = ITSIssueType.Misspelling == span.rule.getLocQualityIssueType() ? Color.red : Color.blue;
HighlightPainter painter = new HighlightPainter(bgColor, underlineColor);
h.addHighlight(span.start, span.end, painter);
}
} catch (BadLocationException ex) {
ex.printStackTrace();
}
}
}
private void showDialog(Component parent, String title, String message, Rule rule) {
Tools.showRuleInfoDialog(parent, title, message, rule, messages, languageTool.getLanguage().getShortCodeWithCountryAndVariant());
}
private static class ReplaceMenuItem extends JMenuItem {
private final Span span;
private ReplaceMenuItem(String name, Span span) {
super(name);
this.span = span;
}
}
private static class Span {
private int start;
private int end;
private final String msg;
private final String desc;
private final List<String> replacement;
private final Rule rule;
private Span(RuleMatch match) {
start = match.getFromPos();
end = match.getToPos();
String tmp = match.getShortMessage();
if (StringUtils.isEmpty(tmp)) {
tmp = match.getMessage();
}
msg = Tools.shortenComment(tmp);
desc = match.getMessage();
replacement = new ArrayList<>();
replacement.addAll(match.getSuggestedReplacements());
rule = match.getRule();
}
}
private class RunnableImpl implements Runnable {
private final Object caller;
private RunnableImpl(Object caller) {
this.caller = caller;
}
@Override
public void run() {
int v = check.decrementAndGet();
if (v != 0) {
return;
}
try {
checkText(caller);
} catch (Exception ex) {
Tools.showError(ex);
}
}
}
}