File indexing completed on 2024-04-28 11:48:59

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, &BackgroundCheckerPrivate::misspelling, this, &BackgroundChecker::misspelling);
0072     connect(d, &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, &BackgroundCheckerPrivate::misspelling, this, &BackgroundChecker::misspelling);
0081     connect(d, &BackgroundCheckerPrivate::done, this, &BackgroundChecker::slotEngineDone);
0082 }
0083 
0084 BackgroundChecker::~BackgroundChecker()
0085 {
0086     delete d;
0087 }
0088 
0089 void BackgroundChecker::setText(const QString &text)
0090 {
0091     d->mainTokenizer.setBuffer(text);
0092     d->start();
0093 }
0094 
0095 void BackgroundChecker::start()
0096 {
0097     // ## what if d->currentText.isEmpty()?
0098 
0099     // TODO: carry state from last buffer
0100     d->mainTokenizer.setBuffer(fetchMoreText());
0101     d->start();
0102 }
0103 
0104 void BackgroundChecker::stop()
0105 {
0106     //    d->stop();
0107 }
0108 
0109 QString BackgroundChecker::fetchMoreText()
0110 {
0111     return QString();
0112 }
0113 
0114 void BackgroundChecker::finishedCurrentFeed()
0115 {
0116 }
0117 
0118 bool BackgroundChecker::autoDetectLanguageDisabled() const
0119 {
0120     return d->autoDetectLanguageDisabled;
0121 }
0122 
0123 void BackgroundChecker::setAutoDetectLanguageDisabled(bool autoDetectDisabled)
0124 {
0125     d->autoDetectLanguageDisabled = autoDetectDisabled;
0126 }
0127 
0128 void BackgroundChecker::setSpeller(const Speller &speller)
0129 {
0130     d->currentDict = speller;
0131 }
0132 
0133 Speller BackgroundChecker::speller() const
0134 {
0135     return d->currentDict;
0136 }
0137 
0138 bool BackgroundChecker::checkWord(const QString &word)
0139 {
0140     return d->currentDict.isCorrect(word);
0141 }
0142 
0143 bool BackgroundChecker::addWordToPersonal(const QString &word)
0144 {
0145     return d->currentDict.addToPersonal(word);
0146 }
0147 
0148 bool BackgroundChecker::addWordToSession(const QString &word)
0149 {
0150     return d->currentDict.addToSession(word);
0151 }
0152 
0153 QStringList BackgroundChecker::suggest(const QString &word) const
0154 {
0155     return d->currentDict.suggest(word);
0156 }
0157 
0158 void BackgroundChecker::changeLanguage(const QString &lang)
0159 {
0160     // this sets language only for current sentence
0161     d->currentDict.setLanguage(lang);
0162 }
0163 
0164 void BackgroundChecker::continueChecking()
0165 {
0166     d->continueChecking();
0167 }
0168 
0169 void BackgroundChecker::slotEngineDone()
0170 {
0171     finishedCurrentFeed();
0172     const QString currentText = fetchMoreText();
0173 
0174     if (currentText.isNull()) {
0175         Q_EMIT done();
0176     } else {
0177         d->mainTokenizer.setBuffer(currentText);
0178         d->start();
0179     }
0180 }
0181 
0182 QString BackgroundChecker::text() const
0183 {
0184     return d->mainTokenizer.buffer();
0185 }
0186 
0187 QString BackgroundChecker::currentContext() const
0188 {
0189     int len = 60;
0190     // we don't want the expression underneath casted to an unsigned int
0191     // which would cause it to always evaluate to false
0192     int currentPosition = d->lastMisspelled.position() + d->sentenceOffset;
0193     bool begin = ((currentPosition - len / 2) <= 0) ? true : false;
0194 
0195     QString buffer = d->mainTokenizer.buffer();
0196     buffer.replace(currentPosition, d->lastMisspelled.length(), QStringLiteral("<b>%1</b>").arg(d->lastMisspelled.toString()));
0197 
0198     QString context;
0199     if (begin) {
0200         context = QStringLiteral("%1...").arg(buffer.mid(0, len));
0201     } else {
0202         context = QStringLiteral("...%1...").arg(buffer.mid(currentPosition - 20, len));
0203     }
0204 
0205     context.replace(QLatin1Char('\n'), QLatin1Char(' '));
0206 
0207     return context;
0208 }
0209 
0210 void Sonnet::BackgroundChecker::replace(int start, const QString &oldText, const QString &newText)
0211 {
0212     // FIXME: here we assume that replacement is in current fragment. So 'words' has
0213     // to be adjusted and sentenceOffset does not
0214     d->words.replace(start - (d->sentenceOffset), oldText.length(), newText);
0215     d->mainTokenizer.replace(start, oldText.length(), newText);
0216 }
0217 
0218 #include "moc_backgroundchecker.cpp"
0219 #include "moc_backgroundchecker_p.cpp"