File indexing completed on 2024-04-28 07:50:09

0001 /*
0002  * backgroundchecker.cpp
0003  *
0004  * SPDX-FileCopyrightText: 2004 Zack Rusin <zack@kde.org>
0005  * SPDX-FileCopyrightText: 2009 Jakub Stachowski <qbast@go2.pl>
0006  *
0007  * SPDX-License-Identifier: LGPL-2.1-or-later
0008  */
0009 #include "backgroundchecker.h"
0010 #include "backgroundchecker_p.h"
0011 
0012 #include "core_debug.h"
0013 
0014 using namespace Sonnet;
0015 
0016 void BackgroundCheckerPrivate::start()
0017 {
0018     sentenceOffset = -1;
0019     continueChecking();
0020 }
0021 
0022 void BackgroundCheckerPrivate::continueChecking()
0023 {
0024     metaObject()->invokeMethod(this, "checkNext", Qt::QueuedConnection);
0025 }
0026 
0027 void BackgroundCheckerPrivate::checkNext()
0028 {
0029     do {
0030         // go over current sentence
0031         while (sentenceOffset != -1 && words.hasNext()) {
0032             Token word = words.next();
0033             if (!words.isSpellcheckable()) {
0034                 continue;
0035             }
0036 
0037             // ok, this is valid word, do something
0038             if (currentDict.isMisspelled(word.toString())) {
0039                 lastMisspelled = word;
0040                 Q_EMIT misspelling(word.toString(), word.position() + sentenceOffset);
0041                 return;
0042             }
0043         }
0044         // current sentence done, grab next suitable
0045 
0046         sentenceOffset = -1;
0047         const bool autodetectLanguage = currentDict.testAttribute(Speller::AutoDetectLanguage);
0048         const bool ignoreUpperCase = !currentDict.testAttribute(Speller::CheckUppercase);
0049         while (mainTokenizer.hasNext()) {
0050             Token sentence = mainTokenizer.next();
0051             if (autodetectLanguage && !autoDetectLanguageDisabled) {
0052                 if (!mainTokenizer.isSpellcheckable()) {
0053                     continue;
0054                 }
0055                 // FIXME: find best from family en -> en_US, en_GB, ... ?
0056                 currentDict.setLanguage(mainTokenizer.language());
0057             }
0058             sentenceOffset = sentence.position();
0059             words.setBuffer(sentence.toString());
0060             words.setIgnoreUppercase(ignoreUpperCase);
0061             break;
0062         }
0063     } while (sentenceOffset != -1);
0064     Q_EMIT done();
0065 }
0066 
0067 BackgroundChecker::BackgroundChecker(QObject *parent)
0068     : QObject(parent)
0069     , d(new BackgroundCheckerPrivate)
0070 {
0071     connect(d.get(), &BackgroundCheckerPrivate::misspelling, this, &BackgroundChecker::misspelling);
0072     connect(d.get(), &BackgroundCheckerPrivate::done, this, &BackgroundChecker::slotEngineDone);
0073 }
0074 
0075 BackgroundChecker::BackgroundChecker(const Speller &speller, QObject *parent)
0076     : QObject(parent)
0077     , d(new BackgroundCheckerPrivate)
0078 {
0079     d->currentDict = speller;
0080     connect(d.get(), &BackgroundCheckerPrivate::misspelling, this, &BackgroundChecker::misspelling);
0081     connect(d.get(), &BackgroundCheckerPrivate::done, this, &BackgroundChecker::slotEngineDone);
0082 }
0083 
0084 BackgroundChecker::~BackgroundChecker() = default;
0085 
0086 void BackgroundChecker::setText(const QString &text)
0087 {
0088     d->mainTokenizer.setBuffer(text);
0089     d->start();
0090 }
0091 
0092 void BackgroundChecker::start()
0093 {
0094     // ## what if d->currentText.isEmpty()?
0095 
0096     // TODO: carry state from last buffer
0097     d->mainTokenizer.setBuffer(fetchMoreText());
0098     d->start();
0099 }
0100 
0101 void BackgroundChecker::stop()
0102 {
0103     //    d->stop();
0104 }
0105 
0106 QString BackgroundChecker::fetchMoreText()
0107 {
0108     return QString();
0109 }
0110 
0111 void BackgroundChecker::finishedCurrentFeed()
0112 {
0113 }
0114 
0115 bool BackgroundChecker::autoDetectLanguageDisabled() const
0116 {
0117     return d->autoDetectLanguageDisabled;
0118 }
0119 
0120 void BackgroundChecker::setAutoDetectLanguageDisabled(bool autoDetectDisabled)
0121 {
0122     d->autoDetectLanguageDisabled = autoDetectDisabled;
0123 }
0124 
0125 void BackgroundChecker::setSpeller(const Speller &speller)
0126 {
0127     d->currentDict = speller;
0128 }
0129 
0130 Speller BackgroundChecker::speller() const
0131 {
0132     return d->currentDict;
0133 }
0134 
0135 bool BackgroundChecker::checkWord(const QString &word)
0136 {
0137     return d->currentDict.isCorrect(word);
0138 }
0139 
0140 bool BackgroundChecker::addWordToPersonal(const QString &word)
0141 {
0142     return d->currentDict.addToPersonal(word);
0143 }
0144 
0145 bool BackgroundChecker::addWordToSession(const QString &word)
0146 {
0147     return d->currentDict.addToSession(word);
0148 }
0149 
0150 QStringList BackgroundChecker::suggest(const QString &word) const
0151 {
0152     return d->currentDict.suggest(word);
0153 }
0154 
0155 void BackgroundChecker::changeLanguage(const QString &lang)
0156 {
0157     // this sets language only for current sentence
0158     d->currentDict.setLanguage(lang);
0159 }
0160 
0161 void BackgroundChecker::continueChecking()
0162 {
0163     d->continueChecking();
0164 }
0165 
0166 void BackgroundChecker::slotEngineDone()
0167 {
0168     finishedCurrentFeed();
0169     const QString currentText = fetchMoreText();
0170 
0171     if (currentText.isNull()) {
0172         Q_EMIT done();
0173     } else {
0174         d->mainTokenizer.setBuffer(currentText);
0175         d->start();
0176     }
0177 }
0178 
0179 QString BackgroundChecker::text() const
0180 {
0181     return d->mainTokenizer.buffer();
0182 }
0183 
0184 QString BackgroundChecker::currentContext() const
0185 {
0186     int len = 60;
0187     // we don't want the expression underneath casted to an unsigned int
0188     // which would cause it to always evaluate to false
0189     int currentPosition = d->lastMisspelled.position() + d->sentenceOffset;
0190     bool begin = ((currentPosition - len / 2) <= 0) ? true : false;
0191 
0192     QString buffer = d->mainTokenizer.buffer();
0193     buffer.replace(currentPosition, d->lastMisspelled.length(), QStringLiteral("<b>%1</b>").arg(d->lastMisspelled.toString()));
0194 
0195     QString context;
0196     if (begin) {
0197         context = QStringLiteral("%1...").arg(buffer.mid(0, len));
0198     } else {
0199         context = QStringLiteral("...%1...").arg(buffer.mid(currentPosition - 20, len));
0200     }
0201 
0202     context.replace(QLatin1Char('\n'), QLatin1Char(' '));
0203 
0204     return context;
0205 }
0206 
0207 void Sonnet::BackgroundChecker::replace(int start, const QString &oldText, const QString &newText)
0208 {
0209     // FIXME: here we assume that replacement is in current fragment. So 'words' has
0210     // to be adjusted and sentenceOffset does not
0211     d->words.replace(start - (d->sentenceOffset), oldText.length(), newText);
0212     d->mainTokenizer.replace(start, oldText.length(), newText);
0213 }
0214 
0215 #include "moc_backgroundchecker.cpp"
0216 #include "moc_backgroundchecker_p.cpp"