X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;f=src%2Ffrontends%2Fqt4%2FGuiSpellchecker.cpp;h=537d9d79b50184b8e652d75aac0ab3369db54e2e;hb=b7f6b979d0f889f08e735f35378bb20ba3788b4b;hp=8d401d12147306ae64c6b45a9a43daafddf01e69;hpb=4a4d7a57f263616d67033b5f53698911544fd15b;p=lyx.git diff --git a/src/frontends/qt4/GuiSpellchecker.cpp b/src/frontends/qt4/GuiSpellchecker.cpp index 8d401d1214..537d9d79b5 100644 --- a/src/frontends/qt4/GuiSpellchecker.cpp +++ b/src/frontends/qt4/GuiSpellchecker.cpp @@ -24,6 +24,7 @@ #include "BufferView.h" #include "buffer_funcs.h" #include "Cursor.h" +#include "Text.h" #include "CutAndPaste.h" #include "FuncRequest.h" #include "Language.h" @@ -41,8 +42,9 @@ #include "support/lstrings.h" #include "support/textutils.h" -#include #include +#include +#include #include "SpellChecker.h" @@ -55,23 +57,82 @@ namespace lyx { namespace frontend { -struct GuiSpellchecker::Private +struct SpellcheckerWidget::Private { - Private() : progress_(0), count_(0) {} + 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() { + if (incheck_) + return true; + incheck_ = true; + return false; + } + void canCheck() { incheck_ = false; } + /// 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; + /// + DockView * dv_; + /// + GuiView * gv_; /// current word being checked and lang code WordLangTuple word_; - /// values for progress - int total_; - int progress_; - /// word count - int count_; + /// 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_; + /// + bool wrap_around_; }; -GuiSpellchecker::GuiSpellchecker(GuiView & lv) - : DockView(lv, "spellchecker", qt_("Spellchecker"), - Qt::RightDockWidgetArea), d(new GuiSpellchecker::Private) +SpellcheckerWidget::SpellcheckerWidget(GuiView * gv, DockView * dv, QWidget * parent) + : QTabWidget(parent), d(new Private(this, dv, gv)) { d->ui.setupUi(this); @@ -91,19 +152,13 @@ GuiSpellchecker::GuiSpellchecker(GuiView & lv) } -GuiSpellchecker::~GuiSpellchecker() +SpellcheckerWidget::~SpellcheckerWidget() { delete d; } -void GuiSpellchecker::on_closePB_clicked() -{ - close(); -} - - -bool GuiSpellchecker::eventFilter(QObject *obj, QEvent *event) +bool SpellcheckerWidget::eventFilter(QObject *obj, QEvent *event) { if (obj == d->ui.suggestionsLW && event->type() == QEvent::KeyPress) { QKeyEvent *e = static_cast (event); @@ -124,7 +179,7 @@ bool GuiSpellchecker::eventFilter(QObject *obj, QEvent *event) } -void GuiSpellchecker::on_suggestionsLW_itemClicked(QListWidgetItem * item) +void SpellcheckerWidget::on_suggestionsLW_itemClicked(QListWidgetItem * item) { if (d->ui.replaceCO->count() != 0) d->ui.replaceCO->setItemText(0, item->text()); @@ -135,7 +190,7 @@ void GuiSpellchecker::on_suggestionsLW_itemClicked(QListWidgetItem * item) } -void GuiSpellchecker::on_replaceCO_highlighted(const QString & str) +void SpellcheckerWidget::on_replaceCO_highlighted(const QString & str) { QListWidget * lw = d->ui.suggestionsLW; if (lw->currentItem() && lw->currentItem()->text() == str) @@ -150,14 +205,173 @@ void GuiSpellchecker::on_replaceCO_highlighted(const QString & str) } -void GuiSpellchecker::updateView() +void SpellcheckerWidget::updateView() { - if (hasFocus()) - check(); + BufferView * bv = d->gv_->documentBufferView(); + // 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(); +} -void GuiSpellchecker::on_languageCO_activated(int index) +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) { + fixPositionsIfBroken(); + hide(); + return false; + } + // there is no selection, start over from the beginning now + wrapAround(true); + dispatch(FuncRequest(LFUN_BUFFER_BEGIN)); + return true; +} + +bool SpellcheckerWidget::Private::isCurrentBuffer(DocIterator const & cursor) const +{ + 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_)); +} + +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(); + 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)); + fixPositionsIfBroken(); + if (!atLastPos(cursor())) { + dispatch(FuncRequest(LFUN_CHAR_FORWARD)); + } + if (atLastPos(cursor())) { + return; + } + if (from == cursor()) { + //FIXME we must be at the end of a cell + dispatch(FuncRequest(LFUN_CHAR_FORWARD)); + } + if (isWrapAround(cursor())) { + hide(); + } +} + + +void SpellcheckerWidget::on_languageCO_activated(int index) { string const lang = fromqstr(d->ui.languageCO->itemData(index).toString()); @@ -165,187 +379,244 @@ void GuiSpellchecker::on_languageCO_activated(int index) // nothing changed return; dispatch(FuncRequest(LFUN_LANGUAGE, lang)); - check(); + d->check(); +} + + +bool SpellcheckerWidget::initialiseParams(std::string const &) +{ + BufferView * bv = d->gv_->documentBufferView(); + if (bv == 0) + return false; + std::set languages = + bv->buffer().masterBuffer()->getLanguages(); + if (!languages.empty()) + d->setLanguage(*languages.begin()); + d->start_ = DocIterator(); + d->wrapAround(false); + d->canCheck(); + return true; } -void GuiSpellchecker::on_ignoreAllPB_clicked() +void SpellcheckerWidget::on_ignoreAllPB_clicked() { - /// replace all occurences of word + /// ignore all occurrences of word + if (d->disabled()) + return; + LYXERR(Debug::GUI, "Spellchecker: ignore all button"); if (d->word_.lang() && !d->word_.word().empty()) theSpellChecker()->accept(d->word_); - check(); + d->forward(); + d->check(); + d->canCheck(); } -void GuiSpellchecker::on_addPB_clicked() +void SpellcheckerWidget::on_addPB_clicked() { /// insert word in personal dictionary + if (d->disabled()) + return; + LYXERR(Debug::GUI, "Spellchecker: add word button"); theSpellChecker()->insert(d->word_); - check(); + d->forward(); + d->check(); + d->canCheck(); } -void GuiSpellchecker::on_ignorePB_clicked() +void SpellcheckerWidget::on_ignorePB_clicked() { - dispatch(FuncRequest(LFUN_ESCAPE)); - dispatch(FuncRequest(LFUN_CHAR_FORWARD)); - check(); + /// ignore this occurrence of word + if (d->disabled()) + return; + LYXERR(Debug::GUI, "Spellchecker: ignore button"); + d->forward(); + d->check(); + d->canCheck(); } -void GuiSpellchecker::on_findNextPB_clicked() +void SpellcheckerWidget::on_findNextPB_clicked() { - docstring const data = find2string( - qstring_to_ucs4(d->ui.wordED->text()), + if (d->disabled()) + return; + docstring const textfield = qstring_to_ucs4(d->ui.wordED->text()); + docstring const datastring = find2string(textfield, true, true, true); - dispatch(FuncRequest(LFUN_WORD_FIND, data)); + LYXERR(Debug::GUI, "Spellchecker: find next (" << textfield << ")"); + dispatch(FuncRequest(LFUN_WORD_FIND, datastring)); + d->canCheck(); } -void GuiSpellchecker::on_replacePB_clicked() +void SpellcheckerWidget::on_replacePB_clicked() { + 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, // case sensitive + true, // match word + false, // all words + true, // forward + false); // find next LYXERR(Debug::GUI, "Replace (" << replacement << ")"); - /* - Slight hack ahead: we want to use the dispatch machinery - (see bug #6217), but self-insert honors the ``auto region - delete'' setting, which is not wanted here. Creating a new - ad-hoc LFUN seems overkill, but it could be an option (JMarc). - */ - bool const ard = lyxrc.auto_region_delete; - lyxrc.auto_region_delete = true; - dispatch(FuncRequest(LFUN_SELF_INSERT, replacement)); - lyxrc.auto_region_delete = ard; - // fix up the count - --d->count_; - check(); + dispatch(FuncRequest(LFUN_WORD_REPLACE, datastring)); + d->forward(); + d->check(); + d->canCheck(); } -void GuiSpellchecker::on_replaceAllPB_clicked() +void SpellcheckerWidget::on_replaceAllPB_clicked() { - 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 + 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, // 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(); } -void GuiSpellchecker::updateSuggestions(docstring_list & words) +void SpellcheckerWidget::Private::updateSuggestions(docstring_list & words) { - QString const suggestion = toqstr(d->word_.word()); - d->ui.wordED->setText(suggestion); - QListWidget * lw = d->ui.suggestionsLW; + QString const suggestion = toqstr(word_.word()); + ui.wordED->setText(suggestion); + QListWidget * lw = ui.suggestionsLW; lw->clear(); if (words.empty()) { - on_suggestionsLW_itemClicked(new QListWidgetItem(suggestion)); + p->on_suggestionsLW_itemClicked(new QListWidgetItem(suggestion)); return; } for (size_t i = 0; i != words.size(); ++i) lw->addItem(toqstr(words[i])); - on_suggestionsLW_itemClicked(lw->item(0)); + p->on_suggestionsLW_itemClicked(lw->item(0)); lw->setCurrentRow(0); } -bool GuiSpellchecker::initialiseParams(string const &) +void SpellcheckerWidget::Private::setLanguage(Language const * lang) { - LYXERR(Debug::GUI, "Spellchecker::initialiseParams"); - - if (!theSpellChecker()) - return false; - - 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; + int const pos = ui.languageCO->findData(toqstr(lang->lang())); + if (pos != -1) + ui.languageCO->setCurrentIndex(pos); } -void GuiSpellchecker::check() +void SpellcheckerWidget::Private::check() { - LYXERR(Debug::GUI, "Check the spelling of a word"); + BufferView * bv = gv_->documentBufferView(); + if (!bv || bv->buffer().text().empty()) + return; + + fixPositionsIfBroken(); - DocIterator from = bufferview()->cursor(); - DocIterator to; + 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 = isCurrentBuffer(from) ? end_ : doc_iterator_end(&bv->buffer()); WordLangTuple word_lang; docstring_list suggestions; - int progress; + LYXERR(Debug::GUI, "Spellchecker: start check at " << from); try { - progress = 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_); - close(); return; } throw message; } - LYXERR(Debug::GUI, "Found word \"" << word_lang.word() << "\""); - d->count_ += progress; - d->progress_ += progress; - // end of document - if (from == doc_iterator_end(&buffer())) { - showSummary(); + // end of document or selection? + if (atLastPos(from)) { + if (isWrapAround()) { + hide(); + return; + } + if (continueFromBeginning()) + check(); + return; + } + + if (isWrapAround(from)) { + hide(); return; } - if (!isVisible()) - show(); - d->word_ = word_lang; + word_ = word_lang; - 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); // set language - int const pos = d->ui.languageCO->findData(toqstr(word_lang.lang()->lang())); - if (pos != -1) - d->ui.languageCO->setCurrentIndex(pos); + if (!word_lang.lang()) + return; + setLanguage(word_lang.lang()); + // mark misspelled word + setSelection(from, to); +} + - // 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); +GuiSpellchecker::GuiSpellchecker(GuiView & parent, + Qt::DockWidgetArea area, Qt::WindowFlags flags) + : DockView(parent, "spellchecker", qt_("Spellchecker"), + area, flags) +{ + widget_ = new SpellcheckerWidget(&parent, this); + setWidget(widget_); + setFocusProxy(widget_); } -void GuiSpellchecker::showSummary() +GuiSpellchecker::~GuiSpellchecker() { - if (d->count_ == 0) { - close(); - return; - } + setFocusProxy(0); + delete widget_; +} - docstring message; - if (d->count_ != 1) - message = bformat(_("%1$d words checked."), d->count_); - else - message = _("One word checked."); - close(); - Alert::information(_("Spelling check completed"), message); +void GuiSpellchecker::updateView() +{ + widget_->updateView(); } -Dialog * createGuiSpellchecker(GuiView & lv) -{ - GuiSpellchecker * gui = new GuiSpellchecker(lv); -#ifdef Q_WS_MACX +Dialog * createGuiSpellchecker(GuiView & lv) +{ + GuiSpellchecker * gui = new GuiSpellchecker(lv, Qt::RightDockWidgetArea); +#ifdef Q_OS_MAC gui->setFloating(true); #endif return gui;