/* -*- 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 <vcl/dropcache.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 : public CacheOwner
{
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;
}
};
struct LangSharedString
{
struct Hash
{
size_t operator() (const LangSharedString& rKey) const
{
std::size_t seed = 0;
o3tl::hash_combine(seed, rKey.meLang.get());
o3tl::hash_combine(seed, rKey.mpString);
return seed;
}
};
LanguageType meLang;
const rtl_uString* mpString;
LangSharedString(LanguageType eLang, const ScRefCellValue& rCell)
: meLang(eLang)
, mpString(rCell.getSharedString()->getData())
{
}
bool operator== (const LangSharedString& r) const
{
return meLang == r.meLang && mpString == r.mpString;
}
};
#if defined __cpp_lib_memory_resource
typedef std::pmr::unordered_map<CellPos, std::unique_ptr<MisspellRangesVec>, CellPos::Hash> CellMapType;
typedef std::pmr::unordered_map<LangSharedString, std::unique_ptr<MisspellRangesVec>, LangSharedString::Hash> SharedStringMapType;
#else
typedef std::unordered_map<CellPos, std::unique_ptr<MisspellRangesVec>, CellPos::Hash> CellMapType;
typedef std::unordered_map<LangSharedString, std::unique_ptr<MisspellRangesVec>, LangSharedString::Hash> SharedStringMapType;
#endif
SharedStringMapType maStringMisspells;
CellMapType maEditTextMisspells;
virtual OUString getCacheName() const override
{
return "SpellCheckCache";
}
virtual bool dropCaches() override
{
clear();
return true;
}
virtual void dumpState(rtl::OStringBuffer& rState) override
{
rState.append("\nSpellCheckCache:\t");
rState.append("\t string misspells: ");
rState.append(static_cast<sal_Int32>(maStringMisspells.size()));
rState.append("\t editeng misspells: ");
rState.append(static_cast<sal_Int32>(maEditTextMisspells.size()));
}
public:
SpellCheckCache()
#if defined __cpp_lib_memory_resource
: maStringMisspells(&CacheOwner::GetMemoryResource())
, maEditTextMisspells(&CacheOwner::GetMemoryResource())
#endif
{
}
bool query(SCCOL nCol, SCROW nRow, LanguageType eLang,
const ScRefCellValue& rCell, MisspellRangesVec*& rpRanges) const
{
CellType eType = rCell.getType();
if (eType == CELLTYPE_STRING)
{
SharedStringMapType::const_iterator it = maStringMisspells.find(LangSharedString(eLang, rCell));
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, LanguageType eLang,
const ScRefCellValue& rCell, std::unique_ptr<MisspellRangesVec> pRanges)
{
CellType eType = rCell.getType();
if (eType == CELLTYPE_STRING)
{
maStringMisspells.insert_or_assign(LangSharedString(eLang, rCell), std::move(pRanges));
}
else if (eType == CELLTYPE_EDIT)
maEditTextMisspells.insert_or_assign(CellPos(nCol, nRow), std::move(pRanges));
}
void clear()
{
SharedStringMapType(maStringMisspells.get_allocator()).swap(maStringMisspells);
CellMapType(maEditTextMisspells.get_allocator()).swap(maEditTextMisspells);
}
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;
MisspellRangeResult maRanges;
SpellCheckResult() : mnCol(-1), mnRow(-1) {}
void set(SCCOL nCol, SCROW nRow, const MisspellRangeResult& rMisspells)
{
mnCol = nCol;
mnRow = nRow;
maRanges = rMisspells;
}
MisspellRangeResult query(SCCOL nCol, SCROW nRow) const
{
assert(mnCol == nCol);
assert(mnRow == nRow);
(void)nCol;
(void)nRow;
return maRanges;
}
void clear()
{
mnCol = -1;
mnRow = -1;
maRanges = {};
}
};
SpellCheckContext::SpellCheckContext(ScDocument& rDocument, SCTAB nTab) :
rDoc(rDocument),
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();
}
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).mpRanges;
}
sc::MisspellRangeResult 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 sc::MisspellRangeResult& rRangeResult )
{
if (!mpEngine || !mpCache)
reset();
ScRefCellValue aCell(rDoc, ScAddress(nCol, nRow, mnTab));
CellType eType = aCell.getType();
if (eType != CELLTYPE_STRING && eType != CELLTYPE_EDIT)
return;
const MisspellRangesVec* pRanges = rRangeResult.mpRanges;
std::unique_ptr<MisspellRangesVec> pMisspells(pRanges ? new MisspellRangesVec(*pRanges) : nullptr);
mpCache->set(nCol, nRow, rRangeResult.meCellLang, 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 (rDoc.HasPivotTable())
{
if (ScDPCollection* pDPs = rDoc.GetDPCollection())
{
ScRangeList aPivotRanges = pDPs->GetAllTableRanges(mnTab);
if (aPivotRanges.Contains(ScRange(ScAddress(nCol, nRow, mnTab)))) // Don't spell check within pivot tables
{
mpResult->set(nCol, nRow, {});
return;
}
}
}
ScRefCellValue aCell(rDoc, ScAddress(nCol, nRow, mnTab));
CellType eType = aCell.getType();
if (eType != CELLTYPE_STRING && eType != CELLTYPE_EDIT)
{
// No spell-check required.
mpResult->set(nCol, nRow, {});
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 = rDoc.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, {}); // No need to spell check this cell.
return;
}
MisspellRangesVec* pCacheRanges = nullptr;
bool bFound = mpCache->query(nCol, nRow, eCellLang, aCell, pCacheRanges);
if (bFound)
{
// Cache hit.
mpResult->set(nCol, nRow, MisspellRangeResult(pCacheRanges, eCellLang));
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<MisspellRangesVec> pRanges;
if (mpStatus->mbModified)
{
pRanges.reset(new MisspellRangesVec);
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, MisspellRangeResult(pRanges.get(), eCellLang));
mpCache->set(nCol, nRow, eCellLang, aCell, std::move(pRanges));
}
void SpellCheckContext::resetCache(bool bContentChangeOnly)
{
if (!mpResult)
mpResult.reset(new SpellCheckResult());
else
mpResult->clear();
if (!mpCache)
mpCache.reset(new SpellCheckCache);
else if (bContentChangeOnly)
mpCache->clearEditTextMap();
else
mpCache->clear();
}
void SpellCheckContext::setup()
{
mpEngine.reset(new ScTabEditEngine(rDoc));
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: */
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.