summaryrefslogtreecommitdiffstats
path: root/sc/source/ui/view/spellcheckcontext.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'sc/source/ui/view/spellcheckcontext.cxx')
-rw-r--r--sc/source/ui/view/spellcheckcontext.cxx387
1 files changed, 387 insertions, 0 deletions
diff --git a/sc/source/ui/view/spellcheckcontext.cxx b/sc/source/ui/view/spellcheckcontext.cxx
new file mode 100644
index 0000000000..b18483aa88
--- /dev/null
+++ b/sc/source/ui/view/spellcheckcontext.cxx
@@ -0,0 +1,387 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <spellcheckcontext.hxx>
+#include <svl/sharedstring.hxx>
+#include <editeng/eeitem.hxx>
+#include <editeng/langitem.hxx>
+#include <editeng/unolingu.hxx>
+
+#include <scitems.hxx>
+#include <document.hxx>
+#include <cellvalue.hxx>
+#include <editutil.hxx>
+#include <dpobject.hxx>
+
+#include <com/sun/star/linguistic2/XSpellChecker1.hpp>
+
+#include <o3tl/hash_combine.hxx>
+
+#include <unordered_map>
+
+using namespace css;
+
+using sc::SpellCheckContext;
+
+class SpellCheckContext::SpellCheckCache
+{
+ struct CellPos
+ {
+ struct Hash
+ {
+ size_t operator() (const CellPos& rPos) const
+ {
+ std::size_t seed = 0;
+ o3tl::hash_combine(seed, rPos.mnCol);
+ o3tl::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.getType();
+ if (eType == CELLTYPE_STRING)
+ {
+ SharedStringMapType::const_iterator it = maStringMisspells.find(rCell.getSharedString()->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.getType();
+ if (eType == CELLTYPE_STRING)
+ maStringMisspells.insert_or_assign(rCell.getSharedString()->getData(), std::move(pRanges));
+ else if (eType == CELLTYPE_EDIT)
+ maEditTextMisspells.insert_or_assign(CellPos(nCol, nRow), std::move(pRanges));
+ }
+
+ LanguageType getLanguage(SCCOL nCol, SCROW nRow) const
+ {
+ CellLangMapType::const_iterator it = maCellLanguages.find(CellPos(nCol, nRow));
+ if (it == maCellLanguages.end())
+ return meDefCellLanguage;
+
+ 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
+{
+ bool mbModified;
+
+ SpellCheckStatus() : mbModified(false) {};
+
+ DECL_LINK( EventHdl, EditStatus&, void );
+};
+
+IMPL_LINK(SpellCheckContext::SpellCheckStatus, EventHdl, EditStatus&, rStatus, void)
+{
+ EditStatusFlags nStatus = rStatus.GetStatusWord();
+ if (nStatus & EditStatusFlags::WRONGWORDCHANGED)
+ mbModified = true;
+}
+
+struct SpellCheckContext::SpellCheckResult
+{
+ 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;
+ }
+};
+
+SpellCheckContext::SpellCheckContext(ScDocument* pDocument, SCTAB nTab) :
+ pDoc(pDocument),
+ mnTab(nTab),
+ meLanguage(ScGlobal::GetEditDefaultLanguage())
+{
+ // defer init of engine and cache till the first query/set
+}
+
+SpellCheckContext::~SpellCheckContext()
+{
+}
+
+void SpellCheckContext::dispose()
+{
+ mpEngine.reset();
+ mpCache.reset();
+ pDoc = nullptr;
+}
+
+void SpellCheckContext::setTabNo(SCTAB nTab)
+{
+ if (mnTab == nTab)
+ return;
+ mnTab = nTab;
+ reset();
+}
+
+bool SpellCheckContext::isMisspelled(SCCOL nCol, SCROW nRow) const
+{
+ const_cast<SpellCheckContext*>(this)->ensureResults(nCol, nRow);
+ return mpResult->query(nCol, nRow);
+}
+
+const std::vector<editeng::MisspellRanges>* SpellCheckContext::getMisspellRanges(
+ SCCOL nCol, SCROW nRow ) const
+{
+ 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 )
+{
+ if (!mpEngine || !mpCache)
+ reset();
+
+ ScRefCellValue aCell(*pDoc, ScAddress(nCol, nRow, mnTab));
+ CellType eType = aCell.getType();
+
+ 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();
+}
+
+void SpellCheckContext::resetForContentChange()
+{
+ resetCache(true /* bContentChangeOnly */);
+}
+
+void SpellCheckContext::ensureResults(SCCOL nCol, SCROW nRow)
+{
+ if (!mpEngine || !mpCache ||
+ ScGlobal::GetEditDefaultLanguage() != meLanguage)
+ {
+ 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.Contains(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.getType();
+
+ 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
+ {
+ 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..
+ if (eType == CELLTYPE_STRING)
+ mpEngine->SetText(aCell.getSharedString()->getString());
+ else
+ mpEngine->SetText(*aCell.getEditText());
+
+ // it has to happen after we set text
+ mpEngine->SetDefaultItem(SvxLanguageItem(eCellLang, EE_CHAR_LANGUAGE));
+
+ 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::resetCache(bool bContentChangeOnly)
+{
+ 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: */