From e35d7a0f9bc85a0cc09fd589a89e6c894df246cc Mon Sep 17 00:00:00 2001 From: Stephan Witt Date: Fri, 21 Sep 2012 23:21:41 +0200 Subject: [PATCH] Implementation of spell check of current selection (bug #2511). --- src/Buffer.cpp | 7 +- src/frontends/qt4/GuiSpellchecker.cpp | 244 ++++++++++++++++++++------ status.20x | 2 + 3 files changed, 199 insertions(+), 54 deletions(-) diff --git a/src/Buffer.cpp b/src/Buffer.cpp index 15bead5d11..0af6091b57 100644 --- a/src/Buffer.cpp +++ b/src/Buffer.cpp @@ -4323,8 +4323,9 @@ int Buffer::spellCheck(DocIterator & from, DocIterator & to, WordLangTuple wl; suggestions.clear(); word_lang = WordLangTuple(); + bool const to_end = to.empty(); + DocIterator const end = to_end ? doc_iterator_end(this) : to; // OK, we start from here. - DocIterator const end = doc_iterator_end(this); for (; from != end; from.forwardPos()) { // We are only interested in text so remove the math CursorSlice. while (from.inMathed()) { @@ -4332,8 +4333,8 @@ int Buffer::spellCheck(DocIterator & from, DocIterator & to, from.pos()++; } // If from is at the end of the document (which is possible - // when leaving the mathed) LyX will crash later. - if (from == end) + // when leaving the mathed) LyX will crash later otherwise. + if (from.atEnd() || (!to_end && from >= end)) break; to = from; from.paragraph().spellCheck(); diff --git a/src/frontends/qt4/GuiSpellchecker.cpp b/src/frontends/qt4/GuiSpellchecker.cpp index 5dbdbbb0b0..98eaace14a 100644 --- a/src/frontends/qt4/GuiSpellchecker.cpp +++ b/src/frontends/qt4/GuiSpellchecker.cpp @@ -59,17 +59,22 @@ namespace frontend { struct SpellcheckerWidget::Private { - Private(SpellcheckerWidget * parent, DockView * dv) - : p(parent), dv_(dv), incheck_(false), wrap_around_(false) {} + Private(SpellcheckerWidget * parent, DockView * dv, GuiView * gv) + : p(parent), dv_(dv), gv_(gv), incheck_(false), wrap_around_(false) {} /// update from controller void updateSuggestions(docstring_list & words); /// move to next position after current word void forward(); /// check text until next misspelled/unknown word void check(); - /// + /// close the spell checker dialog + void hide() const; + /// make/restore a selection between from and to + void setSelection(DocIterator const & from, DocIterator const & to) const; + /// if no selection was checked: + /// ask the user if the check should start over bool continueFromBeginning(); - /// + /// set the given language in language chooser void setLanguage(Language const * lang); /// test and set guard flag bool inCheck() { @@ -79,20 +84,46 @@ struct SpellcheckerWidget::Private return false; } void canCheck() { incheck_ = false; } - /// check for wrap around of current position - bool isWrapAround(DocIterator cursor) const; + /// check for wrap around + void wrapAround(bool flag) { + wrap_around_ = flag; + if (flag) { + end_ = start_; + } + } + /// test for existing association with a document buffer + /// and test for already active check + bool disabled() { + return gv_->documentBufferView() == 0 || inCheck(); + } + /// the cursor position of the buffer view + DocIterator const cursor() const; + /// status checks + bool isCurrentBuffer(DocIterator const & cursor) const; + bool isWrapAround(DocIterator const & cursor) const; + bool isWrapAround() const { return wrap_around_; } + bool atLastPos(DocIterator const & cursor) const; + /// validate the cached doc iterators + /// The spell checker dialog is not modal. + /// The user may change the buffer being checked and break the iterators. + void fixPositionsIfBroken(); /// Ui::SpellcheckerUi ui; /// SpellcheckerWidget * p; /// - GuiView * gv_; - /// DockView * dv_; + /// + GuiView * gv_; /// current word being checked and lang code WordLangTuple word_; - /// + /// cursor position where spell checking starts DocIterator start_; + /// range to spell check + /// for selection both are non-empty + /// after wrap around the start position becomes the end + DocIterator begin_; + DocIterator end_; /// bool incheck_; /// @@ -101,10 +132,9 @@ struct SpellcheckerWidget::Private SpellcheckerWidget::SpellcheckerWidget(GuiView * gv, DockView * dv, QWidget * parent) - : QTabWidget(parent), d(new Private(this, dv)) + : QTabWidget(parent), d(new Private(this, dv, gv)) { d->ui.setupUi(this); - d->gv_ = gv; connect(d->ui.suggestionsLW, SIGNAL(itemDoubleClicked(QListWidgetItem*)), this, SLOT(on_replacePB_clicked())); @@ -178,53 +208,165 @@ void SpellcheckerWidget::on_replaceCO_highlighted(const QString & str) void SpellcheckerWidget::updateView() { BufferView * bv = d->gv_->documentBufferView(); - setEnabled(bv != 0); - if (bv && hasFocus() && d->start_.empty()) { - d->start_ = bv->cursor(); - d->check(); + // we need a buffer view and the buffer has to be writable + bool const enabled = bv != 0 && !bv->buffer().isReadonly(); + setEnabled(enabled); + if (enabled && hasFocus()) { + Cursor const & cursor = bv->cursor(); + if (d->start_.empty() || !d->isCurrentBuffer(cursor)) { + if (cursor.selection()) { + d->begin_ = cursor.selectionBegin(); + d->end_ = cursor.selectionEnd(); + d->start_ = d->begin_; + bv->cursor().setCursor(d->start_); + } else { + d->begin_ = DocIterator(); + d->end_ = DocIterator(); + d->start_ = cursor; + } + d->wrapAround(false); + d->check(); + } } } +DocIterator const SpellcheckerWidget::Private::cursor() const +{ + BufferView * bv = gv_->documentBufferView(); + return bv ? bv->cursor() : DocIterator(); +} bool SpellcheckerWidget::Private::continueFromBeginning() { + DocIterator const current_ = cursor(); + if (isCurrentBuffer(current_) && !begin_.empty()) { + // selection was checked + // start over from beginning makes no sense + fixPositionsIfBroken(); + hide(); + if (current_ == start_) { + // no errors found... tell the user the good news + // so there is some feedback + QMessageBox::information(p, + qt_("Spell Checker"), + qt_("Spell check of the selection done, " + "did not find any errors.")); + } + return false; + } QMessageBox::StandardButton const answer = QMessageBox::question(p, qt_("Spell Checker"), qt_("We reached the end of the document, would you like to " "continue from the beginning?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::No); if (answer == QMessageBox::No) { - dv_->hide(); + fixPositionsIfBroken(); + hide(); return false; } + // there is no selection, start over from the beginning now + wrapAround(true); dispatch(FuncRequest(LFUN_BUFFER_BEGIN)); - wrap_around_ = true; return true; } -bool SpellcheckerWidget::Private::isWrapAround(DocIterator cursor) const +bool SpellcheckerWidget::Private::isCurrentBuffer(DocIterator const & cursor) const { - return wrap_around_ && start_.buffer() == cursor.buffer() && start_ < cursor; + return start_.buffer() == cursor.buffer(); } +bool SpellcheckerWidget::Private::atLastPos(DocIterator const & cursor) const +{ + bool const valid_end = !end_.empty(); + return cursor.depth() <= 1 && ( + cursor.atEnd() || + (valid_end && isCurrentBuffer(cursor) && cursor >= end_)); +} -void SpellcheckerWidget::Private::forward() +bool SpellcheckerWidget::Private::isWrapAround(DocIterator const & cursor) const +{ + return wrap_around_ && isCurrentBuffer(cursor) && start_ < cursor; +} + +void SpellcheckerWidget::Private::fixPositionsIfBroken() +{ + DocIterator const current_ = cursor(); + if (!isCurrentBuffer(current_)) { + LYXERR(Debug::GUI, "wrong document of current cursor position " << start_); + start_ = current_; + begin_ = DocIterator(); + end_ = DocIterator(); + } + if (start_.fixIfBroken()) + LYXERR(Debug::GUI, "broken start position fixed " << start_); + if (begin_.fixIfBroken()) { + LYXERR(Debug::GUI, "broken selection begin position fixed " << begin_); + begin_ = DocIterator(); + end_ = DocIterator(); + } + if (end_.fixIfBroken()) + LYXERR(Debug::GUI, "broken selection end position fixed " << end_); +} + +void SpellcheckerWidget::Private::hide() const { BufferView * bv = gv_->documentBufferView(); - DocIterator from = bv->cursor(); + Cursor & bvcur = bv->cursor(); + dv_->hide(); + if (isCurrentBuffer(bvcur)) { + if (!begin_.empty() && !end_.empty()) { + // restore previous selection + setSelection(begin_, end_); + } else { + // restore cursor position + bvcur.setCursor(start_); + bvcur.clearSelection(); + bv->processUpdateFlags(Update::Force | Update::FitCursor); + } + } +} + +void SpellcheckerWidget::Private::setSelection( + DocIterator const & from, DocIterator const & to) const +{ + BufferView * bv = gv_->documentBufferView(); + DocIterator end = to; + + if (from.pit() != end.pit()) { + // there are multiple paragraphs in selection + Cursor & bvcur = bv->cursor(); + bvcur.setCursor(from); + bvcur.clearSelection(); + bvcur.setSelection(true); + bvcur.setCursor(end); + bvcur.setSelection(true); + } else { + // FIXME LFUN + // If we used a LFUN, dispatch would do all of this for us + int const size = end.pos() - from.pos(); + bv->putSelectionAt(from, size, false); + } + bv->processUpdateFlags(Update::Force | Update::FitCursor); +} + +void SpellcheckerWidget::Private::forward() +{ + DocIterator const from = cursor(); dispatch(FuncRequest(LFUN_ESCAPE)); - dispatch(FuncRequest(LFUN_CHAR_FORWARD)); - if (bv->cursor().depth() <= 1 && bv->cursor().atLastPos()) { - continueFromBeginning(); + fixPositionsIfBroken(); + if (!atLastPos(cursor())) { + dispatch(FuncRequest(LFUN_CHAR_FORWARD)); + } + if (atLastPos(cursor())) { return; } - if (from == bv->cursor()) { + if (from == cursor()) { //FIXME we must be at the end of a cell dispatch(FuncRequest(LFUN_CHAR_FORWARD)); } - if (isWrapAround(bv->cursor())) { - dv_->hide(); + if (isWrapAround(cursor())) { + hide(); } } @@ -247,12 +389,12 @@ bool SpellcheckerWidget::initialiseParams(std::string const &) if (bv == 0) return false; std::set languages = - bv->buffer().masterBuffer()->getLanguages(); + bv->buffer().masterBuffer()->getLanguages(); if (!languages.empty()) d->setLanguage(*languages.begin()); d->start_ = DocIterator(); - d->wrap_around_ = false; - d->incheck_ = false; + d->wrapAround(false); + d->canCheck(); return true; } @@ -260,7 +402,7 @@ bool SpellcheckerWidget::initialiseParams(std::string const &) void SpellcheckerWidget::on_ignoreAllPB_clicked() { /// ignore all occurrences of word - if (d->inCheck()) + if (d->disabled()) return; LYXERR(Debug::GUI, "Spellchecker: ignore all button"); if (d->word_.lang() && !d->word_.word().empty()) @@ -274,7 +416,7 @@ void SpellcheckerWidget::on_ignoreAllPB_clicked() void SpellcheckerWidget::on_addPB_clicked() { /// insert word in personal dictionary - if (d->inCheck()) + if (d->disabled()) return; LYXERR(Debug::GUI, "Spellchecker: add word button"); theSpellChecker()->insert(d->word_); @@ -287,7 +429,7 @@ void SpellcheckerWidget::on_addPB_clicked() void SpellcheckerWidget::on_ignorePB_clicked() { /// ignore this occurrence of word - if (d->inCheck()) + if (d->disabled()) return; LYXERR(Debug::GUI, "Spellchecker: ignore button"); d->forward(); @@ -298,7 +440,7 @@ void SpellcheckerWidget::on_ignorePB_clicked() void SpellcheckerWidget::on_findNextPB_clicked() { - if (d->inCheck()) + if (d->disabled()) return; docstring const textfield = qstring_to_ucs4(d->ui.wordED->text()); docstring const datastring = find2string(textfield, @@ -311,7 +453,7 @@ void SpellcheckerWidget::on_findNextPB_clicked() void SpellcheckerWidget::on_replacePB_clicked() { - if (d->inCheck()) + if (d->disabled()) return; docstring const textfield = qstring_to_ucs4(d->ui.wordED->text()); docstring const replacement = qstring_to_ucs4(d->ui.replaceCO->currentText()); @@ -333,7 +475,7 @@ void SpellcheckerWidget::on_replacePB_clicked() void SpellcheckerWidget::on_replaceAllPB_clicked() { - if (d->inCheck()) + if (d->disabled()) return; docstring const textfield = qstring_to_ucs4(d->ui.wordED->text()); docstring const replacement = qstring_to_ucs4(d->ui.replaceCO->currentText()); @@ -385,11 +527,14 @@ void SpellcheckerWidget::Private::check() BufferView * bv = gv_->documentBufferView(); if (!bv || bv->buffer().text().empty()) return; + + fixPositionsIfBroken(); + SpellChecker * speller = theSpellChecker(); if (speller && !speller->hasDictionary(bv->buffer().language())) { int dsize = speller->numDictionaries(); if (0 == dsize) { - dv_->hide(); + hide(); QMessageBox::information(p, qt_("Spell Checker"), qt_("Spell checker has no dictionaries.")); @@ -398,14 +543,13 @@ void SpellcheckerWidget::Private::check() } DocIterator from = bv->cursor(); - DocIterator to; + DocIterator to = isCurrentBuffer(from) ? end_ : doc_iterator_end(&bv->buffer()); WordLangTuple word_lang; docstring_list suggestions; LYXERR(Debug::GUI, "Spellchecker: start check at " << from); - int progress; try { - progress = bv->buffer().spellCheck(from, to, word_lang, suggestions); + bv->buffer().spellCheck(from, to, word_lang, suggestions); } catch (ExceptionMessage const & message) { if (message.type_ == WarningException) { Alert::warning(message.title_, message.details_); @@ -414,10 +558,10 @@ void SpellcheckerWidget::Private::check() throw message; } - // end of document - if (from == doc_iterator_end(&bv->buffer())) { - if (wrap_around_ || start_ == doc_iterator_begin(&bv->buffer())) { - dv_->hide(); + // end of document or selection? + if (atLastPos(from)) { + if (isWrapAround()) { + hide(); return; } if (continueFromBeginning()) @@ -426,7 +570,7 @@ void SpellcheckerWidget::Private::check() } if (isWrapAround(from)) { - dv_->hide(); + hide(); return; } @@ -435,13 +579,11 @@ void SpellcheckerWidget::Private::check() // set suggestions updateSuggestions(suggestions); // set language + if (!word_lang.lang()) + return; setLanguage(word_lang.lang()); - - // FIXME LFUN - // If we used a LFUN, dispatch would do all of this for us - int const size = to.pos() - from.pos(); - bv->putSelectionAt(from, size, false); - bv->processUpdateFlags(Update::Force | Update::FitCursor); + // mark misspelled word + setSelection(from, to); } diff --git a/status.20x b/status.20x index a3b6dc2c5c..fc67c6084f 100644 --- a/status.20x +++ b/status.20x @@ -51,6 +51,8 @@ What's new - Allow native LyX format to be shown in menu View->Source. +- Implementation of spell check of current selection (bug #2511). + * DOCUMENTATION AND LOCALIZATION -- 2.39.5