/src/libreoffice/sc/source/ui/view/spellcheckcontext.cxx
Line | Count | Source |
1 | | /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
2 | | /* |
3 | | * This file is part of the LibreOffice project. |
4 | | * |
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/. |
8 | | */ |
9 | | |
10 | | #include <spellcheckcontext.hxx> |
11 | | #include <svl/sharedstring.hxx> |
12 | | #include <editeng/eeitem.hxx> |
13 | | #include <editeng/langitem.hxx> |
14 | | #include <editeng/unolingu.hxx> |
15 | | #include <vcl/dropcache.hxx> |
16 | | |
17 | | #include <scitems.hxx> |
18 | | #include <document.hxx> |
19 | | #include <cellvalue.hxx> |
20 | | #include <editutil.hxx> |
21 | | #include <dpobject.hxx> |
22 | | |
23 | | #include <com/sun/star/linguistic2/XSpellChecker1.hpp> |
24 | | |
25 | | #include <o3tl/hash_combine.hxx> |
26 | | |
27 | | #include <unordered_map> |
28 | | |
29 | | using namespace css; |
30 | | |
31 | | using sc::SpellCheckContext; |
32 | | |
33 | | class SpellCheckContext::SpellCheckCache : public CacheOwner |
34 | | { |
35 | | struct CellPos |
36 | | { |
37 | | struct Hash |
38 | | { |
39 | | size_t operator() (const CellPos& rPos) const |
40 | 0 | { |
41 | 0 | std::size_t seed = 0; |
42 | 0 | o3tl::hash_combine(seed, rPos.mnCol); |
43 | 0 | o3tl::hash_combine(seed, rPos.mnRow); |
44 | 0 | return seed; |
45 | 0 | } |
46 | | }; |
47 | | |
48 | | SCCOL mnCol; |
49 | | SCROW mnRow; |
50 | | |
51 | 0 | CellPos(SCCOL nCol, SCROW nRow) : mnCol(nCol), mnRow(nRow) {} |
52 | | |
53 | | bool operator== (const CellPos& r) const |
54 | 0 | { |
55 | 0 | return mnCol == r.mnCol && mnRow == r.mnRow; |
56 | 0 | } |
57 | | |
58 | | }; |
59 | | |
60 | | struct LangSharedString |
61 | | { |
62 | | struct Hash |
63 | | { |
64 | | size_t operator() (const LangSharedString& rKey) const |
65 | 0 | { |
66 | 0 | std::size_t seed = 0; |
67 | 0 | o3tl::hash_combine(seed, rKey.meLang.get()); |
68 | 0 | o3tl::hash_combine(seed, rKey.mpString); |
69 | 0 | return seed; |
70 | 0 | } |
71 | | }; |
72 | | |
73 | | LanguageType meLang; |
74 | | const rtl_uString* mpString; |
75 | | |
76 | | LangSharedString(LanguageType eLang, const ScRefCellValue& rCell) |
77 | 0 | : meLang(eLang) |
78 | 0 | , mpString(rCell.getSharedString()->getData()) |
79 | 0 | { |
80 | 0 | } |
81 | | |
82 | | bool operator== (const LangSharedString& r) const |
83 | 0 | { |
84 | 0 | return meLang == r.meLang && mpString == r.mpString; |
85 | 0 | } |
86 | | }; |
87 | | |
88 | | #if defined __cpp_lib_memory_resource |
89 | | typedef std::pmr::unordered_map<CellPos, std::unique_ptr<MisspellRangesVec>, CellPos::Hash> CellMapType; |
90 | | typedef std::pmr::unordered_map<LangSharedString, std::unique_ptr<MisspellRangesVec>, LangSharedString::Hash> SharedStringMapType; |
91 | | #else |
92 | | typedef std::unordered_map<CellPos, std::unique_ptr<MisspellRangesVec>, CellPos::Hash> CellMapType; |
93 | | typedef std::unordered_map<LangSharedString, std::unique_ptr<MisspellRangesVec>, LangSharedString::Hash> SharedStringMapType; |
94 | | #endif |
95 | | |
96 | | SharedStringMapType maStringMisspells; |
97 | | CellMapType maEditTextMisspells; |
98 | | |
99 | | virtual OUString getCacheName() const override |
100 | 0 | { |
101 | 0 | return "SpellCheckCache"; |
102 | 0 | } |
103 | | |
104 | | virtual bool dropCaches() override |
105 | 0 | { |
106 | 0 | clear(); |
107 | 0 | return true; |
108 | 0 | } |
109 | | |
110 | | virtual void dumpState(rtl::OStringBuffer& rState) override |
111 | 0 | { |
112 | 0 | rState.append("\nSpellCheckCache:\t"); |
113 | 0 | rState.append("\t string misspells: "); |
114 | 0 | rState.append(static_cast<sal_Int32>(maStringMisspells.size())); |
115 | 0 | rState.append("\t editeng misspells: "); |
116 | 0 | rState.append(static_cast<sal_Int32>(maEditTextMisspells.size())); |
117 | 0 | } |
118 | | |
119 | | public: |
120 | | |
121 | | SpellCheckCache() |
122 | | #if defined __cpp_lib_memory_resource |
123 | 0 | : maStringMisspells(&CacheOwner::GetMemoryResource()) |
124 | 0 | , maEditTextMisspells(&CacheOwner::GetMemoryResource()) |
125 | | #endif |
126 | 0 | { |
127 | 0 | } |
128 | | |
129 | | bool query(SCCOL nCol, SCROW nRow, LanguageType eLang, |
130 | | const ScRefCellValue& rCell, MisspellRangesVec*& rpRanges) const |
131 | 0 | { |
132 | 0 | CellType eType = rCell.getType(); |
133 | 0 | if (eType == CELLTYPE_STRING) |
134 | 0 | { |
135 | 0 | SharedStringMapType::const_iterator it = maStringMisspells.find(LangSharedString(eLang, rCell)); |
136 | 0 | if (it == maStringMisspells.end()) |
137 | 0 | return false; // Not available |
138 | | |
139 | 0 | rpRanges = it->second.get(); |
140 | 0 | return true; |
141 | 0 | } |
142 | | |
143 | 0 | if (eType == CELLTYPE_EDIT) |
144 | 0 | { |
145 | 0 | CellMapType::const_iterator it = maEditTextMisspells.find(CellPos(nCol, nRow)); |
146 | 0 | if (it == maEditTextMisspells.end()) |
147 | 0 | return false; // Not available |
148 | | |
149 | 0 | rpRanges = it->second.get(); |
150 | 0 | return true; |
151 | 0 | } |
152 | | |
153 | 0 | rpRanges = nullptr; |
154 | 0 | return true; |
155 | 0 | } |
156 | | |
157 | | void set(SCCOL nCol, SCROW nRow, LanguageType eLang, |
158 | | const ScRefCellValue& rCell, std::unique_ptr<MisspellRangesVec> pRanges) |
159 | 0 | { |
160 | 0 | CellType eType = rCell.getType(); |
161 | 0 | if (eType == CELLTYPE_STRING) |
162 | 0 | { |
163 | 0 | maStringMisspells.insert_or_assign(LangSharedString(eLang, rCell), std::move(pRanges)); |
164 | 0 | } |
165 | 0 | else if (eType == CELLTYPE_EDIT) |
166 | 0 | maEditTextMisspells.insert_or_assign(CellPos(nCol, nRow), std::move(pRanges)); |
167 | 0 | } |
168 | | |
169 | | void clear() |
170 | 0 | { |
171 | 0 | SharedStringMapType(maStringMisspells.get_allocator()).swap(maStringMisspells); |
172 | 0 | CellMapType(maEditTextMisspells.get_allocator()).swap(maEditTextMisspells); |
173 | 0 | } |
174 | | |
175 | | void clearEditTextMap() |
176 | 0 | { |
177 | 0 | maEditTextMisspells.clear(); |
178 | 0 | } |
179 | | }; |
180 | | |
181 | | struct SpellCheckContext::SpellCheckStatus |
182 | | { |
183 | | bool mbModified; |
184 | | |
185 | 0 | SpellCheckStatus() : mbModified(false) {}; |
186 | | |
187 | | DECL_LINK( EventHdl, EditStatus&, void ); |
188 | | }; |
189 | | |
190 | | IMPL_LINK(SpellCheckContext::SpellCheckStatus, EventHdl, EditStatus&, rStatus, void) |
191 | 0 | { |
192 | 0 | EditStatusFlags nStatus = rStatus.GetStatusWord(); |
193 | 0 | if (nStatus & EditStatusFlags::WRONGWORDCHANGED) |
194 | 0 | mbModified = true; |
195 | 0 | } |
196 | | |
197 | | struct SpellCheckContext::SpellCheckResult |
198 | | { |
199 | | SCCOL mnCol; |
200 | | SCROW mnRow; |
201 | | MisspellRangeResult maRanges; |
202 | | |
203 | 0 | SpellCheckResult() : mnCol(-1), mnRow(-1) {} |
204 | | |
205 | | void set(SCCOL nCol, SCROW nRow, const MisspellRangeResult& rMisspells) |
206 | 0 | { |
207 | 0 | mnCol = nCol; |
208 | 0 | mnRow = nRow; |
209 | 0 | maRanges = rMisspells; |
210 | 0 | } |
211 | | |
212 | | const MisspellRangeResult & query(SCCOL nCol, SCROW nRow) const |
213 | 0 | { |
214 | 0 | assert(mnCol == nCol); |
215 | 0 | assert(mnRow == nRow); |
216 | 0 | (void)nCol; |
217 | 0 | (void)nRow; |
218 | 0 | return maRanges; |
219 | 0 | } |
220 | | |
221 | | void clear() |
222 | 0 | { |
223 | 0 | mnCol = -1; |
224 | 0 | mnRow = -1; |
225 | 0 | maRanges = {}; |
226 | 0 | } |
227 | | }; |
228 | | |
229 | | SpellCheckContext::SpellCheckContext(ScDocument& rDocument, SCTAB nTab) : |
230 | 0 | rDoc(rDocument), |
231 | 0 | mnTab(nTab), |
232 | 0 | meLanguage(ScGlobal::GetEditDefaultLanguage()) |
233 | 0 | { |
234 | | // defer init of engine and cache till the first query/set |
235 | 0 | } |
236 | | |
237 | | SpellCheckContext::~SpellCheckContext() |
238 | 0 | { |
239 | 0 | } |
240 | | |
241 | | void SpellCheckContext::dispose() |
242 | 0 | { |
243 | 0 | mpEngine.reset(); |
244 | 0 | mpCache.reset(); |
245 | 0 | } |
246 | | |
247 | | void SpellCheckContext::setTabNo(SCTAB nTab) |
248 | 0 | { |
249 | 0 | if (mnTab == nTab) |
250 | 0 | return; |
251 | 0 | mnTab = nTab; |
252 | 0 | reset(); |
253 | 0 | } |
254 | | |
255 | | bool SpellCheckContext::isMisspelled(SCCOL nCol, SCROW nRow) const |
256 | 0 | { |
257 | 0 | const_cast<SpellCheckContext*>(this)->ensureResults(nCol, nRow); |
258 | 0 | return mpResult->query(nCol, nRow).mpRanges; |
259 | 0 | } |
260 | | |
261 | | const sc::MisspellRangeResult & SpellCheckContext::getMisspellRanges( |
262 | | SCCOL nCol, SCROW nRow ) const |
263 | 0 | { |
264 | 0 | const_cast<SpellCheckContext*>(this)->ensureResults(nCol, nRow); |
265 | 0 | return mpResult->query(nCol, nRow); |
266 | 0 | } |
267 | | |
268 | | void SpellCheckContext::setMisspellRanges( |
269 | | SCCOL nCol, SCROW nRow, const sc::MisspellRangeResult& rRangeResult ) |
270 | 0 | { |
271 | 0 | if (!mpEngine || !mpCache) |
272 | 0 | reset(); |
273 | |
|
274 | 0 | ScRefCellValue aCell(rDoc, ScAddress(nCol, nRow, mnTab)); |
275 | 0 | CellType eType = aCell.getType(); |
276 | |
|
277 | 0 | if (eType != CELLTYPE_STRING && eType != CELLTYPE_EDIT) |
278 | 0 | return; |
279 | | |
280 | 0 | const MisspellRangesVec* pRanges = rRangeResult.mpRanges; |
281 | 0 | std::unique_ptr<MisspellRangesVec> pMisspells(pRanges ? new MisspellRangesVec(*pRanges) : nullptr); |
282 | 0 | mpCache->set(nCol, nRow, rRangeResult.meCellLang, aCell, std::move(pMisspells)); |
283 | 0 | } |
284 | | |
285 | | void SpellCheckContext::reset() |
286 | 0 | { |
287 | 0 | meLanguage = ScGlobal::GetEditDefaultLanguage(); |
288 | 0 | resetCache(); |
289 | 0 | mpEngine.reset(); |
290 | 0 | mpStatus.reset(); |
291 | 0 | } |
292 | | |
293 | | void SpellCheckContext::resetForContentChange() |
294 | 0 | { |
295 | 0 | resetCache(true /* bContentChangeOnly */); |
296 | 0 | } |
297 | | |
298 | | void SpellCheckContext::ensureResults(SCCOL nCol, SCROW nRow) |
299 | 0 | { |
300 | 0 | if (!mpEngine || !mpCache || |
301 | 0 | ScGlobal::GetEditDefaultLanguage() != meLanguage) |
302 | 0 | { |
303 | 0 | reset(); |
304 | 0 | setup(); |
305 | 0 | } |
306 | | |
307 | | // perhaps compute the pivot rangelist once in some pivot-table change handler ? |
308 | 0 | if (rDoc.HasPivotTable()) |
309 | 0 | { |
310 | 0 | if (ScDPCollection* pDPs = rDoc.GetDPCollection()) |
311 | 0 | { |
312 | 0 | ScRangeList aPivotRanges = pDPs->GetAllTableRanges(mnTab); |
313 | 0 | if (aPivotRanges.Contains(ScRange(ScAddress(nCol, nRow, mnTab)))) // Don't spell check within pivot tables |
314 | 0 | { |
315 | 0 | mpResult->set(nCol, nRow, {}); |
316 | 0 | return; |
317 | 0 | } |
318 | 0 | } |
319 | 0 | } |
320 | | |
321 | 0 | ScRefCellValue aCell(rDoc, ScAddress(nCol, nRow, mnTab)); |
322 | 0 | CellType eType = aCell.getType(); |
323 | |
|
324 | 0 | if (eType != CELLTYPE_STRING && eType != CELLTYPE_EDIT) |
325 | 0 | { |
326 | | // No spell-check required. |
327 | 0 | mpResult->set(nCol, nRow, {}); |
328 | 0 | return; |
329 | 0 | } |
330 | | |
331 | | |
332 | | // Cell content is either shared-string or EditTextObject |
333 | | |
334 | | // For spell-checking, we currently only use the primary |
335 | | // language; not CJK nor CTL. |
336 | 0 | const ScPatternAttr* pPattern = rDoc.GetPattern(nCol, nRow, mnTab); |
337 | 0 | LanguageType eCellLang = pPattern->GetItem(ATTR_FONT_LANGUAGE).GetValue(); |
338 | |
|
339 | 0 | if (eCellLang == LANGUAGE_SYSTEM) |
340 | 0 | eCellLang = meLanguage; // never use SYSTEM for spelling |
341 | |
|
342 | 0 | if (eCellLang == LANGUAGE_NONE) |
343 | 0 | { |
344 | 0 | mpResult->set(nCol, nRow, {}); // No need to spell check this cell. |
345 | 0 | return; |
346 | 0 | } |
347 | | |
348 | 0 | MisspellRangesVec* pCacheRanges = nullptr; |
349 | 0 | bool bFound = mpCache->query(nCol, nRow, eCellLang, aCell, pCacheRanges); |
350 | 0 | if (bFound) |
351 | 0 | { |
352 | | // Cache hit. |
353 | 0 | mpResult->set(nCol, nRow, MisspellRangeResult(pCacheRanges, eCellLang)); |
354 | 0 | return; |
355 | 0 | } |
356 | | |
357 | | // Cache miss, the cell needs spell-check.. |
358 | 0 | if (eType == CELLTYPE_STRING) |
359 | 0 | mpEngine->SetText(aCell.getSharedString()->getString()); |
360 | 0 | else |
361 | 0 | mpEngine->SetText(*aCell.getEditText()); |
362 | | |
363 | | // it has to happen after we set text |
364 | 0 | mpEngine->SetDefaultItem(SvxLanguageItem(eCellLang, EE_CHAR_LANGUAGE)); |
365 | |
|
366 | 0 | mpStatus->mbModified = false; |
367 | 0 | mpEngine->CompleteOnlineSpelling(); |
368 | 0 | std::unique_ptr<MisspellRangesVec> pRanges; |
369 | 0 | if (mpStatus->mbModified) |
370 | 0 | { |
371 | 0 | pRanges.reset(new MisspellRangesVec); |
372 | 0 | mpEngine->GetAllMisspellRanges(*pRanges); |
373 | |
|
374 | 0 | if (pRanges->empty()) |
375 | 0 | pRanges.reset(nullptr); |
376 | 0 | } |
377 | | // else : No change in status for EditStatusFlags::WRONGWORDCHANGED => no spell errors (which is the default status). |
378 | |
|
379 | 0 | mpResult->set(nCol, nRow, MisspellRangeResult(pRanges.get(), eCellLang)); |
380 | 0 | mpCache->set(nCol, nRow, eCellLang, aCell, std::move(pRanges)); |
381 | 0 | } |
382 | | |
383 | | void SpellCheckContext::resetCache(bool bContentChangeOnly) |
384 | 0 | { |
385 | 0 | if (!mpResult) |
386 | 0 | mpResult.reset(new SpellCheckResult()); |
387 | 0 | else |
388 | 0 | mpResult->clear(); |
389 | |
|
390 | 0 | if (!mpCache) |
391 | 0 | mpCache.reset(new SpellCheckCache); |
392 | 0 | else if (bContentChangeOnly) |
393 | 0 | mpCache->clearEditTextMap(); |
394 | 0 | else |
395 | 0 | mpCache->clear(); |
396 | 0 | } |
397 | | |
398 | | void SpellCheckContext::setup() |
399 | 0 | { |
400 | 0 | mpEngine.reset(new ScTabEditEngine(rDoc)); |
401 | 0 | mpStatus.reset(new SpellCheckStatus()); |
402 | |
|
403 | 0 | mpEngine->SetControlWord( |
404 | 0 | mpEngine->GetControlWord() | (EEControlBits::ONLINESPELLING | EEControlBits::ALLOWBIGOBJS)); |
405 | 0 | mpEngine->SetStatusEventHdl(LINK(mpStatus.get(), SpellCheckStatus, EventHdl)); |
406 | | // Delimiters here like in inputhdl.cxx !!! |
407 | 0 | mpEngine->SetWordDelimiters( |
408 | 0 | ScEditUtil::ModifyDelimiters(mpEngine->GetWordDelimiters())); |
409 | |
|
410 | 0 | uno::Reference<linguistic2::XSpellChecker1> xXSpellChecker1(LinguMgr::GetSpellChecker()); |
411 | 0 | mpEngine->SetSpeller(xXSpellChecker1); |
412 | 0 | mpEngine->SetDefaultLanguage(meLanguage); |
413 | 0 | } |
414 | | |
415 | | /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |