X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;f=src%2Ffrontends%2Fqt4%2FGuiSpellchecker.cpp;h=10edda44629b9b24705a5ed14d0113403dce9b3c;hb=425d092204118ea6c24c28e85fdf03fcf2bb51a4;hp=be77ce0f501d9a5bbf0d5243f4e9a5984adaab58;hpb=8e366b1cab08e144f4709dfd6a35852a18249b79;p=lyx.git diff --git a/src/frontends/qt4/GuiSpellchecker.cpp b/src/frontends/qt4/GuiSpellchecker.cpp index be77ce0f50..10edda4462 100644 --- a/src/frontends/qt4/GuiSpellchecker.cpp +++ b/src/frontends/qt4/GuiSpellchecker.cpp @@ -5,6 +5,7 @@ * * \author John Levon * \author Edwin Leuven + * \author Abdelrazak Younes * * Full author contact details are available in file CREDITS. */ @@ -15,28 +16,34 @@ #include "qt_helpers.h" +#include "ui_SpellcheckerUi.h" + #include "Buffer.h" #include "BufferParams.h" #include "BufferView.h" +#include "buffer_funcs.h" #include "Cursor.h" #include "CutAndPaste.h" +#include "FuncRequest.h" #include "Language.h" +#include "LyX.h" #include "LyXRC.h" +#include "lyxfind.h" #include "Paragraph.h" +#include "WordLangTuple.h" #include "support/debug.h" #include "support/docstring.h" +#include "support/docstring_list.h" +#include "support/ExceptionMessage.h" #include "support/gettext.h" #include "support/lstrings.h" #include "support/textutils.h" #include +#include -#if defined(USE_ASPELL) -# include "ASpell_local.h" -#endif - -#include "SpellBase.h" +#include "SpellChecker.h" #include "frontends/alert.h" @@ -47,252 +54,191 @@ namespace lyx { namespace frontend { -GuiSpellchecker::GuiSpellchecker(GuiView & lv) - : GuiDialog(lv, "spellchecker", qt_("Spellchecker")), exitEarly_(false), - oldval_(0), newvalue_(0), count_(0), speller_(0) +struct GuiSpellchecker::Private { - setupUi(this); + Private() : progress_(0), count_(0) {} + Ui::SpellcheckerUi ui; + /// current word being checked and lang code + WordLangTuple word_; + /// values for progress + int total_; + int progress_; + /// word count + int count_; +}; + - connect(closePB, SIGNAL(clicked()), this, SLOT(slotClose())); - connect(replacePB, SIGNAL(clicked()), this, SLOT(replace())); - connect(ignorePB, SIGNAL(clicked()), this, SLOT(ignore())); - connect(replacePB_3, SIGNAL(clicked()), this, SLOT(accept())); - connect(addPB, SIGNAL(clicked()), this, SLOT(add())); +GuiSpellchecker::GuiSpellchecker(GuiView & lv) + : DockView(lv, "spellchecker", qt_("Spellchecker"), + Qt::RightDockWidgetArea), d(new GuiSpellchecker::Private) +{ + d->ui.setupUi(this); - connect(replaceCO, SIGNAL(highlighted(QString)), - this, SLOT(replaceChanged(QString))); - connect(suggestionsLW, SIGNAL(itemDoubleClicked(QListWidgetItem*)), - this, SLOT(replace())); - connect(suggestionsLW, SIGNAL(itemClicked(QListWidgetItem*)), - this, SLOT(suggestionChanged(QListWidgetItem*))); + connect(d->ui.suggestionsLW, SIGNAL(itemDoubleClicked(QListWidgetItem*)), + this, SLOT(on_replacePB_clicked())); - wordED->setReadOnly(true); + d->ui.wordED->setReadOnly(true); - bc().setPolicy(ButtonPolicy::NoRepeatedApplyReadOnlyPolicy); - bc().setCancel(closePB); + d->ui.suggestionsLW->installEventFilter(this); } GuiSpellchecker::~GuiSpellchecker() { - delete speller_; + delete d; } -void GuiSpellchecker::suggestionChanged(QListWidgetItem * item) +void GuiSpellchecker::on_closePB_clicked() { - if (replaceCO->count() != 0) - replaceCO->setItemText(0, item->text()); - else - replaceCO->addItem(item->text()); - - replaceCO->setCurrentIndex(0); + close(); } -void GuiSpellchecker::replaceChanged(const QString & str) +bool GuiSpellchecker::eventFilter(QObject *obj, QEvent *event) { - if (suggestionsLW->currentItem()->text() == str) - return; - - for (int i = 0; i != suggestionsLW->count(); ++i) { - if (suggestionsLW->item(i)->text() == str) { - suggestionsLW->setCurrentRow(i); - break; + if (obj == d->ui.suggestionsLW && event->type() == QEvent::KeyPress) { + QKeyEvent *e = static_cast (event); + if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) { + on_suggestionsLW_itemClicked(d->ui.suggestionsLW->currentItem()); + on_replacePB_clicked(); + return true; + } else if (e->key() == Qt::Key_Right) { + on_suggestionsLW_itemClicked(d->ui.suggestionsLW->currentItem()); + return true; } } + // standard event processing + return QWidget::eventFilter(obj, event); } -void GuiSpellchecker::reject() +void GuiSpellchecker::on_suggestionsLW_itemClicked(QListWidgetItem * item) { - slotClose(); - QDialog::reject(); + if (d->ui.replaceCO->count() != 0) + d->ui.replaceCO->setItemText(0, item->text()); + else + d->ui.replaceCO->addItem(item->text()); + + d->ui.replaceCO->setCurrentIndex(0); } -void GuiSpellchecker::updateContents() +void GuiSpellchecker::on_replaceCO_highlighted(const QString & str) { - // The clauses below are needed because the spellchecker - // has many flaws (see bugs 1950, 2218). - // Basically, we have to distinguish the case where a - // spellcheck has already been performed for the whole - // document (exitEarly() == true, isVisible() == false) - // from the rest (exitEarly() == false, isVisible() == true). - // FIXME: rewrite the whole beast! - static bool check_after_early_exit; - if (exitEarly()) { - // a spellcheck has already been performed, - check(); - check_after_early_exit = true; - } - else if (isVisible()) { - // the above check triggers a second update, - // and isVisible() is true then. Prevent a - // second check that skips the first word - if (check_after_early_exit) - // don't check, but reset the bool. - // business as usual after this. - check_after_early_exit = false; - else - // perform spellcheck (default case) - check(); + QListWidget * lw = d->ui.suggestionsLW; + if (lw->currentItem() && lw->currentItem()->text() == str) + return; + + for (int i = 0; i != lw->count(); ++i) { + if (lw->item(i)->text() == str) { + lw->setCurrentRow(i); + break; + } } } -void GuiSpellchecker::accept() +void GuiSpellchecker::updateView() { - ignoreAll(); + if (hasFocus()) + check(); } -void GuiSpellchecker::add() +void GuiSpellchecker::on_ignoreAllPB_clicked() { - insert(); + /// replace all occurances of word + theSpellChecker()->accept(d->word_); + check(); } -void GuiSpellchecker::ignore() +void GuiSpellchecker::on_addPB_clicked() { + /// insert word in personal dictionary + theSpellChecker()->insert(d->word_); check(); } -void GuiSpellchecker::replace() +void GuiSpellchecker::on_ignorePB_clicked() { - replace(qstring_to_ucs4(replaceCO->currentText())); + dispatch(FuncRequest(LFUN_CHAR_FORWARD)); + check(); } -void GuiSpellchecker::partialUpdate(int state) +void GuiSpellchecker::on_findNextPB_clicked() { - switch (state) { - case SPELL_PROGRESSED: - spellcheckPR->setValue(getProgress()); - break; - - case SPELL_FOUND_WORD: { - wordED->setText(toqstr(getWord())); - suggestionsLW->clear(); - - docstring w; - while (!(w = getSuggestion()).empty()) - suggestionsLW->addItem(toqstr(w)); - - if (suggestionsLW->count() == 0) - suggestionChanged(new QListWidgetItem(wordED->text())); - else - suggestionChanged(suggestionsLW->item(0)); - - suggestionsLW->setCurrentRow(0); - break; - } - } + docstring const data = find2string( + qstring_to_ucs4(d->ui.wordED->text()), + true, true, true); + dispatch(FuncRequest(LFUN_WORD_FIND, data)); } -static SpellBase * createSpeller(BufferParams const & bp) +void GuiSpellchecker::on_replacePB_clicked() { - string lang = lyxrc.spellchecker_use_alt_lang - ? lyxrc.spellchecker_alt_lang - : bp.language->code(); - -#if defined(USE_ASPELL) - return new ASpell(bp, lang); -#endif - return new SpellBase; + docstring const replacement = qstring_to_ucs4(d->ui.replaceCO->currentText()); + + LYXERR(Debug::GUI, "Replace (" << replacement << ")"); + BufferView * bv = const_cast(bufferview()); + if (!bv->cursor().inTexted()) + return; + cap::replaceSelectionWithString(bv->cursor(), replacement, true); + bv->buffer().markDirty(); + // If we used an LFUN, we would not need that + bv->processUpdateFlags(Update::Force | Update::FitCursor); + // fix up the count + --d->count_; + check(); } -bool GuiSpellchecker::initialiseParams(string const &) +void GuiSpellchecker::on_replaceAllPB_clicked() { - LYXERR(Debug::GUI, "Spellchecker::initialiseParams"); - - speller_ = createSpeller(buffer().params()); - if (!speller_) - return false; - - // reset values to initial - oldval_ = 0; - newvalue_ = 0; - count_ = 0; - - bool const success = speller_->error().empty(); - - if (!success) { - Alert::error(_("Spellchecker error"), - _("The spellchecker could not be started\n") - + speller_->error()); - delete speller_; - speller_ = 0; - } - - return success; + docstring const data = replace2string( + qstring_to_ucs4(d->ui.replaceCO->currentText()), + qstring_to_ucs4(d->ui.wordED->text()), + true, true, true, true); + dispatch(FuncRequest(LFUN_WORD_REPLACE, data)); + check(); // continue spellchecking } -void GuiSpellchecker::clearParams() +void GuiSpellchecker::updateSuggestions(docstring_list & words) { - LYXERR(Debug::GUI, "Spellchecker::clearParams"); - delete speller_; - speller_ = 0; -} + QString const suggestion = toqstr(d->word_.word()); + d->ui.wordED->setText(suggestion); + QListWidget * lw = d->ui.suggestionsLW; + lw->clear(); + if (words.empty()) { + on_suggestionsLW_itemClicked(new QListWidgetItem(suggestion)); + return; + } + for (size_t i = 0; i != words.size(); ++i) + lw->addItem(toqstr(words[i])); -static bool isLetter(DocIterator const & dit) -{ - return dit.inTexted() - && dit.inset().allowSpellCheck() - && dit.pos() != dit.lastpos() - && (dit.paragraph().isLetter(dit.pos()) - // We want to pass the ' and escape chars to ispell - || contains(from_utf8(lyxrc.spellchecker_esc_chars + '\''), - dit.paragraph().getChar(dit.pos()))) - && !dit.paragraph().isDeleted(dit.pos()); + on_suggestionsLW_itemClicked(lw->item(0)); + lw->setCurrentRow(0); } -static WordLangTuple nextWord(Cursor & cur, ptrdiff_t & progress) +bool GuiSpellchecker::initialiseParams(string const &) { - BufferParams const & bp = cur.bv().buffer().params(); - bool inword = false; - bool ignoreword = false; - cur.resetAnchor(); - docstring word; - string lang_code; - - while (cur.depth()) { - if (isLetter(cur)) { - if (!inword) { - inword = true; - ignoreword = false; - cur.resetAnchor(); - word.clear(); - lang_code = cur.paragraph().getFontSettings(bp, cur.pos()).language()->code(); - } - // Insets like optional hyphens and ligature - // break are part of a word. - if (!cur.paragraph().isInset(cur.pos())) { - char_type const c = cur.paragraph().getChar(cur.pos()); - word += c; - if (isDigit(c)) - ignoreword = true; - } - } else { // !isLetter(cur) - if (inword) - if (!word.empty() && !ignoreword) { - cur.setSelection(); - return WordLangTuple(word, lang_code); - } - inword = false; - } + LYXERR(Debug::GUI, "Spellchecker::initialiseParams"); - cur.forwardPos(); - ++progress; - } + if (!theSpellChecker()) + return false; - return WordLangTuple(docstring(), string()); + DocIterator const begin = doc_iterator_begin(&buffer()); + Cursor const & cur = bufferview()->cursor(); + d->progress_ = countWords(begin, cur); + d->total_ = d->progress_ + countWords(cur, doc_iterator_end(&buffer())); + d->count_ = 0; + return true; } @@ -300,160 +246,70 @@ void GuiSpellchecker::check() { LYXERR(Debug::GUI, "Check the spelling of a word"); - SpellBase::Result res = SpellBase::OK; - - Cursor cur = bufferview()->cursor(); - while (cur && cur.pos() && isLetter(cur)) - cur.backwardPos(); - - ptrdiff_t start = 0; - ptrdiff_t total = 0; - DocIterator it = doc_iterator_begin(buffer().inset()); - for (start = 1; it != cur; it.forwardPos()) - ++start; - - for (total = start; it; it.forwardPos()) - ++total; - - exitEarly_ = false; - - while (res == SpellBase::OK || res == SpellBase::IGNORED_WORD) { - word_ = nextWord(cur, start); - - // end of document - if (getWord().empty()) { - showSummary(); - exitEarly_ = true; + DocIterator from = bufferview()->cursor(); + DocIterator to; + WordLangTuple word_lang; + docstring_list suggestions; + + int progress; + try { + progress = buffer().spellCheck(from, to, word_lang, suggestions); + } catch (ExceptionMessage const & message) { + if (message.type_ == WarningException) { + Alert::warning(message.title_, message.details_); + close(); return; } - - ++count_; - - // Update slider if and only if value has changed - float progress = total ? float(start)/total : 1; - newvalue_ = int(100.0 * progress); - if (newvalue_!= oldval_) { - LYXERR(Debug::GUI, "Updating spell progress."); - oldval_ = newvalue_; - // set progress bar - partialUpdate(SPELL_PROGRESSED); - } - - // speller might be dead ... - if (!checkAlive()) - return; - - res = speller_->check(word_); - - // ... or it might just be reporting an error - if (!checkAlive()) - return; + throw message; } + LYXERR(Debug::GUI, "Found word \"" << word_lang.word() << "\""); + d->count_ += progress; + d->progress_ += progress; - LYXERR(Debug::GUI, "Found word \"" << to_utf8(getWord()) << "\""); - - int const size = cur.selEnd().pos() - cur.selBegin().pos(); - cur.pos() -= size; - BufferView * bv = const_cast(bufferview()); - bv->putSelectionAt(cur, size, false); - // FIXME: if we used a lfun like in find/replace, dispatch would do - // that for us - // FIXME: this Controller is very badly designed... - bv->processUpdateFlags(Update::Force | Update::FitCursor); - - // set suggestions - if (res != SpellBase::OK && res != SpellBase::IGNORED_WORD) { - LYXERR(Debug::GUI, "Found a word needing checking."); - partialUpdate(SPELL_FOUND_WORD); + // end of document + if (from == doc_iterator_end(&buffer())) { + showSummary(); + return; } -} - + if (!isVisible()) + show(); -bool GuiSpellchecker::checkAlive() -{ - if (speller_->alive() && speller_->error().empty()) - return true; - - docstring message; - if (speller_->error().empty()) - message = _("The spellchecker has died for some reason.\n" - "Maybe it has been killed."); - else - message = _("The spellchecker has failed.\n") + speller_->error(); + d->word_ = word_lang; - slotClose(); + int const progress_bar = d->total_ + ? int(100.0 * float(d->progress_)/d->total_) : 100; + LYXERR(Debug::GUI, "Updating spell progress."); + // set progress bar + d->ui.spellcheckPR->setValue(progress_bar); + // set suggestions + updateSuggestions(suggestions); - Alert::error(_("The spellchecker has failed"), message); - return false; + // FIXME: if we used a lfun like in find/replace, dispatch would do + // that for us + int const size = to.pos() - from.pos(); + BufferView * bv = const_cast(bufferview()); + bv->putSelectionAt(from, size, false); } void GuiSpellchecker::showSummary() { - if (!checkAlive() || count_ == 0) { - slotClose(); + if (d->count_ == 0) { + close(); return; } docstring message; - if (count_ != 1) - message = bformat(_("%1$d words checked."), count_); + if (d->count_ != 1) + message = bformat(_("%1$d words checked."), d->count_); else message = _("One word checked."); - slotClose(); + close(); Alert::information(_("Spelling check completed"), message); } -void GuiSpellchecker::replace(docstring const & replacement) -{ - LYXERR(Debug::GUI, "GuiSpellchecker::replace(" - << to_utf8(replacement) << ")"); - BufferView * bv = const_cast(bufferview()); - cap::replaceSelectionWithString(bv->cursor(), replacement, true); - bv->buffer().markDirty(); - // If we used an LFUN, we would not need that - bv->processUpdateFlags(Update::Force | Update::FitCursor); - // fix up the count - --count_; - check(); -} - - -void GuiSpellchecker::replaceAll(docstring const & replacement) -{ - // TODO: add to list - replace(replacement); -} - - -void GuiSpellchecker::insert() -{ - speller_->insert(word_); - check(); -} - - -docstring GuiSpellchecker::getSuggestion() const -{ - return speller_->nextMiss(); -} - - -docstring GuiSpellchecker::getWord() const -{ - return word_.word(); -} - - -void GuiSpellchecker::ignoreAll() -{ - speller_->accept(word_); - check(); -} - - Dialog * createGuiSpellchecker(GuiView & lv) { return new GuiSpellchecker(lv); } } // namespace frontend