1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
10 #include <spellcheckcontext.hxx>
11 #include <svl/sharedstring.hxx>
12 #include <editeng/eeitem.hxx>
13 #include <editeng/langitem.hxx>
14 #include <editeng/unolingu.hxx>
16 #include <scitems.hxx>
17 #include <document.hxx>
18 #include <cellvalue.hxx>
19 #include <editutil.hxx>
20 #include <dpobject.hxx>
22 #include <com/sun/star/linguistic2/XSpellChecker1.hpp>
24 #include <o3tl/hash_combine.hxx>
26 #include <unordered_map>
30 using sc::SpellCheckContext
;
32 class SpellCheckContext::SpellCheckCache
38 size_t operator() (const CellPos
& rPos
) const
41 o3tl::hash_combine(seed
, rPos
.mnCol
);
42 o3tl::hash_combine(seed
, rPos
.mnRow
);
50 CellPos(SCCOL nCol
, SCROW nRow
) : mnCol(nCol
), mnRow(nRow
) {}
52 bool operator== (const CellPos
& r
) const
54 return mnCol
== r
.mnCol
&& mnRow
== r
.mnRow
;
59 typedef std::vector
<editeng::MisspellRanges
> MisspellType
;
60 typedef std::unordered_map
<CellPos
, std::unique_ptr
<MisspellType
>, CellPos::Hash
> CellMapType
;
61 typedef std::unordered_map
<const rtl_uString
*, std::unique_ptr
<MisspellType
>> SharedStringMapType
;
62 typedef std::unordered_map
<CellPos
, LanguageType
, CellPos::Hash
> CellLangMapType
;
64 SharedStringMapType maStringMisspells
;
65 CellMapType maEditTextMisspells
;
66 CellLangMapType maCellLanguages
;
67 LanguageType meDefCellLanguage
;
71 SpellCheckCache(LanguageType eDefaultCellLanguage
) : meDefCellLanguage(eDefaultCellLanguage
)
75 bool query(SCCOL nCol
, SCROW nRow
, const ScRefCellValue
& rCell
, MisspellType
*& rpRanges
) const
77 CellType eType
= rCell
.getType();
78 if (eType
== CELLTYPE_STRING
)
80 SharedStringMapType::const_iterator it
= maStringMisspells
.find(rCell
.getSharedString()->getData());
81 if (it
== maStringMisspells
.end())
82 return false; // Not available
84 rpRanges
= it
->second
.get();
88 if (eType
== CELLTYPE_EDIT
)
90 CellMapType::const_iterator it
= maEditTextMisspells
.find(CellPos(nCol
, nRow
));
91 if (it
== maEditTextMisspells
.end())
92 return false; // Not available
94 rpRanges
= it
->second
.get();
102 void set(SCCOL nCol
, SCROW nRow
, const ScRefCellValue
& rCell
, std::unique_ptr
<MisspellType
> pRanges
)
104 CellType eType
= rCell
.getType();
105 if (eType
== CELLTYPE_STRING
)
106 maStringMisspells
.insert_or_assign(rCell
.getSharedString()->getData(), std::move(pRanges
));
107 else if (eType
== CELLTYPE_EDIT
)
108 maEditTextMisspells
.insert_or_assign(CellPos(nCol
, nRow
), std::move(pRanges
));
111 LanguageType
getLanguage(SCCOL nCol
, SCROW nRow
) const
113 CellLangMapType::const_iterator it
= maCellLanguages
.find(CellPos(nCol
, nRow
));
114 if (it
== maCellLanguages
.end())
115 return meDefCellLanguage
;
120 void setLanguage(LanguageType eCellLang
, SCCOL nCol
, SCROW nRow
)
122 if (eCellLang
== meDefCellLanguage
)
123 maCellLanguages
.erase(CellPos(nCol
, nRow
));
125 maCellLanguages
.insert_or_assign(CellPos(nCol
, nRow
), eCellLang
);
128 void clear(LanguageType eDefaultCellLanguage
)
130 maStringMisspells
.clear();
131 maEditTextMisspells
.clear();
132 maCellLanguages
.clear();
133 meDefCellLanguage
= eDefaultCellLanguage
;
136 void clearEditTextMap()
138 maEditTextMisspells
.clear();
142 struct SpellCheckContext::SpellCheckStatus
146 SpellCheckStatus() : mbModified(false) {};
148 DECL_LINK( EventHdl
, EditStatus
&, void );
151 IMPL_LINK(SpellCheckContext::SpellCheckStatus
, EventHdl
, EditStatus
&, rStatus
, void)
153 EditStatusFlags nStatus
= rStatus
.GetStatusWord();
154 if (nStatus
& EditStatusFlags::WRONGWORDCHANGED
)
158 struct SpellCheckContext::SpellCheckResult
162 const std::vector
<editeng::MisspellRanges
>* pRanges
;
164 SpellCheckResult() : mnCol(-1), mnRow(-1), pRanges(nullptr) {}
166 void set(SCCOL nCol
, SCROW nRow
, const std::vector
<editeng::MisspellRanges
>* pMisspells
)
170 pRanges
= pMisspells
;
173 const std::vector
<editeng::MisspellRanges
>* query(SCCOL nCol
, SCROW nRow
) const
175 assert(mnCol
== nCol
);
176 assert(mnRow
== nRow
);
190 SpellCheckContext::SpellCheckContext(ScDocument
* pDocument
, SCTAB nTab
) :
193 meLanguage(ScGlobal::GetEditDefaultLanguage())
195 // defer init of engine and cache till the first query/set
198 SpellCheckContext::~SpellCheckContext()
202 void SpellCheckContext::dispose()
209 void SpellCheckContext::setTabNo(SCTAB nTab
)
217 bool SpellCheckContext::isMisspelled(SCCOL nCol
, SCROW nRow
) const
219 const_cast<SpellCheckContext
*>(this)->ensureResults(nCol
, nRow
);
220 return mpResult
->query(nCol
, nRow
);
223 const std::vector
<editeng::MisspellRanges
>* SpellCheckContext::getMisspellRanges(
224 SCCOL nCol
, SCROW nRow
) const
226 const_cast<SpellCheckContext
*>(this)->ensureResults(nCol
, nRow
);
227 return mpResult
->query(nCol
, nRow
);
230 void SpellCheckContext::setMisspellRanges(
231 SCCOL nCol
, SCROW nRow
, const std::vector
<editeng::MisspellRanges
>* pRanges
)
233 if (!mpEngine
|| !mpCache
)
236 ScRefCellValue
aCell(*pDoc
, ScAddress(nCol
, nRow
, mnTab
));
237 CellType eType
= aCell
.getType();
239 if (eType
!= CELLTYPE_STRING
&& eType
!= CELLTYPE_EDIT
)
242 typedef std::vector
<editeng::MisspellRanges
> MisspellType
;
243 std::unique_ptr
<MisspellType
> pMisspells(pRanges
? new MisspellType(*pRanges
) : nullptr);
244 mpCache
->set(nCol
, nRow
, aCell
, std::move(pMisspells
));
247 void SpellCheckContext::reset()
249 meLanguage
= ScGlobal::GetEditDefaultLanguage();
255 void SpellCheckContext::resetForContentChange()
257 resetCache(true /* bContentChangeOnly */);
260 void SpellCheckContext::ensureResults(SCCOL nCol
, SCROW nRow
)
262 if (!mpEngine
|| !mpCache
||
263 ScGlobal::GetEditDefaultLanguage() != meLanguage
)
269 // perhaps compute the pivot rangelist once in some pivot-table change handler ?
270 if (pDoc
->HasPivotTable())
272 if (ScDPCollection
* pDPs
= pDoc
->GetDPCollection())
274 ScRangeList aPivotRanges
= pDPs
->GetAllTableRanges(mnTab
);
275 if (aPivotRanges
.Contains(ScRange(ScAddress(nCol
, nRow
, mnTab
)))) // Don't spell check within pivot tables
277 mpResult
->set(nCol
, nRow
, nullptr);
283 ScRefCellValue
aCell(*pDoc
, ScAddress(nCol
, nRow
, mnTab
));
284 CellType eType
= aCell
.getType();
286 if (eType
!= CELLTYPE_STRING
&& eType
!= CELLTYPE_EDIT
)
288 // No spell-check required.
289 mpResult
->set(nCol
, nRow
, nullptr);
294 // Cell content is either shared-string or EditTextObject
296 // For spell-checking, we currently only use the primary
297 // language; not CJK nor CTL.
298 const ScPatternAttr
* pPattern
= pDoc
->GetPattern(nCol
, nRow
, mnTab
);
299 LanguageType eCellLang
= pPattern
->GetItem(ATTR_FONT_LANGUAGE
).GetValue();
301 if (eCellLang
== LANGUAGE_SYSTEM
)
302 eCellLang
= meLanguage
; // never use SYSTEM for spelling
304 if (eCellLang
== LANGUAGE_NONE
)
306 mpResult
->set(nCol
, nRow
, nullptr); // No need to spell check this cell.
310 typedef std::vector
<editeng::MisspellRanges
> MisspellType
;
312 LanguageType eCachedCellLang
= mpCache
->getLanguage(nCol
, nRow
);
314 if (eCellLang
!= eCachedCellLang
)
315 mpCache
->setLanguage(eCellLang
, nCol
, nRow
);
319 MisspellType
* pRanges
= nullptr;
320 bool bFound
= mpCache
->query(nCol
, nRow
, aCell
, pRanges
);
324 mpResult
->set(nCol
, nRow
, pRanges
);
329 // Cache miss, the cell needs spell-check..
330 if (eType
== CELLTYPE_STRING
)
331 mpEngine
->SetText(aCell
.getSharedString()->getString());
333 mpEngine
->SetText(*aCell
.getEditText());
335 // it has to happen after we set text
336 mpEngine
->SetDefaultItem(SvxLanguageItem(eCellLang
, EE_CHAR_LANGUAGE
));
338 mpStatus
->mbModified
= false;
339 mpEngine
->CompleteOnlineSpelling();
340 std::unique_ptr
<MisspellType
> pRanges
;
341 if (mpStatus
->mbModified
)
343 pRanges
.reset(new MisspellType
);
344 mpEngine
->GetAllMisspellRanges(*pRanges
);
346 if (pRanges
->empty())
347 pRanges
.reset(nullptr);
349 // else : No change in status for EditStatusFlags::WRONGWORDCHANGED => no spell errors (which is the default status).
351 mpResult
->set(nCol
, nRow
, pRanges
.get());
352 mpCache
->set(nCol
, nRow
, aCell
, std::move(pRanges
));
355 void SpellCheckContext::resetCache(bool bContentChangeOnly
)
358 mpResult
.reset(new SpellCheckResult());
363 mpCache
.reset(new SpellCheckCache(meLanguage
));
364 else if (bContentChangeOnly
)
365 mpCache
->clearEditTextMap();
367 mpCache
->clear(meLanguage
);
370 void SpellCheckContext::setup()
372 mpEngine
.reset(new ScTabEditEngine(pDoc
));
373 mpStatus
.reset(new SpellCheckStatus());
375 mpEngine
->SetControlWord(
376 mpEngine
->GetControlWord() | (EEControlBits::ONLINESPELLING
| EEControlBits::ALLOWBIGOBJS
));
377 mpEngine
->SetStatusEventHdl(LINK(mpStatus
.get(), SpellCheckStatus
, EventHdl
));
378 // Delimiters here like in inputhdl.cxx !!!
379 mpEngine
->SetWordDelimiters(
380 ScEditUtil::ModifyDelimiters(mpEngine
->GetWordDelimiters()));
382 uno::Reference
<linguistic2::XSpellChecker1
> xXSpellChecker1(LinguMgr::GetSpellChecker());
383 mpEngine
->SetSpeller(xXSpellChecker1
);
384 mpEngine
->SetDefaultLanguage(meLanguage
);
387 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */