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