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 * This file incorporates work covered by the following license notice:
10 *
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
18 */
19
20 #include <com/sun/star/linguistic2/ProofreadingResult.hpp>
21 #include <com/sun/star/linguistic2/XProofreadingIterator.hpp>
22 #include <com/sun/star/linguistic2/XHyphenatedWord.hpp>
23 #include <com/sun/star/linguistic2/XLinguProperties.hpp>
24 #include <com/sun/star/text/XFlatParagraph.hpp>
25 #include <com/sun/star/i18n/ScriptType.hpp>
26 #include <com/sun/star/beans/XPropertySet.hpp>
27 #include <o3tl/any.hxx>
28
29 #include <unoflatpara.hxx>
30
31 #include <strings.hrc>
32 #include <hintids.hxx>
33 #include <unotools/linguprops.hxx>
34 #include <linguistic/lngprops.hxx>
35 #include <editeng/unolingu.hxx>
36 #include <editeng/svxacorr.hxx>
37 #include <editeng/langitem.hxx>
38 #include <editeng/SpellPortions.hxx>
39 #include <editeng/scripttypeitem.hxx>
40 #include <charatr.hxx>
41 #include <editsh.hxx>
42 #include <doc.hxx>
43 #include <IDocumentUndoRedo.hxx>
44 #include <IDocumentRedlineAccess.hxx>
45 #include <rootfrm.hxx>
46 #include <pam.hxx>
47 #include <pamtyp.hxx>
48 #include <swundo.hxx>
49 #include <ndtxt.hxx>
50 #include <viewopt.hxx>
51 #include <viscrs.hxx>
52 #include <SwGrammarMarkUp.hxx>
53 #include <mdiexp.hxx>
54 #include <cntfrm.hxx>
55 #include <splargs.hxx>
56 #include <redline.hxx>
57 #include <docary.hxx>
58 #include <docsh.hxx>
59 #include <txatbase.hxx>
60 #include <txtfrm.hxx>
61
62 using namespace ::svx;
63 using namespace ::com::sun::star;
64 using namespace ::com::sun::star::uno;
65 using namespace ::com::sun::star::beans;
66 using namespace ::com::sun::star::linguistic2;
67
68 class SwLinguIter
69 {
70 SwEditShell *pSh;
71 std::unique_ptr<SwPosition> m_pStart;
72 std::unique_ptr<SwPosition> m_pEnd;
73 std::unique_ptr<SwPosition> m_pCurr;
74 std::unique_ptr<SwPosition> m_pCurrX;
75 sal_uInt16 nCursorCnt;
76 public:
77 SwLinguIter();
78
GetSh()79 SwEditShell *GetSh() { return pSh; }
80
GetEnd() const81 const SwPosition *GetEnd() const { return m_pEnd.get(); }
SetEnd(SwPosition * pNew)82 void SetEnd(SwPosition* pNew) { m_pEnd.reset(pNew); }
83
GetStart() const84 const SwPosition *GetStart() const { return m_pStart.get(); }
SetStart(SwPosition * pNew)85 void SetStart(SwPosition* pNew) { m_pStart.reset(pNew); }
86
GetCurr() const87 const SwPosition *GetCurr() const { return m_pCurr.get(); }
SetCurr(SwPosition * pNew)88 void SetCurr(SwPosition* pNew) { m_pCurr.reset(pNew); }
89
GetCurrX() const90 const SwPosition *GetCurrX() const { return m_pCurrX.get(); }
SetCurrX(SwPosition * pNew)91 void SetCurrX(SwPosition* pNew) { m_pCurrX.reset(pNew); }
92
GetCursorCnt()93 sal_uInt16& GetCursorCnt(){ return nCursorCnt; }
94
95 // for the UI:
96 void Start_( SwEditShell *pSh, SwDocPositions eStart,
97 SwDocPositions eEnd );
98 void End_(bool bRestoreSelection = true);
99 };
100
101 // #i18881# to be able to identify the positions of the changed words
102 // the content positions of each portion need to be saved
103 struct SpellContentPosition
104 {
105 sal_Int32 nLeft;
106 sal_Int32 nRight;
107 };
108
109 typedef std::vector<SpellContentPosition> SpellContentPositions;
110
111 class SwSpellIter : public SwLinguIter
112 {
113 uno::Reference< XSpellChecker1 > xSpeller;
114 svx::SpellPortions aLastPortions;
115
116 SpellContentPositions aLastPositions;
117 bool bBackToStartOfSentence;
118
119 void CreatePortion(uno::Reference< XSpellAlternatives > const & xAlt,
120 linguistic2::ProofreadingResult* pGrammarResult,
121 bool bIsField, bool bIsHidden);
122
123 void AddPortion(uno::Reference< XSpellAlternatives > const & xAlt,
124 linguistic2::ProofreadingResult* pGrammarResult,
125 const SpellContentPositions& rDeletedRedlines);
126 public:
SwSpellIter()127 SwSpellIter() :
128 bBackToStartOfSentence(false) {}
129
130 void Start( SwEditShell *pSh, SwDocPositions eStart, SwDocPositions eEnd );
131
132 uno::Any Continue( sal_uInt16* pPageCnt, sal_uInt16* pPageSt );
133
134 bool SpellSentence(svx::SpellPortions& rPortions, bool bIsGrammarCheck);
135 void ToSentenceStart();
GetLastPortions() const136 const svx::SpellPortions& GetLastPortions() const { return aLastPortions;}
GetLastPositions() const137 const SpellContentPositions& GetLastPositions() const {return aLastPositions;}
138 };
139
140 /// used for text conversion
141 class SwConvIter : public SwLinguIter
142 {
143 SwConversionArgs &rArgs;
144 public:
SwConvIter(SwConversionArgs & rConvArgs)145 explicit SwConvIter(SwConversionArgs &rConvArgs)
146 : rArgs(rConvArgs)
147 {
148 }
149
150 void Start( SwEditShell *pSh, SwDocPositions eStart, SwDocPositions eEnd );
151
152 uno::Any Continue( sal_uInt16* pPageCnt, sal_uInt16* pPageSt );
153 };
154
155 class SwHyphIter : public SwLinguIter
156 {
157 // With that we save a GetFrame() in Hyphenate //TODO: does it actually matter?
158 const SwTextNode *m_pLastNode;
159 SwTextFrame *m_pLastFrame;
160 friend SwTextFrame * sw::SwHyphIterCacheLastTextFrame(SwTextNode const * pNode, const sw::Creator& rCreator);
161
162 bool bOldIdle;
163 static void DelSoftHyph( SwPaM &rPam );
164
165 public:
SwHyphIter()166 SwHyphIter() : m_pLastNode(nullptr), m_pLastFrame(nullptr), bOldIdle(false) {}
167
168 void Start( SwEditShell *pSh, SwDocPositions eStart, SwDocPositions eEnd );
169 void End();
170
171 void Ignore();
172
173 uno::Any Continue( sal_uInt16* pPageCnt, sal_uInt16* pPageSt );
174
175 static bool IsAuto();
176 void InsertSoftHyph( const sal_Int32 nHyphPos );
177 void ShowSelection();
178 };
179
180 static SwSpellIter* g_pSpellIter = nullptr;
181 static SwConvIter* g_pConvIter = nullptr;
182 static SwHyphIter* g_pHyphIter = nullptr;
183
SwLinguIter()184 SwLinguIter::SwLinguIter()
185 : pSh(nullptr)
186 , nCursorCnt(0)
187 {
188 // TODO missing: ensurance of re-entrance, OSL_ENSURE( etc.
189 }
190
Start_(SwEditShell * pShell,SwDocPositions eStart,SwDocPositions eEnd)191 void SwLinguIter::Start_( SwEditShell *pShell, SwDocPositions eStart,
192 SwDocPositions eEnd )
193 {
194 // TODO missing: ensurance of re-entrance, locking
195 if( pSh )
196 return;
197
198 bool bSetCurr;
199
200 pSh = pShell;
201
202 SET_CURR_SHELL( pSh );
203
204 OSL_ENSURE(!m_pEnd, "SwLinguIter::Start_ without End?");
205
206 SwPaM *pCursor = pSh->GetCursor();
207
208 if( pShell->HasSelection() || pCursor != pCursor->GetNext() )
209 {
210 bSetCurr = nullptr != GetCurr();
211 nCursorCnt = pSh->GetCursorCnt();
212 if( pSh->IsTableMode() )
213 pSh->TableCursorToCursor();
214
215 pSh->Push();
216 sal_uInt16 n;
217 for( n = 0; n < nCursorCnt; ++n )
218 {
219 pSh->Push();
220 pSh->DestroyCursor();
221 }
222 pSh->Pop(SwCursorShell::PopMode::DeleteCurrent);
223 }
224 else
225 {
226 bSetCurr = false;
227 nCursorCnt = 1;
228 pSh->Push();
229 pSh->SetLinguRange( eStart, eEnd );
230 }
231
232 pCursor = pSh->GetCursor();
233 if ( *pCursor->GetPoint() > *pCursor->GetMark() )
234 pCursor->Exchange();
235
236 m_pStart.reset(new SwPosition(*pCursor->GetPoint()));
237 m_pEnd.reset(new SwPosition(*pCursor->GetMark()));
238 if( bSetCurr )
239 {
240 SwPosition* pNew = new SwPosition( *GetStart() );
241 SetCurr( pNew );
242 pNew = new SwPosition( *pNew );
243 SetCurrX( pNew );
244 }
245
246 pCursor->SetMark();
247 }
248
End_(bool bRestoreSelection)249 void SwLinguIter::End_(bool bRestoreSelection)
250 {
251 if( !pSh )
252 return;
253
254 OSL_ENSURE(m_pEnd, "SwLinguIter::End_ without end?");
255 if(bRestoreSelection)
256 {
257 while( nCursorCnt-- )
258 pSh->Pop(SwCursorShell::PopMode::DeleteCurrent);
259
260 pSh->KillPams();
261 pSh->ClearMark();
262 }
263 m_pStart.reset();
264 m_pEnd.reset();
265 m_pCurr.reset();
266 m_pCurrX.reset();
267
268 pSh = nullptr;
269 }
270
Start(SwEditShell * pShell,SwDocPositions eStart,SwDocPositions eEnd)271 void SwSpellIter::Start( SwEditShell *pShell, SwDocPositions eStart,
272 SwDocPositions eEnd )
273 {
274 if( GetSh() )
275 return;
276
277 xSpeller = ::GetSpellChecker();
278 if ( xSpeller.is() )
279 Start_( pShell, eStart, eEnd );
280 aLastPortions.clear();
281 aLastPositions.clear();
282 }
283
284 // This method is the origin of SwEditShell::SpellContinue()
Continue(sal_uInt16 * pPageCnt,sal_uInt16 * pPageSt)285 uno::Any SwSpellIter::Continue( sal_uInt16* pPageCnt, sal_uInt16* pPageSt )
286 {
287 //!!
288 //!! Please check SwConvIter also when modifying this
289 //!!
290
291 uno::Any aSpellRet;
292 SwEditShell *pMySh = GetSh();
293 if( !pMySh )
294 return aSpellRet;
295
296 OSL_ENSURE( GetEnd(), "SwSpellIter::Continue without start?");
297
298 uno::Reference< uno::XInterface > xSpellRet;
299 bool bGoOn = true;
300 do {
301 SwPaM *pCursor = pMySh->GetCursor();
302 if ( !pCursor->HasMark() )
303 pCursor->SetMark();
304
305 *pMySh->GetCursor()->GetPoint() = *GetCurr();
306 *pMySh->GetCursor()->GetMark() = *GetEnd();
307 pMySh->GetDoc()->Spell(*pMySh->GetCursor(),
308 xSpeller, pPageCnt, pPageSt, false, pMySh->GetLayout()) >>= xSpellRet;
309 bGoOn = GetCursorCnt() > 1;
310 if( xSpellRet.is() )
311 {
312 bGoOn = false;
313 SwPosition* pNewPoint = new SwPosition( *pCursor->GetPoint() );
314 SwPosition* pNewMark = new SwPosition( *pCursor->GetMark() );
315 SetCurr( pNewPoint );
316 SetCurrX( pNewMark );
317 }
318 if( bGoOn )
319 {
320 pMySh->Pop(SwCursorShell::PopMode::DeleteCurrent);
321 pCursor = pMySh->GetCursor();
322 if ( *pCursor->GetPoint() > *pCursor->GetMark() )
323 pCursor->Exchange();
324 SwPosition* pNew = new SwPosition( *pCursor->GetPoint() );
325 SetStart( pNew );
326 pNew = new SwPosition( *pCursor->GetMark() );
327 SetEnd( pNew );
328 pNew = new SwPosition( *GetStart() );
329 SetCurr( pNew );
330 pNew = new SwPosition( *pNew );
331 SetCurrX( pNew );
332 pCursor->SetMark();
333 --GetCursorCnt();
334 }
335 }while ( bGoOn );
336 aSpellRet <<= xSpellRet;
337 return aSpellRet;
338 }
339
Start(SwEditShell * pShell,SwDocPositions eStart,SwDocPositions eEnd)340 void SwConvIter::Start( SwEditShell *pShell, SwDocPositions eStart,
341 SwDocPositions eEnd )
342 {
343 if( GetSh() )
344 return;
345 Start_( pShell, eStart, eEnd );
346 }
347
Continue(sal_uInt16 * pPageCnt,sal_uInt16 * pPageSt)348 uno::Any SwConvIter::Continue( sal_uInt16* pPageCnt, sal_uInt16* pPageSt )
349 {
350 //!!
351 //!! Please check SwSpellIter also when modifying this
352 //!!
353
354 uno::Any aConvRet( makeAny( OUString() ) );
355 SwEditShell *pMySh = GetSh();
356 if( !pMySh )
357 return aConvRet;
358
359 OSL_ENSURE( GetEnd(), "SwConvIter::Continue() without Start?");
360
361 OUString aConvText;
362 bool bGoOn = true;
363 do {
364 SwPaM *pCursor = pMySh->GetCursor();
365 if ( !pCursor->HasMark() )
366 pCursor->SetMark();
367
368 *pMySh->GetCursor()->GetPoint() = *GetCurr();
369 *pMySh->GetCursor()->GetMark() = *GetEnd();
370
371 // call function to find next text portion to be converted
372 uno::Reference< linguistic2::XSpellChecker1 > xEmpty;
373 pMySh->GetDoc()->Spell( *pMySh->GetCursor(),
374 xEmpty, pPageCnt, pPageSt, false, pMySh->GetLayout(), &rArgs) >>= aConvText;
375
376 bGoOn = GetCursorCnt() > 1;
377 if( !aConvText.isEmpty() )
378 {
379 bGoOn = false;
380 SwPosition* pNewPoint = new SwPosition( *pCursor->GetPoint() );
381 SwPosition* pNewMark = new SwPosition( *pCursor->GetMark() );
382
383 SetCurr( pNewPoint );
384 SetCurrX( pNewMark );
385 }
386 if( bGoOn )
387 {
388 pMySh->Pop(SwCursorShell::PopMode::DeleteCurrent);
389 pCursor = pMySh->GetCursor();
390 if ( *pCursor->GetPoint() > *pCursor->GetMark() )
391 pCursor->Exchange();
392 SwPosition* pNew = new SwPosition( *pCursor->GetPoint() );
393 SetStart( pNew );
394 pNew = new SwPosition( *pCursor->GetMark() );
395 SetEnd( pNew );
396 pNew = new SwPosition( *GetStart() );
397 SetCurr( pNew );
398 pNew = new SwPosition( *pNew );
399 SetCurrX( pNew );
400 pCursor->SetMark();
401 --GetCursorCnt();
402 }
403 }while ( bGoOn );
404 return makeAny( aConvText );
405 }
406
IsAuto()407 bool SwHyphIter::IsAuto()
408 {
409 uno::Reference< beans::XPropertySet > xProp( ::GetLinguPropertySet() );
410 return xProp.is() && *o3tl::doAccess<bool>(xProp->getPropertyValue(
411 UPN_IS_HYPH_AUTO ));
412 }
413
ShowSelection()414 void SwHyphIter::ShowSelection()
415 {
416 SwEditShell *pMySh = GetSh();
417 if( pMySh )
418 {
419 pMySh->StartAction();
420 // Caution! Due to EndAction() formatting is started which can lead to the fact that new
421 // words are added to/set in the Hyphenator. Thus: save!
422 pMySh->EndAction();
423 }
424 }
425
Start(SwEditShell * pShell,SwDocPositions eStart,SwDocPositions eEnd)426 void SwHyphIter::Start( SwEditShell *pShell, SwDocPositions eStart, SwDocPositions eEnd )
427 {
428 // robust
429 if( GetSh() || GetEnd() )
430 {
431 OSL_ENSURE( !GetSh(), "SwHyphIter::Start: missing HyphEnd()" );
432 return;
433 }
434
435 // nothing to do (at least not in the way as in the "else" part)
436 bOldIdle = pShell->GetViewOptions()->IsIdle();
437 pShell->GetViewOptions()->SetIdle( false );
438 Start_( pShell, eStart, eEnd );
439 }
440
441 // restore selections
End()442 void SwHyphIter::End()
443 {
444 if( !GetSh() )
445 return;
446 GetSh()->GetViewOptions()->SetIdle( bOldIdle );
447 End_();
448 }
449
Continue(sal_uInt16 * pPageCnt,sal_uInt16 * pPageSt)450 uno::Any SwHyphIter::Continue( sal_uInt16* pPageCnt, sal_uInt16* pPageSt )
451 {
452 uno::Any aHyphRet;
453 SwEditShell *pMySh = GetSh();
454 if( !pMySh )
455 return aHyphRet;
456
457 const bool bAuto = IsAuto();
458 uno::Reference< XHyphenatedWord > xHyphWord;
459 bool bGoOn = false;
460 do {
461 SwPaM *pCursor;
462 do {
463 OSL_ENSURE( GetEnd(), "SwHyphIter::Continue without Start?" );
464 pCursor = pMySh->GetCursor();
465 if ( !pCursor->HasMark() )
466 pCursor->SetMark();
467 if ( *pCursor->GetPoint() < *pCursor->GetMark() )
468 {
469 pCursor->Exchange();
470 pCursor->SetMark();
471 }
472
473 if ( *pCursor->End() <= *GetEnd() )
474 {
475 *pCursor->GetMark() = *GetEnd();
476
477 // Do we need to break the word at the current cursor position?
478 const Point aCursorPos( pMySh->GetCharRect().Pos() );
479 xHyphWord = pMySh->GetDoc()->Hyphenate( pCursor, aCursorPos,
480 pPageCnt, pPageSt );
481 }
482
483 if( bAuto && xHyphWord.is() )
484 {
485 SwEditShell::InsertSoftHyph( xHyphWord->getHyphenationPos() + 1);
486 }
487 } while( bAuto && xHyphWord.is() ); //end of do-while
488 bGoOn = !xHyphWord.is() && GetCursorCnt() > 1;
489
490 if( bGoOn )
491 {
492 pMySh->Pop(SwCursorShell::PopMode::DeleteCurrent);
493 pCursor = pMySh->GetCursor();
494 if ( *pCursor->GetPoint() > *pCursor->GetMark() )
495 pCursor->Exchange();
496 SwPosition* pNew = new SwPosition(*pCursor->End());
497 SetEnd( pNew );
498 pCursor->SetMark();
499 --GetCursorCnt();
500 }
501 } while ( bGoOn );
502 aHyphRet <<= xHyphWord;
503 return aHyphRet;
504 }
505
506 /// ignore hyphenation
Ignore()507 void SwHyphIter::Ignore()
508 {
509 SwEditShell *pMySh = GetSh();
510 SwPaM *pCursor = pMySh->GetCursor();
511
512 // delete old SoftHyphen
513 DelSoftHyph( *pCursor );
514
515 // and continue
516 pCursor->Start()->nContent = pCursor->End()->nContent;
517 pCursor->SetMark();
518 }
519
DelSoftHyph(SwPaM & rPam)520 void SwHyphIter::DelSoftHyph( SwPaM &rPam )
521 {
522 const SwPosition* pStt = rPam.Start();
523 const sal_Int32 nStart = pStt->nContent.GetIndex();
524 const sal_Int32 nEnd = rPam.End()->nContent.GetIndex();
525 SwTextNode *pNode = pStt->nNode.GetNode().GetTextNode();
526 pNode->DelSoftHyph( nStart, nEnd );
527 }
528
InsertSoftHyph(const sal_Int32 nHyphPos)529 void SwHyphIter::InsertSoftHyph( const sal_Int32 nHyphPos )
530 {
531 SwEditShell *pMySh = GetSh();
532 OSL_ENSURE( pMySh, "SwHyphIter::InsertSoftHyph: missing HyphStart()");
533 if( !pMySh )
534 return;
535
536 SwPaM *pCursor = pMySh->GetCursor();
537 SwPosition* pSttPos = pCursor->Start();
538 SwPosition* pEndPos = pCursor->End();
539
540 const sal_Int32 nLastHyphLen = GetEnd()->nContent.GetIndex() -
541 pSttPos->nContent.GetIndex();
542
543 if( pSttPos->nNode != pEndPos->nNode || !nLastHyphLen )
544 {
545 OSL_ENSURE( pSttPos->nNode == pEndPos->nNode,
546 "SwHyphIter::InsertSoftHyph: node warp during hyphenation" );
547 OSL_ENSURE(nLastHyphLen, "SwHyphIter::InsertSoftHyph: missing HyphContinue()");
548 *pSttPos = *pEndPos;
549 return;
550 }
551
552 pMySh->StartAction();
553 {
554 SwDoc *pDoc = pMySh->GetDoc();
555 DelSoftHyph( *pCursor );
556 pSttPos->nContent += nHyphPos;
557 SwPaM aRg( *pSttPos );
558 pDoc->getIDocumentContentOperations().InsertString( aRg, OUString(CHAR_SOFTHYPHEN) );
559 }
560 // revoke selection
561 pCursor->DeleteMark();
562 pMySh->EndAction();
563 pCursor->SetMark();
564 }
565
566 namespace sw {
567
568 SwTextFrame *
SwHyphIterCacheLastTextFrame(SwTextNode const * pNode,const sw::Creator & create)569 SwHyphIterCacheLastTextFrame(SwTextNode const * pNode, const sw::Creator& create)
570 {
571 assert(g_pHyphIter);
572 if (pNode != g_pHyphIter->m_pLastNode || !g_pHyphIter->m_pLastFrame)
573 {
574 g_pHyphIter->m_pLastNode = pNode;
575 g_pHyphIter->m_pLastFrame = create();
576 }
577 return g_pHyphIter->m_pLastFrame;
578 }
579
580 }
581
HasLastSentenceGotGrammarChecked()582 bool SwEditShell::HasLastSentenceGotGrammarChecked()
583 {
584 bool bTextWasGrammarChecked = false;
585 if (g_pSpellIter)
586 {
587 svx::SpellPortions aLastPortions( g_pSpellIter->GetLastPortions() );
588 for (size_t i = 0; i < aLastPortions.size() && !bTextWasGrammarChecked; ++i)
589 {
590 // bIsGrammarError is also true if the text was only checked but no
591 // grammar error was found. (That is if a ProofreadingResult was obtained in
592 // SwDoc::Spell and in turn bIsGrammarError was set in SwSpellIter::CreatePortion)
593 if (aLastPortions[i].bIsGrammarError)
594 bTextWasGrammarChecked = true;
595 }
596 }
597 return bTextWasGrammarChecked;
598 }
599
HasConvIter()600 bool SwEditShell::HasConvIter()
601 {
602 return nullptr != g_pConvIter;
603 }
604
HasHyphIter()605 bool SwEditShell::HasHyphIter()
606 {
607 return nullptr != g_pHyphIter;
608 }
609
SetLinguRange(SwDocPositions eStart,SwDocPositions eEnd)610 void SwEditShell::SetLinguRange( SwDocPositions eStart, SwDocPositions eEnd )
611 {
612 SwPaM *pCursor = GetCursor();
613 MakeFindRange( eStart, eEnd, pCursor );
614 if( *pCursor->GetPoint() > *pCursor->GetMark() )
615 pCursor->Exchange();
616 }
617
SpellStart(SwDocPositions eStart,SwDocPositions eEnd,SwDocPositions eCurr,SwConversionArgs * pConvArgs)618 void SwEditShell::SpellStart(
619 SwDocPositions eStart, SwDocPositions eEnd, SwDocPositions eCurr,
620 SwConversionArgs *pConvArgs )
621 {
622 SwLinguIter *pLinguIter = nullptr;
623
624 // do not spell if interactive spelling is active elsewhere
625 if (!pConvArgs && !g_pSpellIter)
626 {
627 g_pSpellIter = new SwSpellIter;
628 pLinguIter = g_pSpellIter;
629 }
630 // do not do text conversion if it is active elsewhere
631 if (pConvArgs && !g_pConvIter)
632 {
633 g_pConvIter = new SwConvIter( *pConvArgs );
634 pLinguIter = g_pConvIter;
635 }
636
637 if (pLinguIter)
638 {
639 SwCursor* pSwCursor = GetSwCursor();
640
641 SwPosition *pTmp = new SwPosition( *pSwCursor->GetPoint() );
642 pSwCursor->FillFindPos( eCurr, *pTmp );
643 pLinguIter->SetCurr( pTmp );
644
645 pTmp = new SwPosition( *pTmp );
646 pLinguIter->SetCurrX( pTmp );
647 }
648
649 if (!pConvArgs && g_pSpellIter)
650 g_pSpellIter->Start( this, eStart, eEnd );
651 if (pConvArgs && g_pConvIter)
652 g_pConvIter->Start( this, eStart, eEnd );
653 }
654
SpellEnd(SwConversionArgs const * pConvArgs,bool bRestoreSelection)655 void SwEditShell::SpellEnd( SwConversionArgs const *pConvArgs, bool bRestoreSelection )
656 {
657 if (!pConvArgs && g_pSpellIter && g_pSpellIter->GetSh() == this)
658 {
659 g_pSpellIter->End_(bRestoreSelection);
660 delete g_pSpellIter;
661 g_pSpellIter = nullptr;
662 }
663 if (pConvArgs && g_pConvIter && g_pConvIter->GetSh() == this)
664 {
665 g_pConvIter->End_();
666 delete g_pConvIter;
667 g_pConvIter = nullptr;
668 }
669 }
670
671 /// @returns SPL_ return values as in splchk.hxx
SpellContinue(sal_uInt16 * pPageCnt,sal_uInt16 * pPageSt,SwConversionArgs const * pConvArgs)672 uno::Any SwEditShell::SpellContinue(
673 sal_uInt16* pPageCnt, sal_uInt16* pPageSt,
674 SwConversionArgs const *pConvArgs )
675 {
676 uno::Any aRes;
677
678 if ((!pConvArgs && g_pSpellIter->GetSh() != this) ||
679 ( pConvArgs && g_pConvIter->GetSh() != this))
680 return aRes;
681
682 if( pPageCnt && !*pPageCnt )
683 {
684 sal_uInt16 nEndPage = GetLayout()->GetPageNum();
685 nEndPage += nEndPage * 10 / 100;
686 *pPageCnt = nEndPage;
687 if( nEndPage )
688 ::StartProgress( STR_STATSTR_SPELL, 0, nEndPage, GetDoc()->GetDocShell() );
689 }
690
691 OSL_ENSURE( pConvArgs || g_pSpellIter, "SpellIter missing" );
692 OSL_ENSURE( !pConvArgs || g_pConvIter, "ConvIter missing" );
693 //JP 18.07.95: prevent displaying selection on error messages. NO StartAction so that all
694 // Paints are also disabled.
695 ++mnStartAction;
696 OUString aRet;
697 uno::Reference< uno::XInterface > xRet;
698 if (pConvArgs)
699 {
700 g_pConvIter->Continue( pPageCnt, pPageSt ) >>= aRet;
701 aRes <<= aRet;
702 }
703 else
704 {
705 g_pSpellIter->Continue( pPageCnt, pPageSt ) >>= xRet;
706 aRes <<= xRet;
707 }
708 --mnStartAction;
709
710 if( !aRet.isEmpty() || xRet.is() )
711 {
712 // then make awt::Selection again visible
713 StartAction();
714 EndAction();
715 }
716 return aRes;
717 }
718
719 /* Interactive Hyphenation (BP 10.03.93)
720 *
721 * 1) HyphStart
722 * - Revoke all Selections
723 * - Save current Cursor
724 * - if no selections existent:
725 * - create new selection reaching until document end
726 * 2) HyphContinue
727 * - add nLastHyphLen onto SelectionStart
728 * - iterate over all selected areas
729 * - pDoc->Hyphenate() iterates over all Nodes of a selection
730 * - pTextNode->Hyphenate() calls SwTextFrame::Hyphenate of the EditShell
731 * - SwTextFrame:Hyphenate() iterates over all rows of the Pam
732 * - LineIter::Hyphenate() sets the Hyphenator and the Pam based on
733 * the to be separated word.
734 * - Returns true if there is a hyphenation and false if the Pam is processed.
735 * - If true, show the selected word and set nLastHyphLen.
736 * - If false, delete current selection and select next one. Returns HYPH_OK if no more.
737 * 3) InsertSoftHyph (might be called by UI if needed)
738 * - Place current cursor and add attribute.
739 * 4) HyphEnd
740 * - Restore old cursor, EndAction
741 */
HyphStart(SwDocPositions eStart,SwDocPositions eEnd)742 void SwEditShell::HyphStart( SwDocPositions eStart, SwDocPositions eEnd )
743 {
744 // do not hyphenate if interactive hyphenation is active elsewhere
745 if (!g_pHyphIter)
746 {
747 g_pHyphIter = new SwHyphIter;
748 g_pHyphIter->Start( this, eStart, eEnd );
749 }
750 }
751
752 /// restore selections
HyphEnd()753 void SwEditShell::HyphEnd()
754 {
755 assert(g_pHyphIter);
756 if (g_pHyphIter->GetSh() == this)
757 {
758 g_pHyphIter->End();
759 delete g_pHyphIter;
760 g_pHyphIter = nullptr;
761 }
762 }
763
764 /// @returns HYPH_CONTINUE if hyphenation, HYPH_OK if selected area was processed.
765 uno::Reference< uno::XInterface >
HyphContinue(sal_uInt16 * pPageCnt,sal_uInt16 * pPageSt)766 SwEditShell::HyphContinue( sal_uInt16* pPageCnt, sal_uInt16* pPageSt )
767 {
768 assert(g_pHyphIter);
769 if (g_pHyphIter->GetSh() != this)
770 return nullptr;
771
772 if( pPageCnt && !*pPageCnt && !*pPageSt )
773 {
774 sal_uInt16 nEndPage = GetLayout()->GetPageNum();
775 nEndPage += nEndPage * 10 / 100;
776 if( nEndPage > 14 )
777 {
778 *pPageCnt = nEndPage;
779 ::StartProgress( STR_STATSTR_HYPHEN, 0, nEndPage, GetDoc()->GetDocShell());
780 }
781 else // here we once and for all suppress StatLineStartPercent
782 *pPageSt = 1;
783 }
784
785 //JP 18.07.95: prevent displaying selection on error messages. NO StartAction so that all
786 // Paints are also disabled.
787 ++mnStartAction;
788 uno::Reference< uno::XInterface > xRet;
789 g_pHyphIter->Continue( pPageCnt, pPageSt ) >>= xRet;
790 --mnStartAction;
791
792 if( xRet.is() )
793 g_pHyphIter->ShowSelection();
794
795 return xRet;
796 }
797
798 /** Insert soft hyphen
799 *
800 * @param nHyphPos Offset in the to be separated word
801 */
InsertSoftHyph(const sal_Int32 nHyphPos)802 void SwEditShell::InsertSoftHyph( const sal_Int32 nHyphPos )
803 {
804 assert(g_pHyphIter);
805 g_pHyphIter->InsertSoftHyph( nHyphPos );
806 }
807
808 /// ignore hyphenation
HyphIgnore()809 void SwEditShell::HyphIgnore()
810 {
811 assert(g_pHyphIter);
812 //JP 18.07.95: prevent displaying selection on error messages. NO StartAction so that all
813 // Paints are also disabled.
814 ++mnStartAction;
815 g_pHyphIter->Ignore();
816 --mnStartAction;
817
818 g_pHyphIter->ShowSelection();
819 }
820
HandleCorrectionError(const OUString & aText,SwPosition aPos,sal_Int32 nBegin,sal_Int32 nLen,const Point * pPt,SwRect & rSelectRect)821 void SwEditShell::HandleCorrectionError(const OUString& aText, SwPosition aPos, sal_Int32 nBegin,
822 sal_Int32 nLen, const Point* pPt,
823 SwRect& rSelectRect)
824 {
825 // save the start and end positions of the line and the starting point
826 Push();
827 LeftMargin();
828 const sal_Int32 nLineStart = GetCursor()->GetPoint()->nContent.GetIndex();
829 RightMargin();
830 const sal_Int32 nLineEnd = GetCursor()->GetPoint()->nContent.GetIndex();
831 Pop(PopMode::DeleteCurrent);
832
833 // make sure the selection build later from the data below does
834 // not "in word" character to the left and right in order to
835 // preserve those. Therefore count those "in words" in order to
836 // modify the selection accordingly.
837 const sal_Unicode* pChar = aText.getStr();
838 sal_Int32 nLeft = 0;
839 while (*pChar++ == CH_TXTATR_INWORD)
840 ++nLeft;
841 pChar = aText.getLength() ? aText.getStr() + aText.getLength() - 1 : nullptr;
842 sal_Int32 nRight = 0;
843 while (pChar && *pChar-- == CH_TXTATR_INWORD)
844 ++nRight;
845
846 aPos.nContent = nBegin + nLeft;
847 SwPaM* pCursor = GetCursor();
848 *pCursor->GetPoint() = aPos;
849 pCursor->SetMark();
850 ExtendSelection( true, nLen - nLeft - nRight );
851 // don't determine the rectangle in the current line
852 const sal_Int32 nWordStart = (nBegin + nLeft) < nLineStart ? nLineStart : nBegin + nLeft;
853 // take one less than the line end - otherwise the next line would be calculated
854 const sal_Int32 nWordEnd = (nBegin + nLen - nLeft - nRight) > nLineEnd
855 ? nLineEnd : (nBegin + nLen - nLeft - nRight);
856 Push();
857 pCursor->DeleteMark();
858 SwIndex& rContent = GetCursor()->GetPoint()->nContent;
859 rContent = nWordStart;
860 SwRect aStartRect;
861 SwCursorMoveState aState;
862 aState.m_bRealWidth = true;
863 SwContentNode* pContentNode = pCursor->GetContentNode();
864 std::pair<Point, bool> tmp;
865 if (pPt)
866 {
867 tmp.first = *pPt;
868 tmp.second = false;
869 }
870 SwContentFrame *const pContentFrame = pContentNode->getLayoutFrame(GetLayout(), pCursor->GetPoint(), pPt ? &tmp : nullptr);
871
872 pContentFrame->GetCharRect( aStartRect, *pCursor->GetPoint(), &aState );
873 rContent = nWordEnd - 1;
874 SwRect aEndRect;
875 pContentFrame->GetCharRect( aEndRect, *pCursor->GetPoint(),&aState );
876 rSelectRect = aStartRect.Union( aEndRect );
877 Pop(PopMode::DeleteCurrent);
878 }
879
880 /** Get a list of potential corrections for misspelled word.
881 *
882 * If empty, word is unknown but there are no corrections available.
883 * If NULL then the word is not misspelled but correct.
884 *
885 * @brief SwEditShell::GetCorrection
886 * @return list or NULL pointer
887 */
888 uno::Reference< XSpellAlternatives >
GetCorrection(const Point * pPt,SwRect & rSelectRect)889 SwEditShell::GetCorrection( const Point* pPt, SwRect& rSelectRect )
890 {
891 uno::Reference< XSpellAlternatives > xSpellAlt;
892
893 if( IsTableMode() )
894 return nullptr;
895 SwPaM* pCursor = GetCursor();
896 SwPosition aPos( *pCursor->GetPoint() );
897 Point aPt( *pPt );
898 SwCursorMoveState eTmpState( MV_SETONLYTEXT );
899 SwTextNode *pNode = nullptr;
900 SwWrongList *pWrong = nullptr;
901 if (GetLayout()->GetCursorOfst( &aPos, aPt, &eTmpState ))
902 pNode = aPos.nNode.GetNode().GetTextNode();
903 if (nullptr != pNode)
904 pWrong = pNode->GetWrong();
905 if (nullptr != pWrong && !pNode->IsInProtectSect())
906 {
907 sal_Int32 nBegin = aPos.nContent.GetIndex();
908 sal_Int32 nLen = 1;
909 if (pWrong->InWrongWord(nBegin, nLen) && !pNode->IsSymbolAt(nBegin))
910 {
911 const OUString aText(pNode->GetText().copy(nBegin, nLen));
912 OUString aWord = aText.replaceAll(OUStringChar(CH_TXTATR_BREAKWORD), "")
913 .replaceAll(OUStringChar(CH_TXTATR_INWORD), "");
914
915 uno::Reference< XSpellChecker1 > xSpell( ::GetSpellChecker() );
916 if( xSpell.is() )
917 {
918 LanguageType eActLang = pNode->GetLang( nBegin, nLen );
919 if( xSpell->hasLanguage( static_cast<sal_uInt16>(eActLang) ))
920 {
921 // restrict the maximal number of suggestions displayed
922 // in the context menu.
923 // Note: That could of course be done by clipping the
924 // resulting sequence but the current third party
925 // implementations result differs greatly if the number of
926 // suggestions to be returned gets changed. Statistically
927 // it gets much better if told to return e.g. only 7 strings
928 // than returning e.g. 16 suggestions and using only the
929 // first 7. Thus we hand down the value to use to that
930 // implementation here by providing an additional parameter.
931 Sequence< PropertyValue > aPropVals(1);
932 PropertyValue &rVal = aPropVals.getArray()[0];
933 rVal.Name = UPN_MAX_NUMBER_OF_SUGGESTIONS;
934 rVal.Value <<= sal_Int16(7);
935
936 xSpellAlt = xSpell->spell( aWord, static_cast<sal_uInt16>(eActLang), aPropVals );
937 }
938 }
939
940 if ( xSpellAlt.is() ) // error found?
941 {
942 HandleCorrectionError( aText, aPos, nBegin, nLen, pPt, rSelectRect );
943 }
944 }
945 }
946 return xSpellAlt;
947 }
948
GetGrammarCorrection(linguistic2::ProofreadingResult & rResult,sal_Int32 & rErrorPosInText,sal_Int32 & rErrorIndexInResult,uno::Sequence<OUString> & rSuggestions,const Point * pPt,SwRect & rSelectRect)949 bool SwEditShell::GetGrammarCorrection(
950 linguistic2::ProofreadingResult /*out*/ &rResult, // the complete result
951 sal_Int32 /*out*/ &rErrorPosInText, // offset of error position in string that was grammar checked...
952 sal_Int32 /*out*/ &rErrorIndexInResult, // index of error in rResult.aGrammarErrors
953 uno::Sequence< OUString > /*out*/ &rSuggestions, // suggestions to be used for the error found
954 const Point *pPt, SwRect &rSelectRect )
955 {
956 bool bRes = false;
957
958 if( IsTableMode() )
959 return bRes;
960
961 SwPaM* pCursor = GetCursor();
962 SwPosition aPos( *pCursor->GetPoint() );
963 Point aPt( *pPt );
964 SwCursorMoveState eTmpState( MV_SETONLYTEXT );
965 SwTextNode *pNode = nullptr;
966 SwGrammarMarkUp *pWrong = nullptr;
967 if (GetLayout()->GetCursorOfst( &aPos, aPt, &eTmpState ))
968 pNode = aPos.nNode.GetNode().GetTextNode();
969 if (nullptr != pNode)
970 pWrong = pNode->GetGrammarCheck();
971 if (nullptr != pWrong && !pNode->IsInProtectSect())
972 {
973 sal_Int32 nBegin = aPos.nContent.GetIndex();
974 sal_Int32 nLen = 1;
975 if (pWrong->InWrongWord(nBegin, nLen))
976 {
977 const OUString aText(pNode->GetText().copy(nBegin, nLen));
978
979 uno::Reference< linguistic2::XProofreadingIterator > xGCIterator( mxDoc->GetGCIterator() );
980 if (xGCIterator.is())
981 {
982 uno::Reference< lang::XComponent > xDoc( mxDoc->GetDocShell()->GetBaseModel(), uno::UNO_QUERY );
983
984 // Expand the string:
985 const ModelToViewHelper aConversionMap(*pNode, GetLayout());
986 const OUString& aExpandText = aConversionMap.getViewText();
987 // get XFlatParagraph to use...
988 uno::Reference< text::XFlatParagraph > xFlatPara = new SwXFlatParagraph( *pNode, aExpandText, aConversionMap );
989
990 // get error position of cursor in XFlatParagraph
991 rErrorPosInText = aConversionMap.ConvertToViewPosition( nBegin );
992
993 const sal_Int32 nStartOfSentence = aConversionMap.ConvertToViewPosition( pWrong->getSentenceStart( nBegin ) );
994 const sal_Int32 nEndOfSentence = aConversionMap.ConvertToViewPosition( pWrong->getSentenceEnd( nBegin ) );
995
996 rResult = xGCIterator->checkSentenceAtPosition(
997 xDoc, xFlatPara, aExpandText, lang::Locale(), nStartOfSentence,
998 nEndOfSentence == COMPLETE_STRING ? aExpandText.getLength() : nEndOfSentence,
999 rErrorPosInText );
1000 bRes = true;
1001
1002 // get suggestions to use for the specific error position
1003 rSuggestions.realloc( 0 );
1004 // return suggestions for first error that includes the given error position
1005 auto pError = std::find_if(rResult.aErrors.begin(), rResult.aErrors.end(),
1006 [rErrorPosInText, nLen](const linguistic2::SingleProofreadingError &rError) {
1007 return rError.nErrorStart <= rErrorPosInText
1008 && rErrorPosInText + nLen <= rError.nErrorStart + rError.nErrorLength; });
1009 if (pError != rResult.aErrors.end())
1010 {
1011 rSuggestions = pError->aSuggestions;
1012 rErrorIndexInResult = static_cast<sal_Int32>(std::distance(rResult.aErrors.begin(), pError));
1013 }
1014 }
1015
1016 if (rResult.aErrors.hasElements()) // error found?
1017 {
1018 HandleCorrectionError( aText, aPos, nBegin, nLen, pPt, rSelectRect );
1019 }
1020 }
1021 }
1022
1023 return bRes;
1024 }
1025
SpellSentence(svx::SpellPortions & rPortions,bool bIsGrammarCheck)1026 bool SwEditShell::SpellSentence(svx::SpellPortions& rPortions, bool bIsGrammarCheck)
1027 {
1028 OSL_ENSURE( g_pSpellIter, "SpellIter missing" );
1029 if (!g_pSpellIter)
1030 return false;
1031 bool bRet = g_pSpellIter->SpellSentence(rPortions, bIsGrammarCheck);
1032
1033 // make Selection visible - this should simply move the
1034 // cursor to the end of the sentence
1035 StartAction();
1036 EndAction();
1037 return bRet;
1038 }
1039
1040 ///make SpellIter start with the current sentence when called next time
PutSpellingToSentenceStart()1041 void SwEditShell::PutSpellingToSentenceStart()
1042 {
1043 OSL_ENSURE( g_pSpellIter, "SpellIter missing" );
1044 if (!g_pSpellIter)
1045 return;
1046 g_pSpellIter->ToSentenceStart();
1047 }
1048
lcl_CountRedlines(const svx::SpellPortions & rLastPortions)1049 static sal_uInt32 lcl_CountRedlines(const svx::SpellPortions& rLastPortions)
1050 {
1051 return static_cast<sal_uInt32>(std::count_if(rLastPortions.begin(), rLastPortions.end(),
1052 [](const svx::SpellPortion& rPortion) { return rPortion.bIsHidden; }));
1053 }
1054
MoveContinuationPosToEndOfCheckedSentence()1055 void SwEditShell::MoveContinuationPosToEndOfCheckedSentence()
1056 {
1057 // give hint that continuation position for spell/grammar checking is
1058 // at the end of this sentence
1059 if (g_pSpellIter)
1060 {
1061 g_pSpellIter->SetCurr( new SwPosition( *g_pSpellIter->GetCurrX() ) );
1062 }
1063 }
1064
ApplyChangedSentence(const svx::SpellPortions & rNewPortions,bool bRecheck)1065 void SwEditShell::ApplyChangedSentence(const svx::SpellPortions& rNewPortions, bool bRecheck)
1066 {
1067 // Note: rNewPortions.size() == 0 is valid and happens when the whole
1068 // sentence got removed in the dialog
1069
1070 OSL_ENSURE( g_pSpellIter, "SpellIter missing" );
1071 if (!g_pSpellIter ||
1072 g_pSpellIter->GetLastPortions().empty()) // no portions -> no text to be changed
1073 return;
1074
1075 const SpellPortions& rLastPortions = g_pSpellIter->GetLastPortions();
1076 const SpellContentPositions rLastPositions = g_pSpellIter->GetLastPositions();
1077 OSL_ENSURE(!rLastPortions.empty() &&
1078 rLastPortions.size() == rLastPositions.size(),
1079 "last vectors of spelling results are not set or not equal");
1080
1081 // iterate over the new portions, beginning at the end to take advantage of the previously
1082 // saved content positions
1083
1084 mxDoc->GetIDocumentUndoRedo().StartUndo( SwUndoId::UI_TEXT_CORRECTION, nullptr );
1085 StartAction();
1086
1087 SwPaM *pCursor = GetCursor();
1088 // save cursor position (which should be at the end of the current sentence)
1089 // for later restoration
1090 Push();
1091
1092 sal_uInt32 nRedlinePortions = lcl_CountRedlines(rLastPortions);
1093 if((rLastPortions.size() - nRedlinePortions) == rNewPortions.size())
1094 {
1095 OSL_ENSURE( !rNewPortions.empty(), "rNewPortions should not be empty here" );
1096 OSL_ENSURE( !rLastPortions.empty(), "rLastPortions should not be empty here" );
1097 OSL_ENSURE( !rLastPositions.empty(), "rLastPositions should not be empty here" );
1098
1099 // the simple case: the same number of elements on both sides
1100 // each changed element has to be applied to the corresponding source element
1101 svx::SpellPortions::const_iterator aCurrentNewPortion = rNewPortions.end();
1102 SpellPortions::const_iterator aCurrentOldPortion = rLastPortions.end();
1103 SpellContentPositions::const_iterator aCurrentOldPosition = rLastPositions.end();
1104 do
1105 {
1106 --aCurrentNewPortion;
1107 --aCurrentOldPortion;
1108 --aCurrentOldPosition;
1109 //jump over redline portions
1110 while(aCurrentOldPortion->bIsHidden)
1111 {
1112 if (aCurrentOldPortion != rLastPortions.begin() &&
1113 aCurrentOldPosition != rLastPositions.begin())
1114 {
1115 --aCurrentOldPortion;
1116 --aCurrentOldPosition;
1117 }
1118 else
1119 {
1120 OSL_FAIL("ApplyChangedSentence: iterator positions broken" );
1121 break;
1122 }
1123 }
1124 if ( !pCursor->HasMark() )
1125 pCursor->SetMark();
1126 pCursor->GetPoint()->nContent = aCurrentOldPosition->nLeft;
1127 pCursor->GetMark()->nContent = aCurrentOldPosition->nRight;
1128 sal_uInt16 nScriptType = SvtLanguageOptions::GetI18NScriptTypeOfLanguage( aCurrentNewPortion->eLanguage );
1129 sal_uInt16 nLangWhichId = RES_CHRATR_LANGUAGE;
1130 switch(nScriptType)
1131 {
1132 case css::i18n::ScriptType::ASIAN : nLangWhichId = RES_CHRATR_CJK_LANGUAGE; break;
1133 case css::i18n::ScriptType::COMPLEX : nLangWhichId = RES_CHRATR_CTL_LANGUAGE; break;
1134 }
1135 if(aCurrentNewPortion->sText != aCurrentOldPortion->sText)
1136 {
1137 // change text ...
1138 // ... and apply language if necessary
1139 if(aCurrentNewPortion->eLanguage != aCurrentOldPortion->eLanguage)
1140 SetAttrItem( SvxLanguageItem(aCurrentNewPortion->eLanguage, nLangWhichId) );
1141 mxDoc->getIDocumentContentOperations().ReplaceRange(*pCursor, aCurrentNewPortion->sText, false);
1142 }
1143 else if(aCurrentNewPortion->eLanguage != aCurrentOldPortion->eLanguage)
1144 {
1145 // apply language
1146 SetAttrItem( SvxLanguageItem(aCurrentNewPortion->eLanguage, nLangWhichId) );
1147 }
1148 else if( aCurrentNewPortion->bIgnoreThisError )
1149 {
1150 // add the 'ignore' markup to the TextNode's grammar ignore markup list
1151 IgnoreGrammarErrorAt( *pCursor );
1152 OSL_FAIL("TODO: add ignore mark to text node");
1153 }
1154 }
1155 while(aCurrentNewPortion != rNewPortions.begin());
1156 }
1157 else
1158 {
1159 OSL_ENSURE( !rLastPositions.empty(), "rLastPositions should not be empty here" );
1160
1161 // select the complete sentence
1162 SpellContentPositions::const_iterator aCurrentEndPosition = rLastPositions.end();
1163 --aCurrentEndPosition;
1164 SpellContentPositions::const_iterator aCurrentStartPosition = rLastPositions.begin();
1165 pCursor->GetPoint()->nContent = aCurrentStartPosition->nLeft;
1166 pCursor->GetMark()->nContent = aCurrentEndPosition->nRight;
1167
1168 // delete the sentence completely
1169 mxDoc->getIDocumentContentOperations().DeleteAndJoin(*pCursor);
1170 for(const auto& rCurrentNewPortion : rNewPortions)
1171 {
1172 // set the language attribute
1173 SvtScriptType nScriptType = GetScriptType();
1174 sal_uInt16 nLangWhichId = RES_CHRATR_LANGUAGE;
1175 switch(nScriptType)
1176 {
1177 case SvtScriptType::ASIAN : nLangWhichId = RES_CHRATR_CJK_LANGUAGE; break;
1178 case SvtScriptType::COMPLEX : nLangWhichId = RES_CHRATR_CTL_LANGUAGE; break;
1179 default: break;
1180 }
1181 SfxItemSet aSet(GetAttrPool(), {{nLangWhichId, nLangWhichId}});
1182 GetCurAttr( aSet );
1183 const SvxLanguageItem& rLang = static_cast<const SvxLanguageItem& >(aSet.Get(nLangWhichId));
1184 if(rLang.GetLanguage() != rCurrentNewPortion.eLanguage)
1185 SetAttrItem( SvxLanguageItem(rCurrentNewPortion.eLanguage, nLangWhichId) );
1186 // insert the new string
1187 mxDoc->getIDocumentContentOperations().InsertString(*pCursor, rCurrentNewPortion.sText);
1188
1189 // set the cursor to the end of the inserted string
1190 *pCursor->Start() = *pCursor->End();
1191 }
1192 }
1193
1194 // restore cursor to the end of the sentence
1195 // (will work also if the sentence length has changed,
1196 // since cursors get updated automatically!)
1197 Pop(PopMode::DeleteCurrent);
1198
1199 // collapse cursor to the end of the modified sentence
1200 *pCursor->Start() = *pCursor->End();
1201 if (bRecheck)
1202 {
1203 // in grammar check the current sentence has to be checked again
1204 GoStartSentence();
1205 }
1206 // set continuation position for spell/grammar checking to the end of this sentence
1207 g_pSpellIter->SetCurr( new SwPosition(*pCursor->Start()) );
1208
1209 mxDoc->GetIDocumentUndoRedo().EndUndo( SwUndoId::UI_TEXT_CORRECTION, nullptr );
1210 EndAction();
1211
1212 }
1213 /** Collect all deleted redlines of the current text node
1214 * beginning at the start of the cursor position
1215 */
lcl_CollectDeletedRedlines(SwEditShell const * pSh)1216 static SpellContentPositions lcl_CollectDeletedRedlines(SwEditShell const * pSh)
1217 {
1218 SpellContentPositions aRedlines;
1219 SwDoc* pDoc = pSh->GetDoc();
1220 const bool bShowChg = IDocumentRedlineAccess::IsShowChanges( pDoc->getIDocumentRedlineAccess().GetRedlineFlags() );
1221 if ( bShowChg )
1222 {
1223 SwPaM *pCursor = pSh->GetCursor();
1224 const SwPosition* pStartPos = pCursor->Start();
1225 const SwTextNode* pTextNode = pCursor->GetNode().GetTextNode();
1226
1227 SwRedlineTable::size_type nAct = pDoc->getIDocumentRedlineAccess().GetRedlinePos( *pTextNode, RedlineType::Any );
1228 const sal_Int32 nStartIndex = pStartPos->nContent.GetIndex();
1229 for ( ; nAct < pDoc->getIDocumentRedlineAccess().GetRedlineTable().size(); nAct++ )
1230 {
1231 const SwRangeRedline* pRed = pDoc->getIDocumentRedlineAccess().GetRedlineTable()[ nAct ];
1232
1233 if ( pRed->Start()->nNode > pTextNode->GetIndex() )
1234 break;
1235
1236 if( RedlineType::Delete == pRed->GetType() )
1237 {
1238 sal_Int32 nStart_, nEnd_;
1239 pRed->CalcStartEnd( pTextNode->GetIndex(), nStart_, nEnd_ );
1240 sal_Int32 nStart = nStart_;
1241 sal_Int32 nEnd = nEnd_;
1242 if(nStart >= nStartIndex || nEnd >= nStartIndex)
1243 {
1244 SpellContentPosition aAdd;
1245 aAdd.nLeft = nStart;
1246 aAdd.nRight = nEnd;
1247 aRedlines.push_back(aAdd);
1248 }
1249 }
1250 }
1251 }
1252 return aRedlines;
1253 }
1254
1255 /// remove the redline positions after the current selection
lcl_CutRedlines(SpellContentPositions & aDeletedRedlines,SwEditShell const * pSh)1256 static void lcl_CutRedlines( SpellContentPositions& aDeletedRedlines, SwEditShell const * pSh )
1257 {
1258 if(!aDeletedRedlines.empty())
1259 {
1260 SwPaM *pCursor = pSh->GetCursor();
1261 const SwPosition* pEndPos = pCursor->End();
1262 const sal_Int32 nEnd = pEndPos->nContent.GetIndex();
1263 while(!aDeletedRedlines.empty() &&
1264 aDeletedRedlines.back().nLeft > nEnd)
1265 {
1266 aDeletedRedlines.pop_back();
1267 }
1268 }
1269 }
1270
lcl_FindNextDeletedRedline(const SpellContentPositions & rDeletedRedlines,sal_Int32 nSearchFrom)1271 static SpellContentPosition lcl_FindNextDeletedRedline(
1272 const SpellContentPositions& rDeletedRedlines,
1273 sal_Int32 nSearchFrom )
1274 {
1275 SpellContentPosition aRet;
1276 aRet.nLeft = aRet.nRight = SAL_MAX_INT32;
1277 if(!rDeletedRedlines.empty())
1278 {
1279 auto aIter = std::find_if_not(rDeletedRedlines.begin(), rDeletedRedlines.end(),
1280 [nSearchFrom](const SpellContentPosition& rPos) { return rPos.nLeft < nSearchFrom; });
1281 if (aIter != rDeletedRedlines.end())
1282 aRet = *aIter;
1283 }
1284 return aRet;
1285 }
1286
SpellSentence(svx::SpellPortions & rPortions,bool bIsGrammarCheck)1287 bool SwSpellIter::SpellSentence(svx::SpellPortions& rPortions, bool bIsGrammarCheck)
1288 {
1289 bool bRet = false;
1290 aLastPortions.clear();
1291 aLastPositions.clear();
1292
1293 SwEditShell *pMySh = GetSh();
1294 if( !pMySh )
1295 return false;
1296
1297 OSL_ENSURE( GetEnd(), "SwSpellIter::SpellSentence without Start?");
1298
1299 uno::Reference< XSpellAlternatives > xSpellRet;
1300 linguistic2::ProofreadingResult aGrammarResult;
1301 bool bGoOn = true;
1302 bool bGrammarErrorFound = false;
1303 do {
1304 SwPaM *pCursor = pMySh->GetCursor();
1305 if ( !pCursor->HasMark() )
1306 pCursor->SetMark();
1307
1308 *pCursor->GetPoint() = *GetCurr();
1309 *pCursor->GetMark() = *GetEnd();
1310
1311 if( bBackToStartOfSentence )
1312 {
1313 pMySh->GoStartSentence();
1314 bBackToStartOfSentence = false;
1315 }
1316 uno::Any aSpellRet =
1317 pMySh->GetDoc()->Spell(*pCursor,
1318 xSpeller, nullptr, nullptr, bIsGrammarCheck, pMySh->GetLayout());
1319 aSpellRet >>= xSpellRet;
1320 aSpellRet >>= aGrammarResult;
1321 bGoOn = GetCursorCnt() > 1;
1322 bGrammarErrorFound = aGrammarResult.aErrors.hasElements();
1323 if( xSpellRet.is() || bGrammarErrorFound )
1324 {
1325 bGoOn = false;
1326 SwPosition* pNewPoint = new SwPosition( *pCursor->GetPoint() );
1327 SwPosition* pNewMark = new SwPosition( *pCursor->GetMark() );
1328
1329 SetCurr( pNewPoint );
1330 SetCurrX( pNewMark );
1331 }
1332 if( bGoOn )
1333 {
1334 pMySh->Pop(SwCursorShell::PopMode::DeleteCurrent);
1335 pCursor = pMySh->GetCursor();
1336 if ( *pCursor->GetPoint() > *pCursor->GetMark() )
1337 pCursor->Exchange();
1338 SwPosition* pNew = new SwPosition( *pCursor->GetPoint() );
1339 SetStart( pNew );
1340 pNew = new SwPosition( *pCursor->GetMark() );
1341 SetEnd( pNew );
1342 pNew = new SwPosition( *GetStart() );
1343 SetCurr( pNew );
1344 pNew = new SwPosition( *pNew );
1345 SetCurrX( pNew );
1346 pCursor->SetMark();
1347 --GetCursorCnt();
1348 }
1349 } while ( bGoOn );
1350
1351 if(xSpellRet.is() || bGrammarErrorFound)
1352 {
1353 // an error has been found
1354 // To fill the spell portions the beginning of the sentence has to be found
1355 SwPaM *pCursor = pMySh->GetCursor();
1356 // set the mark to the right if necessary
1357 if ( *pCursor->GetPoint() > *pCursor->GetMark() )
1358 pCursor->Exchange();
1359 // the cursor has to be collapsed on the left to go to the start of the sentence - if sentence ends inside of the error
1360 pCursor->DeleteMark();
1361 pCursor->SetMark();
1362 bool bStartSent = pMySh->GoStartSentence();
1363 SpellContentPositions aDeletedRedlines = lcl_CollectDeletedRedlines(pMySh);
1364 if(bStartSent)
1365 {
1366 // create a portion from the start part
1367 AddPortion(nullptr, nullptr, aDeletedRedlines);
1368 }
1369 // Set the cursor to the error already found
1370 *pCursor->GetPoint() = *GetCurrX();
1371 *pCursor->GetMark() = *GetCurr();
1372 AddPortion(xSpellRet, &aGrammarResult, aDeletedRedlines);
1373
1374 // save the end position of the error to continue from here
1375 SwPosition aSaveStartPos = *pCursor->End();
1376 // determine the end of the current sentence
1377 if ( *pCursor->GetPoint() < *pCursor->GetMark() )
1378 pCursor->Exchange();
1379 // again collapse to start marking after the end of the error
1380 pCursor->DeleteMark();
1381 pCursor->SetMark();
1382
1383 pMySh->GoEndSentence();
1384 if( bGrammarErrorFound )
1385 {
1386 const ModelToViewHelper aConversionMap(static_cast<SwTextNode&>(pCursor->GetNode()), pMySh->GetLayout());
1387 const OUString& aExpandText = aConversionMap.getViewText();
1388 sal_Int32 nSentenceEnd =
1389 aConversionMap.ConvertToViewPosition( aGrammarResult.nBehindEndOfSentencePosition );
1390 // remove trailing space
1391 if( aExpandText[nSentenceEnd - 1] == ' ' )
1392 --nSentenceEnd;
1393 if( pCursor->End()->nContent.GetIndex() < nSentenceEnd )
1394 {
1395 pCursor->End()->nContent.Assign(
1396 pCursor->End()->nNode.GetNode().GetContentNode(), nSentenceEnd);
1397 }
1398 }
1399
1400 lcl_CutRedlines( aDeletedRedlines, pMySh );
1401 // save the 'global' end of the spellchecking
1402 const SwPosition aSaveEndPos = *GetEnd();
1403 // set the sentence end as 'local' end
1404 SetEnd( new SwPosition( *pCursor->End() ));
1405
1406 *pCursor->GetPoint() = aSaveStartPos;
1407 *pCursor->GetMark() = *GetEnd();
1408 // now the rest of the sentence has to be searched for errors
1409 // for each error the non-error text between the current and the last error has
1410 // to be added to the portions - if necessary broken into same-language-portions
1411 if( !bGrammarErrorFound ) //in grammar check there's only one error returned
1412 {
1413 do
1414 {
1415 xSpellRet = nullptr;
1416 // don't search for grammar errors here anymore!
1417 pMySh->GetDoc()->Spell(*pCursor,
1418 xSpeller, nullptr, nullptr, false, pMySh->GetLayout()) >>= xSpellRet;
1419 if ( *pCursor->GetPoint() > *pCursor->GetMark() )
1420 pCursor->Exchange();
1421 SetCurr( new SwPosition( *pCursor->GetPoint() ));
1422 SetCurrX( new SwPosition( *pCursor->GetMark() ));
1423
1424 // if an error has been found go back to the text preceding the error
1425 if(xSpellRet.is())
1426 {
1427 *pCursor->GetPoint() = aSaveStartPos;
1428 *pCursor->GetMark() = *GetCurr();
1429 }
1430 // add the portion
1431 AddPortion(nullptr, nullptr, aDeletedRedlines);
1432
1433 if(xSpellRet.is())
1434 {
1435 *pCursor->GetPoint() = *GetCurr();
1436 *pCursor->GetMark() = *GetCurrX();
1437 AddPortion(xSpellRet, nullptr, aDeletedRedlines);
1438 // move the cursor to the end of the error string
1439 *pCursor->GetPoint() = *GetCurrX();
1440 // and save the end of the error as new start position
1441 aSaveStartPos = *GetCurrX();
1442 // and the end of the sentence
1443 *pCursor->GetMark() = *GetEnd();
1444 }
1445 // if the end of the sentence has already been reached then break here
1446 if(*GetCurrX() >= *GetEnd())
1447 break;
1448 }
1449 while(xSpellRet.is());
1450 }
1451 else
1452 {
1453 // go to the end of sentence as the grammar check returned it
1454 // at this time the Point is behind the grammar error
1455 // and the mark points to the sentence end as
1456 if ( *pCursor->GetPoint() < *pCursor->GetMark() )
1457 pCursor->Exchange();
1458 }
1459
1460 // the part between the last error and the end of the sentence has to be added
1461 *pMySh->GetCursor()->GetPoint() = *GetEnd();
1462 if(*GetCurrX() < *GetEnd())
1463 {
1464 AddPortion(nullptr, nullptr, aDeletedRedlines);
1465 }
1466 // set the shell cursor to the end of the sentence to prevent a visible selection
1467 *pCursor->GetMark() = *GetEnd();
1468 if( !bIsGrammarCheck )
1469 {
1470 // set the current position to the end of the sentence
1471 SetCurr( new SwPosition(*GetEnd()) );
1472 }
1473 // restore the 'global' end
1474 SetEnd( new SwPosition(aSaveEndPos) );
1475 rPortions = aLastPortions;
1476 bRet = true;
1477 }
1478 else
1479 {
1480 // if no error could be found the selection has to be corrected - at least if it's not in the body
1481 *pMySh->GetCursor()->GetPoint() = *GetEnd();
1482 pMySh->GetCursor()->DeleteMark();
1483 }
1484
1485 return bRet;
1486 }
1487
ToSentenceStart()1488 void SwSpellIter::ToSentenceStart()
1489 {
1490 bBackToStartOfSentence = true;
1491 }
1492
lcl_GetLanguage(SwEditShell & rSh)1493 static LanguageType lcl_GetLanguage(SwEditShell& rSh)
1494 {
1495 SvtScriptType nScriptType = rSh.GetScriptType();
1496 sal_uInt16 nLangWhichId = RES_CHRATR_LANGUAGE;
1497
1498 switch(nScriptType)
1499 {
1500 case SvtScriptType::ASIAN : nLangWhichId = RES_CHRATR_CJK_LANGUAGE; break;
1501 case SvtScriptType::COMPLEX : nLangWhichId = RES_CHRATR_CTL_LANGUAGE; break;
1502 default: break;
1503 }
1504 SfxItemSet aSet(rSh.GetAttrPool(), {{nLangWhichId, nLangWhichId}});
1505 rSh.GetCurAttr( aSet );
1506 const SvxLanguageItem& rLang = static_cast<const SvxLanguageItem& >(aSet.Get(nLangWhichId));
1507 return rLang.GetLanguage();
1508 }
1509
1510 /// create a text portion at the given position
CreatePortion(uno::Reference<XSpellAlternatives> const & xAlt,linguistic2::ProofreadingResult * pGrammarResult,bool bIsField,bool bIsHidden)1511 void SwSpellIter::CreatePortion(uno::Reference< XSpellAlternatives > const & xAlt,
1512 linguistic2::ProofreadingResult* pGrammarResult,
1513 bool bIsField, bool bIsHidden)
1514 {
1515 svx::SpellPortion aPortion;
1516 OUString sText;
1517 GetSh()->GetSelectedText( sText );
1518 if(sText.isEmpty())
1519 return;
1520
1521 // in case of redlined deletions the selection of an error is not the same as the _real_ word
1522 if(xAlt.is())
1523 aPortion.sText = xAlt->getWord();
1524 else if(pGrammarResult)
1525 {
1526 aPortion.bIsGrammarError = true;
1527 if(pGrammarResult->aErrors.hasElements())
1528 {
1529 aPortion.aGrammarError = pGrammarResult->aErrors[0];
1530 aPortion.sText = pGrammarResult->aText.copy( aPortion.aGrammarError.nErrorStart, aPortion.aGrammarError.nErrorLength );
1531 aPortion.xGrammarChecker = pGrammarResult->xProofreader;
1532 auto pProperty = std::find_if(pGrammarResult->aProperties.begin(), pGrammarResult->aProperties.end(),
1533 [](const beans::PropertyValue& rProperty) { return rProperty.Name == "DialogTitle"; });
1534 if (pProperty != pGrammarResult->aProperties.end())
1535 pProperty->Value >>= aPortion.sDialogTitle;
1536 }
1537 }
1538 else
1539 aPortion.sText = sText;
1540 aPortion.eLanguage = lcl_GetLanguage(*GetSh());
1541 aPortion.bIsField = bIsField;
1542 aPortion.bIsHidden = bIsHidden;
1543 aPortion.xAlternatives = xAlt;
1544 SpellContentPosition aPosition;
1545 SwPaM *pCursor = GetSh()->GetCursor();
1546 aPosition.nLeft = pCursor->Start()->nContent.GetIndex();
1547 aPosition.nRight = pCursor->End()->nContent.GetIndex();
1548 aLastPortions.push_back(aPortion);
1549 aLastPositions.push_back(aPosition);
1550
1551 }
1552
AddPortion(uno::Reference<XSpellAlternatives> const & xAlt,linguistic2::ProofreadingResult * pGrammarResult,const SpellContentPositions & rDeletedRedlines)1553 void SwSpellIter::AddPortion(uno::Reference< XSpellAlternatives > const & xAlt,
1554 linguistic2::ProofreadingResult* pGrammarResult,
1555 const SpellContentPositions& rDeletedRedlines)
1556 {
1557 SwEditShell *pMySh = GetSh();
1558 OUString sText;
1559 pMySh->GetSelectedText( sText );
1560 if(!sText.isEmpty())
1561 {
1562 if(xAlt.is() || pGrammarResult != nullptr)
1563 {
1564 CreatePortion(xAlt, pGrammarResult, false, false);
1565 }
1566 else
1567 {
1568 SwPaM *pCursor = GetSh()->GetCursor();
1569 if ( *pCursor->GetPoint() > *pCursor->GetMark() )
1570 pCursor->Exchange();
1571 // save the start and end positions
1572 SwPosition aStart(*pCursor->GetPoint());
1573 SwPosition aEnd(*pCursor->GetMark());
1574 // iterate over the text to find changes in language
1575 // set the mark equal to the point
1576 *pCursor->GetMark() = aStart;
1577 SwTextNode* pTextNode = pCursor->GetNode().GetTextNode();
1578 LanguageType eStartLanguage = lcl_GetLanguage(*GetSh());
1579 SpellContentPosition aNextRedline = lcl_FindNextDeletedRedline(
1580 rDeletedRedlines, aStart.nContent.GetIndex() );
1581 if( aNextRedline.nLeft == aStart.nContent.GetIndex() )
1582 {
1583 // select until the end of the current redline
1584 const sal_Int32 nEnd = aEnd.nContent.GetIndex() < aNextRedline.nRight ?
1585 aEnd.nContent.GetIndex() : aNextRedline.nRight;
1586 pCursor->GetPoint()->nContent.Assign( pTextNode, nEnd );
1587 CreatePortion(xAlt, pGrammarResult, false, true);
1588 aStart = *pCursor->End();
1589 // search for next redline
1590 aNextRedline = lcl_FindNextDeletedRedline(
1591 rDeletedRedlines, aStart.nContent.GetIndex() );
1592 }
1593 while(*pCursor->GetPoint() < aEnd)
1594 {
1595 // #125786 in table cell with fixed row height the cursor might not move forward
1596 if(!GetSh()->Right(1, CRSR_SKIP_CELLS))
1597 break;
1598
1599 bool bField = false;
1600 // read the character at the current position to check if it's a field
1601 sal_Unicode const cChar =
1602 pTextNode->GetText()[pCursor->GetMark()->nContent.GetIndex()];
1603 if( CH_TXTATR_BREAKWORD == cChar || CH_TXTATR_INWORD == cChar)
1604 {
1605 const SwTextAttr* pTextAttr = pTextNode->GetTextAttrForCharAt(
1606 pCursor->GetMark()->nContent.GetIndex() );
1607 const sal_uInt16 nWhich = pTextAttr
1608 ? pTextAttr->Which()
1609 : static_cast<sal_uInt16>(RES_TXTATR_END);
1610 switch (nWhich)
1611 {
1612 case RES_TXTATR_FIELD:
1613 case RES_TXTATR_ANNOTATION:
1614 case RES_TXTATR_FTN:
1615 case RES_TXTATR_FLYCNT:
1616 bField = true;
1617 break;
1618 }
1619 }
1620 else if (cChar == CH_TXT_ATR_FORMELEMENT)
1621 {
1622 SwPosition aPos(*pCursor->GetMark());
1623 bField = pMySh->GetDoc()->getIDocumentMarkAccess()->getDropDownFor(aPos);
1624 }
1625
1626 LanguageType eCurLanguage = lcl_GetLanguage(*GetSh());
1627 bool bRedline = aNextRedline.nLeft == pCursor->GetPoint()->nContent.GetIndex();
1628 // create a portion if the next character
1629 // - is a field,
1630 // - is at the beginning of a deleted redline
1631 // - has a different language
1632 if(bField || bRedline || eCurLanguage != eStartLanguage)
1633 {
1634 eStartLanguage = eCurLanguage;
1635 // go one step back - the cursor currently selects the first character
1636 // with a different language
1637 // in the case of redlining it's different
1638 if(eCurLanguage != eStartLanguage || bField)
1639 *pCursor->GetPoint() = *pCursor->GetMark();
1640 // set to the last start
1641 *pCursor->GetMark() = aStart;
1642 // create portion should only be called if a selection exists
1643 // there's no selection if there's a field at the beginning
1644 if(*pCursor->Start() != *pCursor->End())
1645 CreatePortion(xAlt, pGrammarResult, false, false);
1646 aStart = *pCursor->End();
1647 // now export the field - if there is any
1648 if(bField)
1649 {
1650 *pCursor->GetMark() = *pCursor->GetPoint();
1651 GetSh()->Right(1, CRSR_SKIP_CELLS);
1652 CreatePortion(xAlt, pGrammarResult, true, false);
1653 aStart = *pCursor->End();
1654 }
1655 }
1656 // if a redline start then create a portion for it
1657 if(bRedline)
1658 {
1659 *pCursor->GetMark() = *pCursor->GetPoint();
1660 // select until the end of the current redline
1661 const sal_Int32 nEnd = aEnd.nContent.GetIndex() < aNextRedline.nRight ?
1662 aEnd.nContent.GetIndex() : aNextRedline.nRight;
1663 pCursor->GetPoint()->nContent.Assign( pTextNode, nEnd );
1664 CreatePortion(xAlt, pGrammarResult, false, true);
1665 aStart = *pCursor->End();
1666 // search for next redline
1667 aNextRedline = lcl_FindNextDeletedRedline(
1668 rDeletedRedlines, aStart.nContent.GetIndex() );
1669 }
1670 *pCursor->GetMark() = *pCursor->GetPoint();
1671 }
1672 pCursor->SetMark();
1673 *pCursor->GetMark() = aStart;
1674 CreatePortion(xAlt, pGrammarResult, false, false);
1675 }
1676 }
1677 }
1678
IgnoreGrammarErrorAt(SwPaM & rErrorPosition)1679 void SwEditShell::IgnoreGrammarErrorAt( SwPaM& rErrorPosition )
1680 {
1681 SwTextNode *pNode;
1682 SwWrongList *pWrong;
1683 SwNodeIndex aIdx = rErrorPosition.Start()->nNode;
1684 SwNodeIndex aEndIdx = rErrorPosition.Start()->nNode;
1685 sal_Int32 nStart = rErrorPosition.Start()->nContent.GetIndex();
1686 sal_Int32 nEnd = COMPLETE_STRING;
1687 while( aIdx <= aEndIdx )
1688 {
1689 pNode = aIdx.GetNode().GetTextNode();
1690 if( pNode ) {
1691 if( aIdx == aEndIdx )
1692 nEnd = rErrorPosition.End()->nContent.GetIndex();
1693 pWrong = pNode->GetGrammarCheck();
1694 if( pWrong )
1695 pWrong->RemoveEntry( nStart, nEnd );
1696 pWrong = pNode->GetWrong();
1697 if( pWrong )
1698 pWrong->RemoveEntry( nStart, nEnd );
1699 SwTextFrame::repaintTextFrames( *pNode );
1700 }
1701 ++aIdx;
1702 nStart = 0;
1703 }
1704 }
1705
1706 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
1707