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