X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;f=src%2Ffrontends%2Fqt4%2FGuiSpellchecker.cpp;h=537d9d79b50184b8e652d75aac0ab3369db54e2e;hb=b7f6b979d0f889f08e735f35378bb20ba3788b4b;hp=e3ed0c403f0fa51b33182c9ff815ee075052e419;hpb=ea50cd71f9dcf43cf75fbb06609d22abec3a1920;p=lyx.git diff --git a/src/frontends/qt4/GuiSpellchecker.cpp b/src/frontends/qt4/GuiSpellchecker.cpp index e3ed0c403f..537d9d79b5 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.selection(true); + bvcur.setCursor(end); + bvcur.selection(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(); } } @@ -246,13 +388,13 @@ bool SpellcheckerWidget::initialiseParams(std::string const &) BufferView * bv = d->gv_->documentBufferView(); if (bv == 0) return false; - std::set languages = - bv->buffer().masterBuffer()->getLanguages(); + std::set languages = + 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,12 +453,17 @@ 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()); - docstring const datastring = replace2string(replacement, textfield, - true, true, false, false); + docstring const datastring = + replace2string(replacement, textfield, + true, // case sensitive + true, // match word + false, // all words + true, // forward + false); // find next LYXERR(Debug::GUI, "Replace (" << replacement << ")"); dispatch(FuncRequest(LFUN_WORD_REPLACE, datastring)); @@ -328,16 +475,23 @@ 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()); - docstring const datastring = replace2string(replacement, textfield, - true, true, true, true); + docstring const datastring = + replace2string(replacement, textfield, + true, // case sensitive + true, // match word + true, // all words + true, // forward + false); // find next LYXERR(Debug::GUI, "Replace all (" << replacement << ")"); dispatch(FuncRequest(LFUN_WORD_REPLACE, datastring)); d->forward(); + // replace all wraps around + d->wrapAround(true); d->check(); // continue spellchecking d->canCheck(); } @@ -376,8 +530,22 @@ void SpellcheckerWidget::Private::check() 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) { + hide(); + QMessageBox::information(p, + qt_("Spell Checker"), + qt_("Spell checker has no dictionaries.")); + return; + } + } + DocIterator from = bv->cursor(); - DocIterator to; + DocIterator to = isCurrentBuffer(from) ? end_ : doc_iterator_end(&bv->buffer()); WordLangTuple word_lang; docstring_list suggestions; @@ -392,10 +560,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()) @@ -404,7 +572,7 @@ void SpellcheckerWidget::Private::check() } if (isWrapAround(from)) { - dv_->hide(); + hide(); return; } @@ -413,13 +581,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); } @@ -447,10 +613,10 @@ void GuiSpellchecker::updateView() } -Dialog * createGuiSpellchecker(GuiView & lv) -{ +Dialog * createGuiSpellchecker(GuiView & lv) +{ GuiSpellchecker * gui = new GuiSpellchecker(lv, Qt::RightDockWidgetArea); -#ifdef Q_WS_MACX +#ifdef Q_OS_MAC gui->setFloating(true); #endif return gui;