sc/inc/editutil.hxx | 4 sc/inc/scmod.hxx | 2 sc/inc/spellcheckcontext.hxx | 71 +++--- sc/source/ui/app/scmod.cxx | 37 --- sc/source/ui/inc/gridwin.hxx | 4 sc/source/ui/inc/output.hxx | 2 sc/source/ui/inc/tabview.hxx | 2 sc/source/ui/view/dbfunc.cxx | 2 sc/source/ui/view/gridwin.cxx | 200 ----------------- sc/source/ui/view/gridwin2.cxx | 6 sc/source/ui/view/spellcheckcontext.cxx | 360 ++++++++++++++++++++++++++++---- sc/source/ui/view/tabview.cxx | 18 - sc/source/ui/view/viewfun2.cxx | 32 ++ sc/source/ui/view/viewfun3.cxx | 15 - sc/source/ui/view/viewfunc.cxx | 7 15 files changed, 426 insertions(+), 336 deletions(-)
New commits: commit bdd149b1ff3d43b94cadc0d43365100c287c7639 Author: Dennis Francis <dennis.fran...@collabora.com> AuthorDate: Sun Oct 4 12:47:46 2020 +0530 Commit: Noel Grandin <noel.gran...@collabora.co.uk> CommitDate: Wed Oct 28 08:39:25 2020 +0100 Improve spell checking performance and impl. in several ways: * do synchronous spell checking, avoiding an idle handler * avoid continuous invalidations caused per-cell by spell-checking * cache spell-checking information for a given SharedString to avoid repeated checking of frequently recurring strings. Change-Id: Ie251f263a8932465297dd8bd66dfc4aa10984947 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/104705 Tested-by: Jenkins Reviewed-by: Noel Grandin <noel.gran...@collabora.co.uk> diff --git a/sc/inc/editutil.hxx b/sc/inc/editutil.hxx index f28249145c06..d760faf5c53e 100644 --- a/sc/inc/editutil.hxx +++ b/sc/inc/editutil.hxx @@ -117,10 +117,8 @@ protected: class SC_DLLPUBLIC ScEditEngineDefaulter : public ScEnginePoolHelper, public EditEngine { -private: - using EditEngine::SetText; - public: + using EditEngine::SetText; /// bDeleteEnginePool: Engine becomes the owner of the pool /// and deletes it on destruction ScEditEngineDefaulter( SfxItemPool* pEnginePool, diff --git a/sc/inc/scmod.hxx b/sc/inc/scmod.hxx index c8af1d424d9b..20dc94e9e9e8 100644 --- a/sc/inc/scmod.hxx +++ b/sc/inc/scmod.hxx @@ -81,7 +81,6 @@ class SfxDialogController; class SAL_DLLPUBLIC_RTTI ScModule final : public SfxModule, public SfxListener, public utl::ConfigurationListener { Timer m_aIdleTimer; - Idle m_aSpellIdle; std::unique_ptr<ScDragData> m_pDragData; ScSelectionTransferObj* m_pSelTransfer; ScMessagePool* m_pMessagePool; @@ -131,7 +130,6 @@ public: // moved by the application DECL_LINK( IdleHandler, Timer*, void ); // Timer instead of idle - DECL_LINK( SpellTimerHdl, Timer*, void ); DECL_LINK( CalcFieldValueHdl, EditFieldInfo*, void ); void Execute( SfxRequest& rReq ); diff --git a/sc/inc/spellcheckcontext.hxx b/sc/inc/spellcheckcontext.hxx index 42ec80389af2..684b2ceb9edc 100644 --- a/sc/inc/spellcheckcontext.hxx +++ b/sc/inc/spellcheckcontext.hxx @@ -10,50 +10,53 @@ #ifndef INCLUDED_SC_INC_SPELLCHECKCONTEXT_HXX #define INCLUDED_SC_INC_SPELLCHECKCONTEXT_HXX +#include <i18nlangtag/lang.h> #include <editeng/misspellrange.hxx> #include "types.hxx" -#include <unordered_map> +#include <memory> #include <vector> -namespace sc { +class ScDocument; +class ScTabEditEngine; -struct SpellCheckContext +namespace sc { - struct CellPos - { - struct Hash - { - size_t operator() (const CellPos& rPos) const; - }; - - SCCOL mnCol; - SCROW mnRow; - - CellPos(); - CellPos(SCCOL nCol, SCROW nRow); - - void setInvalid(); - bool isValid() const; - void reset(); - - bool operator== (const CellPos& r) const; - }; - - typedef std::unordered_map<CellPos, std::vector<editeng::MisspellRanges>, CellPos::Hash> CellMapType; - - CellPos maPos; - CellMapType maMisspellCells; - - SpellCheckContext(); - - bool isMisspelled( SCCOL nCol, SCROW nRow ) const; - const std::vector<editeng::MisspellRanges>* getMisspellRanges( SCCOL nCol, SCROW nRow ) const; - void setMisspellRanges( SCCOL nCol, SCROW nRow, const std::vector<editeng::MisspellRanges>* pRanges ); +/** + * Class shared between grid windows to cache + * spelling results. + */ +class SpellCheckContext +{ + class SpellCheckCache; + struct SpellCheckStatus; + struct SpellCheckResult; + + std::unique_ptr<SpellCheckCache> mpCache; + std::unique_ptr<SpellCheckResult> mpResult; + ScDocument* pDoc; + std::unique_ptr<ScTabEditEngine> mpEngine; + std::unique_ptr<SpellCheckStatus> mpStatus; + SCTAB mnTab; + LanguageType meLanguage; + +public: + SpellCheckContext(ScDocument* pDocument, SCTAB nTab); + ~SpellCheckContext(); + + bool isMisspelled(SCCOL nCol, SCROW nRow) const; + const std::vector<editeng::MisspellRanges>* getMisspellRanges(SCCOL nCol, SCROW nRow) const; + void setMisspellRanges(SCCOL nCol, SCROW nRow, + const std::vector<editeng::MisspellRanges>* pRanges); void reset(); -}; + void resetForContentChange(); +private: + void ensureResults(SCCOL nCol, SCROW nRow); + void resetCache(bool bContentChangeOnly = false); + void setup(); +}; } #endif diff --git a/sc/source/ui/app/scmod.cxx b/sc/source/ui/app/scmod.cxx index 3115b27126c1..d39c9df9a683 100644 --- a/sc/source/ui/app/scmod.cxx +++ b/sc/source/ui/app/scmod.cxx @@ -117,7 +117,6 @@ void ScModule::InitInterface_Impl() ScModule::ScModule( SfxObjectFactory* pFact ) : SfxModule("sc", {pFact}), m_aIdleTimer("sc ScModule IdleTimer"), - m_aSpellIdle("sc ScModule SpellIdle"), m_pDragData(new ScDragData), m_pSelTransfer( nullptr ), m_pMessagePool( nullptr ), @@ -144,8 +143,6 @@ ScModule::ScModule( SfxObjectFactory* pFact ) : ErrCodeArea::Sc, GetResLocale()) ); - m_aSpellIdle.SetInvokeHandler( LINK( this, ScModule, SpellTimerHdl ) ); - m_aIdleTimer.SetTimeout(SC_IDLE_MIN); m_aIdleTimer.SetInvokeHandler( LINK( this, ScModule, IdleHandler ) ); m_aIdleTimer.Start(); @@ -1815,16 +1812,11 @@ IMPL_LINK_NOARG(ScModule, IdleHandler, Timer *, void) } bool bMore = false; - bool bAutoSpell = false; ScDocShell* pDocSh = dynamic_cast<ScDocShell*>(SfxObjectShell::Current()); if ( pDocSh ) { ScDocument& rDoc = pDocSh->GetDocument(); - bAutoSpell = rDoc.GetDocOptions().IsAutoSpell(); - if (pDocSh->IsReadOnly()) - bAutoSpell = false; - sc::DocumentLinkManager& rLinkMgr = rDoc.GetDocLinkManager(); bool bLinks = rLinkMgr.idleCheckLinks(); bool bWidth = rDoc.IdleCalcTextWidth(); @@ -1837,19 +1829,6 @@ IMPL_LINK_NOARG(ScModule, IdleHandler, Timer *, void) lcl_CheckNeedsRepaint( pDocSh ); } - if (bAutoSpell) - { - ScTabViewShell* pViewSh = dynamic_cast<ScTabViewShell*>(SfxViewShell::Current()); - if (pViewSh) - { - bool bSpell = pViewSh->ContinueOnlineSpelling(); - if (bSpell) - { - m_aSpellIdle.Start(); - bMore = true; - } - } - } sal_uInt64 nOldTime = m_aIdleTimer.GetTimeout(); sal_uInt64 nNewTime = nOldTime; @@ -1877,22 +1856,6 @@ IMPL_LINK_NOARG(ScModule, IdleHandler, Timer *, void) m_aIdleTimer.Start(); } -IMPL_LINK_NOARG(ScModule, SpellTimerHdl, Timer *, void) -{ - if ( Application::AnyInput( VclInputFlags::KEYBOARD ) ) - { - m_aSpellIdle.Start(); - return; // Later again ... - } - - ScTabViewShell* pViewSh = dynamic_cast<ScTabViewShell*>(SfxViewShell::Current()); - if (pViewSh) - { - if (pViewSh->ContinueOnlineSpelling()) - m_aSpellIdle.Start(); - } -} - /** * Virtual methods for the OptionsDialog */ diff --git a/sc/source/ui/inc/gridwin.hxx b/sc/source/ui/inc/gridwin.hxx index 74b335653c54..d9c0606b2dd2 100644 --- a/sc/source/ui/inc/gridwin.hxx +++ b/sc/source/ui/inc/gridwin.hxx @@ -37,7 +37,7 @@ namespace editeng { } namespace sc { - struct SpellCheckContext; + class SpellCheckContext; } namespace sdr::overlay { class OverlayManager; } @@ -433,9 +433,9 @@ public: void CursorChanged(); void DrawLayerCreated(); - bool ContinueOnlineSpelling(); void EnableAutoSpell( bool bEnable ); void ResetAutoSpell(); + void ResetAutoSpellForContentChange(); void SetAutoSpellData( SCCOL nPosX, SCROW nPosY, const std::vector<editeng::MisspellRanges>* pRanges ); const std::vector<editeng::MisspellRanges>* GetAutoSpellData( SCCOL nPosX, SCROW nPosY ); bool InsideVisibleRange( SCCOL nPosX, SCROW nPosY ); diff --git a/sc/source/ui/inc/output.hxx b/sc/source/ui/inc/output.hxx index a1e5d6661135..528b4ef770f4 100644 --- a/sc/source/ui/inc/output.hxx +++ b/sc/source/ui/inc/output.hxx @@ -31,7 +31,7 @@ #include <optional> namespace sc { - struct SpellCheckContext; + class SpellCheckContext; } namespace editeng { diff --git a/sc/source/ui/inc/tabview.hxx b/sc/source/ui/inc/tabview.hxx index 97a1edc276b7..a716763f1d73 100644 --- a/sc/source/ui/inc/tabview.hxx +++ b/sc/source/ui/inc/tabview.hxx @@ -589,9 +589,9 @@ public: void SetDrawBrushSet( std::unique_ptr<SfxItemSet> pNew, bool bLock ); void ResetBrushDocument(); - bool ContinueOnlineSpelling(); void EnableAutoSpell( bool bEnable ); void ResetAutoSpell(); + void ResetAutoSpellForContentChange(); void SetAutoSpellData( SCCOL nPosX, SCROW nPosY, const std::vector<editeng::MisspellRanges>* pRanges ); /// @see ScModelObj::getRowColumnHeaders(). void getRowColumnHeaders(const tools::Rectangle& rRectangle, tools::JsonWriter& rJsonWriter); diff --git a/sc/source/ui/view/dbfunc.cxx b/sc/source/ui/view/dbfunc.cxx index 9698af40aa53..8f9f8e3a52a2 100644 --- a/sc/source/ui/view/dbfunc.cxx +++ b/sc/source/ui/view/dbfunc.cxx @@ -225,7 +225,7 @@ void ScDBFunc::Sort( const ScSortParam& rSortParam, bool bRecord, bool bPaint ) MarkRange( aDestRange ); } - ResetAutoSpell(); + ResetAutoSpellForContentChange(); } // filters diff --git a/sc/source/ui/view/gridwin.cxx b/sc/source/ui/view/gridwin.cxx index 5c45e2ef53bb..07a4a8317bac 100644 --- a/sc/source/ui/view/gridwin.cxx +++ b/sc/source/ui/view/gridwin.cxx @@ -466,6 +466,10 @@ void ScGridWindow::dispose() mpDPFieldPopup.disposeAndClear(); aComboButton.SetOutputDevice(nullptr); + if (mpSpellCheckCxt) + mpSpellCheckCxt->reset(); + mpSpellCheckCxt.reset(); + vcl::Window::dispose(); } @@ -5497,191 +5501,10 @@ void ScGridWindow::DrawLayerCreated() ImpCreateOverlayObjects(); } -namespace { - -struct SpellCheckStatus -{ - bool mbModified; - - SpellCheckStatus() : mbModified(false) {}; - - DECL_LINK( EventHdl, EditStatus&, void ); -}; - -IMPL_LINK(SpellCheckStatus, EventHdl, EditStatus&, rStatus, void) -{ - EditStatusFlags nStatus = rStatus.GetStatusWord(); - if (nStatus & EditStatusFlags::WRONGWORDCHANGED) - mbModified = true; -} - -} - -bool ScGridWindow::ContinueOnlineSpelling() -{ - if (!mpSpellCheckCxt) - return false; - - if (!mpSpellCheckCxt->maPos.isValid()) - return false; - - ScDocument& rDoc = mrViewData.GetDocument(); - ScDPCollection* pDPs = nullptr; - if (rDoc.HasPivotTable()) - pDPs = rDoc.GetDPCollection(); - - SCTAB nTab = mrViewData.GetTabNo(); - SpellCheckStatus aStatus; - - ScHorizontalCellIterator aIter( - rDoc, nTab, maVisibleRange.mnCol1, mpSpellCheckCxt->maPos.mnRow, maVisibleRange.mnCol2, maVisibleRange.mnRow2); - - ScRangeList aPivotRanges = pDPs ? pDPs->GetAllTableRanges(nTab) : ScRangeList(); - - SCCOL nCol; - SCROW nRow; - ScRefCellValue* pCell = aIter.GetNext(nCol, nRow); - SCROW nEndRow = 0; - bool bHidden = pCell && rDoc.RowHidden(nRow, nTab, nullptr, &nEndRow); - bool bSkip = pCell && (nRow < mpSpellCheckCxt->maPos.mnRow || bHidden); - while (bSkip) - { - pCell = aIter.GetNext(nCol, nRow); - if (pCell && nRow > nEndRow) - { - bHidden = rDoc.RowHidden(nRow, nTab, nullptr, &nEndRow); - } - bSkip = pCell && (nRow < mpSpellCheckCxt->maPos.mnRow || bHidden); - } - - SCCOL nEndCol = 0; - bHidden = pCell && rDoc.ColHidden(nCol, nTab, nullptr, &nEndCol); - bSkip = pCell && (nCol < mpSpellCheckCxt->maPos.mnCol || bHidden); - while (bSkip) - { - pCell = aIter.GetNext(nCol, nRow); - if (pCell && nCol > nEndCol) - { - bHidden = rDoc.ColHidden(nCol, nTab, nullptr, &nEndCol); - } - bSkip = pCell && (nCol < mpSpellCheckCxt->maPos.mnCol || bHidden); - } - - std::unique_ptr<ScTabEditEngine> pEngine; - - // Check only up to 256 cells at a time. - size_t nTotalCellCount = 0; - size_t nTextCellCount = 0; - bool bSpellCheckPerformed = false; - - while (pCell) - { - ++nTotalCellCount; - - if (aPivotRanges.In(ScAddress(nCol, nRow, nTab))) - { - // Don't spell check within pivot tables. - if (nTotalCellCount >= 255) - break; - - pCell = aIter.GetNext(nCol, nRow); - continue; - } - - CellType eType = pCell->meType; - if (eType == CELLTYPE_STRING || eType == CELLTYPE_EDIT) - { - ++nTextCellCount; - - // NB: For spell-checking, we currently only use the primary - // language; not CJK nor CTL. - const ScPatternAttr* pPattern = rDoc.GetPattern(nCol, nRow, nTab); - LanguageType nCellLang = pPattern->GetItem(ATTR_FONT_LANGUAGE).GetValue(); - - if (nCellLang == LANGUAGE_SYSTEM) - nCellLang = Application::GetSettings().GetLanguageTag().getLanguageType(); // never use SYSTEM for spelling - - if (nCellLang == LANGUAGE_NONE) - { - // No need to spell check this cell. - pCell = aIter.GetNext(nCol, nRow); - continue; - } - - if (!pEngine) - { - // ScTabEditEngine is needed - // because MapMode must be set for some old documents - pEngine.reset(new ScTabEditEngine(&rDoc)); - pEngine->SetControlWord( - pEngine->GetControlWord() | (EEControlBits::ONLINESPELLING | EEControlBits::ALLOWBIGOBJS)); - pEngine->SetStatusEventHdl(LINK(&aStatus, SpellCheckStatus, EventHdl)); - // Delimiters here like in inputhdl.cxx !!! - pEngine->SetWordDelimiters( - ScEditUtil::ModifyDelimiters(pEngine->GetWordDelimiters())); - - uno::Reference<linguistic2::XSpellChecker1> xXSpellChecker1(LinguMgr::GetSpellChecker()); - pEngine->SetSpeller(xXSpellChecker1); - pEngine->SetDefaultLanguage(ScGlobal::GetEditDefaultLanguage()); - } - - pEngine->SetDefaultItem(SvxLanguageItem(nCellLang, EE_CHAR_LANGUAGE)); - - if (eType == CELLTYPE_STRING) - pEngine->SetTextCurrentDefaults(pCell->mpString->getString()); - else - pEngine->SetTextCurrentDefaults(*pCell->mpEditText); - - aStatus.mbModified = false; - pEngine->CompleteOnlineSpelling(); - if (aStatus.mbModified) - { - std::vector<editeng::MisspellRanges> aRanges; - pEngine->GetAllMisspellRanges(aRanges); - if (!aRanges.empty()) - { - sc::SpellCheckContext::CellPos aPos(nCol, nRow); - mpSpellCheckCxt->maMisspellCells.emplace(aPos, aRanges); - } - - // Broadcast for re-paint. - ScPaintHint aHint(ScRange(nCol, nRow, nTab), PaintPartFlags::Grid); - aHint.SetPrintFlag(false); - rDoc.GetDocumentShell()->Broadcast(aHint); - } - - bSpellCheckPerformed = true; - } - - if (nTotalCellCount >= 255 || nTextCellCount >= 1) - break; - - pCell = aIter.GetNext(nCol, nRow); - } - - if (pCell) - // Move to the next cell position for the next iteration. - pCell = aIter.GetNext(nCol, nRow); - - if (pCell) - { - // This will become the first cell position for the next time. - mpSpellCheckCxt->maPos.mnCol = nCol; - mpSpellCheckCxt->maPos.mnRow = nRow; - } - else - { - // No more cells to spell check. - mpSpellCheckCxt->maPos.setInvalid(); - } - - return bSpellCheckPerformed; -} - void ScGridWindow::EnableAutoSpell( bool bEnable ) { if (bEnable) - mpSpellCheckCxt.reset(new sc::SpellCheckContext); + mpSpellCheckCxt.reset(new sc::SpellCheckContext(&mrViewData.GetDocument(), mrViewData.GetTabNo())); else mpSpellCheckCxt.reset(); } @@ -5689,11 +5512,13 @@ void ScGridWindow::EnableAutoSpell( bool bEnable ) void ScGridWindow::ResetAutoSpell() { if (mpSpellCheckCxt) - { mpSpellCheckCxt->reset(); - mpSpellCheckCxt->maPos.mnCol = maVisibleRange.mnCol1; - mpSpellCheckCxt->maPos.mnRow = maVisibleRange.mnRow1; - } +} + +void ScGridWindow::ResetAutoSpellForContentChange() +{ + if (mpSpellCheckCxt) + mpSpellCheckCxt->resetForContentChange(); } void ScGridWindow::SetAutoSpellData( SCCOL nPosX, SCROW nPosY, const std::vector<editeng::MisspellRanges>* pRanges ) @@ -5701,9 +5526,6 @@ void ScGridWindow::SetAutoSpellData( SCCOL nPosX, SCROW nPosY, const std::vector if (!mpSpellCheckCxt) return; - if (!maVisibleRange.isInside(nPosX, nPosY)) - return; - mpSpellCheckCxt->setMisspellRanges(nPosX, nPosY, pRanges); } diff --git a/sc/source/ui/view/gridwin2.cxx b/sc/source/ui/view/gridwin2.cxx index 0d1ef7ea71c9..efb952dc7721 100644 --- a/sc/source/ui/view/gridwin2.cxx +++ b/sc/source/ui/view/gridwin2.cxx @@ -638,11 +638,7 @@ bool ScGridWindow::UpdateVisibleRange() } // Store the current visible range. - bool bChanged = maVisibleRange.set(nPosX, nPosY, nXRight, nYBottom); - if (bChanged) - ResetAutoSpell(); - - return bChanged; + return maVisibleRange.set(nPosX, nPosY, nXRight, nYBottom); } void ScGridWindow::DPMouseMove( const MouseEvent& rMEvt ) diff --git a/sc/source/ui/view/spellcheckcontext.cxx b/sc/source/ui/view/spellcheckcontext.cxx index 867dc26f5738..6ff81468c5b1 100644 --- a/sc/source/ui/view/spellcheckcontext.cxx +++ b/sc/source/ui/view/spellcheckcontext.cxx @@ -8,88 +8,366 @@ */ #include <spellcheckcontext.hxx> +#include <i18nlangtag/languagetag.hxx> +#include <svl/sharedstring.hxx> +#include <editeng/eeitem.hxx> +#include <editeng/langitem.hxx> +#include <editeng/editeng.hxx> +#include <editeng/unolingu.hxx> + +#include <scitems.hxx> +#include <attarray.hxx> +#include <document.hxx> +#include <cellvalue.hxx> +#include <editutil.hxx> +#include <dpobject.hxx> + +#include <com/sun/star/linguistic2/XSpellChecker1.hpp> + #include <boost/functional/hash.hpp> -namespace sc { +#include <unordered_map> + +using namespace css; -size_t SpellCheckContext::CellPos::Hash::operator() (const CellPos& rPos) const +using sc::SpellCheckContext; + +class SpellCheckContext::SpellCheckCache { - std::size_t seed = 0; - boost::hash_combine(seed, rPos.mnCol); - boost::hash_combine(seed, rPos.mnRow); - return seed; -} + struct CellPos + { + struct Hash + { + size_t operator() (const CellPos& rPos) const + { + std::size_t seed = 0; + boost::hash_combine(seed, rPos.mnCol); + boost::hash_combine(seed, rPos.mnRow); + return seed; + } + }; + + SCCOL mnCol; + SCROW mnRow; + + CellPos(SCCOL nCol, SCROW nRow) : mnCol(nCol), mnRow(nRow) {} + + bool operator== (const CellPos& r) const + { + return mnCol == r.mnCol && mnRow == r.mnRow; + } + + }; + + typedef std::vector<editeng::MisspellRanges> MisspellType; + typedef std::unordered_map<CellPos, std::unique_ptr<MisspellType>, CellPos::Hash> CellMapType; + typedef std::unordered_map<const rtl_uString*, std::unique_ptr<MisspellType>> SharedStringMapType; + typedef std::unordered_map<CellPos, LanguageType, CellPos::Hash> CellLangMapType; + + SharedStringMapType maStringMisspells; + CellMapType maEditTextMisspells; + CellLangMapType maCellLanguages; + LanguageType meDefCellLanguage; + +public: + + SpellCheckCache(LanguageType eDefaultCellLanguage) : meDefCellLanguage(eDefaultCellLanguage) + { + } + + bool query(SCCOL nCol, SCROW nRow, const ScRefCellValue& rCell, MisspellType*& rpRanges) const + { + CellType eType = rCell.meType; + if (eType == CELLTYPE_STRING) + { + SharedStringMapType::const_iterator it = maStringMisspells.find(rCell.mpString->getData()); + if (it == maStringMisspells.end()) + return false; // Not available + + rpRanges = it->second.get(); + return true; + } + + if (eType == CELLTYPE_EDIT) + { + CellMapType::const_iterator it = maEditTextMisspells.find(CellPos(nCol, nRow)); + if (it == maEditTextMisspells.end()) + return false; // Not available + + rpRanges = it->second.get(); + return true; + } + + rpRanges = nullptr; + return true; + } + + void set(SCCOL nCol, SCROW nRow, const ScRefCellValue& rCell, std::unique_ptr<MisspellType> pRanges) + { + CellType eType = rCell.meType; + if (eType == CELLTYPE_STRING) + maStringMisspells.insert_or_assign(rCell.mpString->getData(), std::move(pRanges)); + else if (eType == CELLTYPE_EDIT) + maEditTextMisspells.insert_or_assign(CellPos(nCol, nRow), std::move(pRanges)); + } -SpellCheckContext::CellPos::CellPos() : mnCol(0), mnRow(0) {} -SpellCheckContext::CellPos::CellPos(SCCOL nCol, SCROW nRow) : mnCol(nCol), mnRow(nRow) {} + LanguageType getLanguage(SCCOL nCol, SCROW nRow) const + { + CellLangMapType::const_iterator it = maCellLanguages.find(CellPos(nCol, nRow)); + if (it == maCellLanguages.end()) + return meDefCellLanguage; -void SpellCheckContext::CellPos::setInvalid() + return it->second; + } + + void setLanguage(LanguageType eCellLang, SCCOL nCol, SCROW nRow) + { + if (eCellLang == meDefCellLanguage) + maCellLanguages.erase(CellPos(nCol, nRow)); + else + maCellLanguages.insert_or_assign(CellPos(nCol, nRow), eCellLang); + } + + void clear(LanguageType eDefaultCellLanguage) + { + maStringMisspells.clear(); + maEditTextMisspells.clear(); + maCellLanguages.clear(); + meDefCellLanguage = eDefaultCellLanguage; + } + + void clearEditTextMap() + { + maEditTextMisspells.clear(); + } +}; + +struct SpellCheckContext::SpellCheckStatus { - mnCol = -1; - mnRow = -1; -} + bool mbModified; -bool SpellCheckContext::CellPos::isValid() const + SpellCheckStatus() : mbModified(false) {}; + + DECL_LINK( EventHdl, EditStatus&, void ); +}; + +IMPL_LINK(SpellCheckContext::SpellCheckStatus, EventHdl, EditStatus&, rStatus, void) { - return mnCol >= 0 && mnRow >= 0; + EditStatusFlags nStatus = rStatus.GetStatusWord(); + if (nStatus & EditStatusFlags::WRONGWORDCHANGED) + mbModified = true; } -void SpellCheckContext::CellPos::reset() +struct SpellCheckContext::SpellCheckResult { - mnCol = 0; - mnRow = 0; -} + SCCOL mnCol; + SCROW mnRow; + const std::vector<editeng::MisspellRanges>* pRanges; + + SpellCheckResult() : mnCol(-1), mnRow(-1), pRanges(nullptr) {} + + void set(SCCOL nCol, SCROW nRow, const std::vector<editeng::MisspellRanges>* pMisspells) + { + mnCol = nCol; + mnRow = nRow; + pRanges = pMisspells; + } + + const std::vector<editeng::MisspellRanges>* query(SCCOL nCol, SCROW nRow) const + { + assert(mnCol == nCol); + assert(mnRow == nRow); + (void)nCol; + (void)nRow; + return pRanges; + } + + void clear() + { + mnCol = -1; + mnRow = -1; + pRanges = nullptr; + } +}; -bool SpellCheckContext::CellPos::operator== (const CellPos& r) const +SpellCheckContext::SpellCheckContext(ScDocument* pDocument, SCTAB nTab) : + pDoc(pDocument), + mnTab(nTab), + meLanguage(ScGlobal::GetEditDefaultLanguage()) { - return mnCol == r.mnCol && mnRow == r.mnRow; + // defer init of engine and cache till the first query/set } -SpellCheckContext::SpellCheckContext() +SpellCheckContext::~SpellCheckContext() { } -bool SpellCheckContext::isMisspelled( SCCOL nCol, SCROW nRow ) const +bool SpellCheckContext::isMisspelled(SCCOL nCol, SCROW nRow) const { - return maMisspellCells.count(CellPos(nCol, nRow)) > 0; + const_cast<SpellCheckContext*>(this)->ensureResults(nCol, nRow); + return mpResult->query(nCol, nRow); } const std::vector<editeng::MisspellRanges>* SpellCheckContext::getMisspellRanges( SCCOL nCol, SCROW nRow ) const { - CellMapType::const_iterator it = maMisspellCells.find(CellPos(nCol,nRow)); - if (it == maMisspellCells.end()) - return nullptr; - - return &it->second; + const_cast<SpellCheckContext*>(this)->ensureResults(nCol, nRow); + return mpResult->query(nCol, nRow); } void SpellCheckContext::setMisspellRanges( SCCOL nCol, SCROW nRow, const std::vector<editeng::MisspellRanges>* pRanges ) { - CellPos aPos(nCol, nRow); - CellMapType::iterator it = maMisspellCells.find(aPos); + if (!mpEngine || !mpCache) + reset(); + + ScRefCellValue aCell(*pDoc, ScAddress(nCol, nRow, mnTab)); + CellType eType = aCell.meType; + + if (eType != CELLTYPE_STRING && eType != CELLTYPE_EDIT) + return; + + typedef std::vector<editeng::MisspellRanges> MisspellType; + std::unique_ptr<MisspellType> pMisspells(pRanges ? new MisspellType(*pRanges) : nullptr); + mpCache->set(nCol, nRow, aCell, std::move(pMisspells)); +} + +void SpellCheckContext::reset() +{ + meLanguage = ScGlobal::GetEditDefaultLanguage(); + resetCache(); + mpEngine.reset(); + mpStatus.reset(); +} - if (pRanges) +void SpellCheckContext::resetForContentChange() +{ + resetCache(true /* bContentChangeOnly */); +} + +void SpellCheckContext::ensureResults(SCCOL nCol, SCROW nRow) +{ + if (!mpEngine || !mpCache || + ScGlobal::GetEditDefaultLanguage() != meLanguage) { - if (it == maMisspellCells.end()) - maMisspellCells.emplace(aPos, *pRanges); - else - it->second = *pRanges; + reset(); + setup(); + } + + // perhaps compute the pivot rangelist once in some pivot-table change handler ? + if (pDoc->HasPivotTable()) + { + if (ScDPCollection* pDPs = pDoc->GetDPCollection()) + { + ScRangeList aPivotRanges = pDPs->GetAllTableRanges(mnTab); + if (aPivotRanges.In(ScAddress(nCol, nRow, mnTab))) // Don't spell check within pivot tables + { + mpResult->set(nCol, nRow, nullptr); + return; + } + } + } + + ScRefCellValue aCell(*pDoc, ScAddress(nCol, nRow, mnTab)); + CellType eType = aCell.meType; + + if (eType != CELLTYPE_STRING && eType != CELLTYPE_EDIT) + { + // No spell-check required. + mpResult->set(nCol, nRow, nullptr); + return; } + + + // Cell content is either shared-string or EditTextObject + + // For spell-checking, we currently only use the primary + // language; not CJK nor CTL. + const ScPatternAttr* pPattern = pDoc->GetPattern(nCol, nRow, mnTab); + LanguageType eCellLang = pPattern->GetItem(ATTR_FONT_LANGUAGE).GetValue(); + + if (eCellLang == LANGUAGE_SYSTEM) + eCellLang = meLanguage; // never use SYSTEM for spelling + + if (eCellLang == LANGUAGE_NONE) + { + mpResult->set(nCol, nRow, nullptr); // No need to spell check this cell. + return; + } + + typedef std::vector<editeng::MisspellRanges> MisspellType; + + LanguageType eCachedCellLang = mpCache->getLanguage(nCol, nRow); + + if (eCellLang != eCachedCellLang) + mpCache->setLanguage(eCellLang, nCol, nRow); + else { - if (it != maMisspellCells.end()) - maMisspellCells.erase(it); + MisspellType* pRanges = nullptr; + bool bFound = mpCache->query(nCol, nRow, aCell, pRanges); + if (bFound) + { + // Cache hit. + mpResult->set(nCol, nRow, pRanges); + return; + } } + + // Cache miss, the cell needs spell-check.. + mpEngine->SetDefaultItem(SvxLanguageItem(eCellLang, EE_CHAR_LANGUAGE)); + if (eType == CELLTYPE_STRING) + mpEngine->SetText(aCell.mpString->getString()); + else + mpEngine->SetText(*aCell.mpEditText); + + mpStatus->mbModified = false; + mpEngine->CompleteOnlineSpelling(); + std::unique_ptr<MisspellType> pRanges; + if (mpStatus->mbModified) + { + pRanges.reset(new MisspellType); + mpEngine->GetAllMisspellRanges(*pRanges); + + if (pRanges->empty()) + pRanges.reset(nullptr); + } + // else : No change in status for EditStatusFlags::WRONGWORDCHANGED => no spell errors (which is the default status). + + mpResult->set(nCol, nRow, pRanges.get()); + mpCache->set(nCol, nRow, aCell, std::move(pRanges)); } -void SpellCheckContext::reset() +void SpellCheckContext::resetCache(bool bContentChangeOnly) { - maPos.reset(); - maMisspellCells.clear(); + if (!mpResult) + mpResult.reset(new SpellCheckResult()); + else + mpResult->clear(); + + if (!mpCache) + mpCache.reset(new SpellCheckCache(meLanguage)); + else if (bContentChangeOnly) + mpCache->clearEditTextMap(); + else + mpCache->clear(meLanguage); } +void SpellCheckContext::setup() +{ + mpEngine.reset(new ScTabEditEngine(pDoc)); + mpStatus.reset(new SpellCheckStatus()); + + mpEngine->SetControlWord( + mpEngine->GetControlWord() | (EEControlBits::ONLINESPELLING | EEControlBits::ALLOWBIGOBJS)); + mpEngine->SetStatusEventHdl(LINK(mpStatus.get(), SpellCheckStatus, EventHdl)); + // Delimiters here like in inputhdl.cxx !!! + mpEngine->SetWordDelimiters( + ScEditUtil::ModifyDelimiters(mpEngine->GetWordDelimiters())); + + uno::Reference<linguistic2::XSpellChecker1> xXSpellChecker1(LinguMgr::GetSpellChecker()); + mpEngine->SetSpeller(xXSpellChecker1); + mpEngine->SetDefaultLanguage(meLanguage); } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/view/tabview.cxx b/sc/source/ui/view/tabview.cxx index bcf2831995c0..2f4a407aca5c 100644 --- a/sc/source/ui/view/tabview.cxx +++ b/sc/source/ui/view/tabview.cxx @@ -2232,40 +2232,36 @@ void ScTabView::EnableRefInput(bool bFlag) p->EnableInput(bFlag, false); } -bool ScTabView::ContinueOnlineSpelling() +void ScTabView::EnableAutoSpell( bool bEnable ) { - bool bChanged = false; for (VclPtr<ScGridWindow> & pWin : pGridWin) { - if (!pWin || !pWin->IsVisible()) + if (!pWin) continue; - if (pWin->ContinueOnlineSpelling()) - bChanged = true; + pWin->EnableAutoSpell(bEnable); } - - return bChanged; } -void ScTabView::EnableAutoSpell( bool bEnable ) +void ScTabView::ResetAutoSpell() { for (VclPtr<ScGridWindow> & pWin : pGridWin) { if (!pWin) continue; - pWin->EnableAutoSpell(bEnable); + pWin->ResetAutoSpell(); } } -void ScTabView::ResetAutoSpell() +void ScTabView::ResetAutoSpellForContentChange() { for (VclPtr<ScGridWindow> & pWin : pGridWin) { if (!pWin) continue; - pWin->ResetAutoSpell(); + pWin->ResetAutoSpellForContentChange(); } } diff --git a/sc/source/ui/view/viewfun2.cxx b/sc/source/ui/view/viewfun2.cxx index 572b62cf4036..9bad80bb01d0 100644 --- a/sc/source/ui/view/viewfun2.cxx +++ b/sc/source/ui/view/viewfun2.cxx @@ -1477,6 +1477,10 @@ void ScViewFunc::FillAuto( FillDir eDir, SCCOL nStartCol, SCROW nStartRow, void ScViewFunc::CopyAutoSpellData( FillDir eDir, SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow, sal_uLong nCount ) { + const ScDocument* pDoc = &GetViewData().GetDocument(); + SCTAB nTab = GetViewData().GetTabNo(); + CellType eCellType; + ScGridWindow* pWin = GetActiveWin(); if ( pWin->InsideVisibleRange(nStartCol, nStartRow) && pWin->InsideVisibleRange(nEndCol, nEndRow) ) { @@ -1487,6 +1491,10 @@ void ScViewFunc::CopyAutoSpellData( FillDir eDir, SCCOL nStartCol, SCROW nStartR case FILL_TO_BOTTOM: for ( SCCOL nColItr = nStartCol; nColItr <= nEndCol; ++nColItr ) { + pDoc->GetCellType(nColItr, nStartRow, nTab, eCellType); // We need this optimization only for EditTextObject source cells + if (eCellType != CELLTYPE_EDIT) + continue; + const std::vector<editeng::MisspellRanges>* pRanges = pWin->GetAutoSpellData(nColItr, nStartRow); if ( !pRanges ) continue; @@ -1497,6 +1505,10 @@ void ScViewFunc::CopyAutoSpellData( FillDir eDir, SCCOL nStartCol, SCROW nStartR case FILL_TO_TOP: for ( SCCOL nColItr = nStartCol; nColItr <= nEndCol; ++nColItr ) { + pDoc->GetCellType(nColItr, nEndRow, nTab, eCellType); // We need this optimization only for EditTextObject source cells + if (eCellType != CELLTYPE_EDIT) + continue; + const std::vector<editeng::MisspellRanges>* pRanges = pWin->GetAutoSpellData(nColItr, nEndRow); if ( !pRanges ) continue; @@ -1507,6 +1519,10 @@ void ScViewFunc::CopyAutoSpellData( FillDir eDir, SCCOL nStartCol, SCROW nStartR case FILL_TO_RIGHT: for ( SCROW nRowItr = nStartRow; nRowItr <= nEndRow; ++nRowItr ) { + pDoc->GetCellType(nStartCol, nRowItr, nTab, eCellType); // We need this optimization only for EditTextObject source cells + if (eCellType != CELLTYPE_EDIT) + continue; + const std::vector<editeng::MisspellRanges>* pRanges = pWin->GetAutoSpellData(nStartCol, nRowItr); if ( !pRanges ) continue; @@ -1517,6 +1533,10 @@ void ScViewFunc::CopyAutoSpellData( FillDir eDir, SCCOL nStartCol, SCROW nStartR case FILL_TO_LEFT: for ( SCROW nRowItr = nStartRow; nRowItr <= nEndRow; ++nRowItr ) { + pDoc->GetCellType(nEndCol, nRowItr, nTab, eCellType); // We need this optimization only for EditTextObject source cells + if (eCellType != CELLTYPE_EDIT) + continue; + const std::vector<editeng::MisspellRanges>* pRanges = pWin->GetAutoSpellData(nEndCol, nRowItr); if ( !pRanges ) continue; @@ -1533,11 +1553,19 @@ void ScViewFunc::CopyAutoSpellData( FillDir eDir, SCCOL nStartCol, SCROW nStartR SCCOL nColRepeatSize = nEndCol - nStartCol + 1; SCROW nTillRow = 0; SCCOL nTillCol = 0; - std::vector<std::vector<MisspellRangesType>> aSourceSpellRanges(nRowRepeatSize, std::vector<MisspellRangesType>(nColRepeatSize)); + std::vector<std::vector<MisspellRangesType>> aSourceSpellRanges(nRowRepeatSize, std::vector<MisspellRangesType>(nColRepeatSize, nullptr)); for ( SCROW nRowIdx = 0; nRowIdx < nRowRepeatSize; ++nRowIdx ) + { for ( SCCOL nColIdx = 0; nColIdx < nColRepeatSize; ++nColIdx ) + { + pDoc->GetCellType(nStartCol + nColIdx, nStartRow + nRowIdx, nTab, eCellType); // We need this optimization only for EditTextObject source cells + if (eCellType != CELLTYPE_EDIT) + continue; + aSourceSpellRanges[nRowIdx][nColIdx] = pWin->GetAutoSpellData( nStartCol + nColIdx, nStartRow + nRowIdx ); + } + } switch( eDir ) { @@ -1603,7 +1631,7 @@ void ScViewFunc::CopyAutoSpellData( FillDir eDir, SCCOL nStartCol, SCROW nStartR } } else - pWin->ResetAutoSpell(); + pWin->ResetAutoSpellForContentChange(); } diff --git a/sc/source/ui/view/viewfun3.cxx b/sc/source/ui/view/viewfun3.cxx index 50b73566b1a7..5354391fcaa1 100644 --- a/sc/source/ui/view/viewfun3.cxx +++ b/sc/source/ui/view/viewfun3.cxx @@ -955,6 +955,9 @@ bool ScViewFunc::PasteFromClip( InsertDeleteFlags nFlags, ScDocument* pClipDoc, ScDrawLayer::SetGlobalDrawPersist(nullptr); } + // TODO: position this call better for performance. + ResetAutoSpellForContentChange(); + SCCOL nStartCol; SCROW nStartRow; SCTAB nStartTab; @@ -1446,7 +1449,6 @@ bool ScViewFunc::PasteFromClip( InsertDeleteFlags nFlags, ScDocument* pClipDoc, nPaint, nExtFlags); // AdjustBlockHeight has already been called above - ResetAutoSpell(); aModificator.SetDocumentModified(); PostPasteFromClip(aUserRange, rMark); @@ -1544,6 +1546,9 @@ bool ScViewFunc::PasteMultiRangesFromClip( return false; } + // TODO: position this call better for performance. + ResetAutoSpellForContentChange(); + bool bRowInfo = ( aMarkedRange.aStart.Col()==0 && aMarkedRange.aEnd.Col()==pClipDoc->MaxCol() ); ScDocumentUniquePtr pUndoDoc; if (rDoc.IsUndoEnabled()) @@ -1624,7 +1629,6 @@ bool ScViewFunc::PasteMultiRangesFromClip( pUndoMgr->LeaveListAction(); } - ResetAutoSpell(); aModificator.SetDocumentModified(); PostPasteFromClip(aMarkedRange, aMark); return true; @@ -1696,6 +1700,9 @@ bool ScViewFunc::PasteFromClipToMultiRanges( return false; } + // TODO: position this call better for performance. + ResetAutoSpellForContentChange(); + ScDocumentUniquePtr pUndoDoc; if (rDoc.IsUndoEnabled()) { @@ -1784,7 +1791,6 @@ bool ScViewFunc::PasteFromClipToMultiRanges( pUndoMgr->LeaveListAction(); } - ResetAutoSpell(); aModificator.SetDocumentModified(); PostPasteFromClip(aRanges, aMark); @@ -1828,6 +1834,8 @@ bool ScViewFunc::MoveBlockTo( const ScRange& rSource, const ScAddress& rDestPos, ScDocShell* pDocSh = GetViewData().GetDocShell(); HideAllCursors(); + ResetAutoSpellForContentChange(); + bool bSuccess = true; SCTAB nDestTab = rDestPos.Tab(); const ScMarkData& rMark = GetViewData().GetMarkData(); @@ -1899,7 +1907,6 @@ bool ScViewFunc::MoveBlockTo( const ScRange& rSource, const ScAddress& rDestPos, pDocSh->UpdateOle(GetViewData()); SelectionChanged(); - ResetAutoSpell(); } return bSuccess; } diff --git a/sc/source/ui/view/viewfunc.cxx b/sc/source/ui/view/viewfunc.cxx index c22effdaa37f..d0484beb37c9 100644 --- a/sc/source/ui/view/viewfunc.cxx +++ b/sc/source/ui/view/viewfunc.cxx @@ -1607,12 +1607,12 @@ bool ScViewFunc::InsertCells( InsCellCmd eCmd, bool bRecord, bool bPartOfPaste ) bool bSuccess = pDocSh->GetDocFunc().InsertCells( aRange, &rMark, eCmd, bRecord, false, bPartOfPaste ); if (bSuccess) { + ResetAutoSpellForContentChange(); bool bInsertCols = ( eCmd == INS_INSCOLS_BEFORE || eCmd == INS_INSCOLS_AFTER); bool bInsertRows = ( eCmd == INS_INSROWS_BEFORE || eCmd == INS_INSROWS_AFTER ); pDocSh->UpdateOle(GetViewData()); CellContentChanged(); - ResetAutoSpell(); if ( bInsertCols || bInsertRows ) { @@ -1684,9 +1684,9 @@ void ScViewFunc::DeleteCells( DelCellCmd eCmd ) pDocSh->GetDocFunc().DeleteCells( aRange, &rMark, eCmd, false ); } + ResetAutoSpellForContentChange(); pDocSh->UpdateOle(GetViewData()); CellContentChanged(); - ResetAutoSpell(); if ( eCmd == DelCellCmd::Rows || eCmd == DelCellCmd::Cols ) { @@ -1831,6 +1831,8 @@ void ScViewFunc::DeleteMulti( bool bRows ) WaitObject aWait( GetFrameWin() ); // important for TrackFormulas in UpdateReference + ResetAutoSpellForContentChange(); + ScDocumentUniquePtr pUndoDoc; std::unique_ptr<ScRefUndoData> pUndoData; if (bRecord) @@ -1914,7 +1916,6 @@ void ScViewFunc::DeleteMulti( bool bRows ) } } - ResetAutoSpell(); aModificator.SetDocumentModified(); CellContentChanged(); _______________________________________________ Libreoffice-commits mailing list libreoffice-comm...@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/libreoffice-commits