Version 7.6.3.2-android, tag libreoffice-7.6.3.2-android
[LibreOffice.git] / sw / source / core / edit / edlingu.cxx
blobab89fc5b72413f29089e89df629e9981bcef4b72
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
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 .
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>
29 #include <unoflatpara.hxx>
31 #include <strings.hrc>
32 #include <hintids.hxx>
33 #include <osl/diagnose.h>
34 #include <unotools/linguprops.hxx>
35 #include <linguistic/lngprops.hxx>
36 #include <editeng/langitem.hxx>
37 #include <editeng/SpellPortions.hxx>
38 #include <svl/languageoptions.hxx>
39 #include <editsh.hxx>
40 #include <doc.hxx>
41 #include <IDocumentUndoRedo.hxx>
42 #include <IDocumentRedlineAccess.hxx>
43 #include <rootfrm.hxx>
44 #include <pam.hxx>
45 #include <swundo.hxx>
46 #include <ndtxt.hxx>
47 #include <viewopt.hxx>
48 #include <SwGrammarMarkUp.hxx>
49 #include <mdiexp.hxx>
50 #include <cntfrm.hxx>
51 #include <splargs.hxx>
52 #include <redline.hxx>
53 #include <docary.hxx>
54 #include <docsh.hxx>
55 #include <txatbase.hxx>
56 #include <txtfrm.hxx>
57 #include <comphelper/propertyvalue.hxx>
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;
65 namespace {
67 class SwLinguIter
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;
76 public:
77 SwLinguIter();
79 SwEditShell* GetSh() { return m_pSh; }
81 const SwPosition *GetEnd() const { return m_pEnd.get(); }
82 void SetEnd(SwPosition* pNew) { m_pEnd.reset(pNew); }
84 const SwPosition *GetStart() const { return m_pStart.get(); }
85 void SetStart(SwPosition* pNew) { m_pStart.reset(pNew); }
87 const SwPosition *GetCurr() const { return m_pCurr.get(); }
88 void SetCurr(SwPosition* pNew) { m_pCurr.reset(pNew); }
90 const SwPosition *GetCurrX() const { return m_pCurrX.get(); }
91 void SetCurrX(SwPosition* pNew) { m_pCurrX.reset(pNew); }
93 sal_uInt16& GetCursorCnt() { return m_nCursorCount; }
95 // for the UI:
96 void Start_( SwEditShell *pSh, SwDocPositions eStart,
97 SwDocPositions eEnd );
98 void End_(bool bRestoreSelection = true);
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
105 sal_Int32 nLeft;
106 sal_Int32 nRight;
111 typedef std::vector<SpellContentPosition> SpellContentPositions;
113 namespace {
115 class SwSpellIter : public SwLinguIter
117 uno::Reference<XSpellChecker1> m_xSpeller;
118 svx::SpellPortions m_aLastPortions;
120 SpellContentPositions m_aLastPositions;
121 bool m_bBackToStartOfSentence;
123 void CreatePortion(uno::Reference< XSpellAlternatives > const & xAlt,
124 const linguistic2::ProofreadingResult* pGrammarResult,
125 bool bIsField, bool bIsHidden);
127 void AddPortion(uno::Reference< XSpellAlternatives > const & xAlt,
128 const linguistic2::ProofreadingResult* pGrammarResult,
129 const SpellContentPositions& rDeletedRedlines);
130 public:
131 SwSpellIter()
132 : m_bBackToStartOfSentence(false)
136 void Start( SwEditShell *pSh, SwDocPositions eStart, SwDocPositions eEnd );
138 uno::Any Continue( sal_uInt16* pPageCnt, sal_uInt16* pPageSt );
140 bool SpellSentence(svx::SpellPortions& rPortions, bool bIsGrammarCheck);
141 void ToSentenceStart();
142 const svx::SpellPortions& GetLastPortions() const { return m_aLastPortions; }
143 const SpellContentPositions& GetLastPositions() const { return m_aLastPositions; }
146 /// used for text conversion
147 class SwConvIter : public SwLinguIter
149 SwConversionArgs& m_rArgs;
151 public:
152 explicit SwConvIter(SwConversionArgs& rConvArgs)
153 : m_rArgs(rConvArgs)
157 void Start( SwEditShell *pSh, SwDocPositions eStart, SwDocPositions eEnd );
159 uno::Any Continue( sal_uInt16* pPageCnt, sal_uInt16* pPageSt );
162 class SwHyphIter : public SwLinguIter
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);
169 bool m_bOldIdle;
170 static void DelSoftHyph( SwPaM &rPam );
172 public:
173 SwHyphIter()
174 : m_pLastNode(nullptr)
175 , m_pLastFrame(nullptr)
176 , m_bOldIdle(false)
180 void Start( SwEditShell *pSh, SwDocPositions eStart, SwDocPositions eEnd );
181 void End();
183 void Ignore();
185 uno::Any Continue( sal_uInt16* pPageCnt, sal_uInt16* pPageSt );
187 static bool IsAuto();
188 void InsertSoftHyph( const sal_Int32 nHyphPos );
189 void ShowSelection();
194 static SwSpellIter* g_pSpellIter = nullptr;
195 static SwConvIter* g_pConvIter = nullptr;
196 static SwHyphIter* g_pHyphIter = nullptr;
198 SwLinguIter::SwLinguIter()
199 : m_pSh(nullptr)
200 , m_nCursorCount(0)
202 // TODO missing: ensurance of re-entrance, OSL_ENSURE( etc.
205 void SwLinguIter::Start_( SwEditShell *pShell, SwDocPositions eStart,
206 SwDocPositions eEnd )
208 // TODO missing: ensurance of re-entrance, locking
209 if (m_pSh)
210 return;
212 bool bSetCurr;
214 m_pSh = pShell;
216 CurrShell aCurr(m_pSh);
218 OSL_ENSURE(!m_pEnd, "SwLinguIter::Start_ without End?");
220 SwPaM* pCursor = m_pSh->GetCursor();
222 if( pShell->HasSelection() || pCursor != pCursor->GetNext() )
224 bSetCurr = nullptr != GetCurr();
225 m_nCursorCount = m_pSh->GetCursorCnt();
226 if (m_pSh->IsTableMode())
227 m_pSh->TableCursorToCursor();
229 m_pSh->Push();
230 sal_uInt16 n;
231 for (n = 0; n < m_nCursorCount; ++n)
233 m_pSh->Push();
234 m_pSh->DestroyCursor();
236 m_pSh->Pop(SwCursorShell::PopMode::DeleteCurrent);
238 else
240 bSetCurr = false;
241 m_nCursorCount = 1;
242 m_pSh->Push();
243 m_pSh->SetLinguRange(eStart, eEnd);
246 pCursor = m_pSh->GetCursor();
247 if ( *pCursor->GetPoint() > *pCursor->GetMark() )
248 pCursor->Exchange();
250 m_pStart.reset(new SwPosition(*pCursor->GetPoint()));
251 m_pEnd.reset(new SwPosition(*pCursor->GetMark()));
252 if( bSetCurr )
254 SwPosition* pNew = new SwPosition( *GetStart() );
255 SetCurr( pNew );
256 pNew = new SwPosition( *pNew );
257 SetCurrX( pNew );
260 pCursor->SetMark();
263 void SwLinguIter::End_(bool bRestoreSelection)
265 if (!m_pSh)
266 return;
268 OSL_ENSURE(m_pEnd, "SwLinguIter::End_ without end?");
269 if(bRestoreSelection)
271 while (m_nCursorCount--)
272 m_pSh->Pop(SwCursorShell::PopMode::DeleteCurrent);
274 m_pSh->KillPams();
275 m_pSh->ClearMark();
277 m_pStart.reset();
278 m_pEnd.reset();
279 m_pCurr.reset();
280 m_pCurrX.reset();
282 m_pSh = nullptr;
285 void SwSpellIter::Start( SwEditShell *pShell, SwDocPositions eStart,
286 SwDocPositions eEnd )
288 if( GetSh() )
289 return;
291 m_xSpeller = ::GetSpellChecker();
292 if (m_xSpeller.is())
293 Start_( pShell, eStart, eEnd );
294 m_aLastPortions.clear();
295 m_aLastPositions.clear();
298 // This method is the origin of SwEditShell::SpellContinue()
299 uno::Any SwSpellIter::Continue( sal_uInt16* pPageCnt, sal_uInt16* pPageSt )
301 //!!
302 //!! Please check SwConvIter also when modifying this
303 //!!
305 uno::Any aSpellRet;
306 SwEditShell *pMySh = GetSh();
307 if( !pMySh )
308 return aSpellRet;
310 OSL_ENSURE( GetEnd(), "SwSpellIter::Continue without start?");
312 uno::Reference< uno::XInterface > xSpellRet;
313 bool bGoOn = true;
314 do {
315 SwPaM *pCursor = pMySh->GetCursor();
316 if ( !pCursor->HasMark() )
317 pCursor->SetMark();
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() )
327 bGoOn = false;
328 SwPosition* pNewPoint = new SwPosition( *pCursor->GetPoint() );
329 SwPosition* pNewMark = new SwPosition( *pCursor->GetMark() );
330 SetCurr( pNewPoint );
331 SetCurrX( pNewMark );
333 if( bGoOn )
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();
350 }while ( bGoOn );
351 aSpellRet <<= xSpellRet;
352 return aSpellRet;
355 void SwConvIter::Start( SwEditShell *pShell, SwDocPositions eStart,
356 SwDocPositions eEnd )
358 if( GetSh() )
359 return;
360 Start_( pShell, eStart, eEnd );
363 uno::Any SwConvIter::Continue( sal_uInt16* pPageCnt, sal_uInt16* pPageSt )
365 //!!
366 //!! Please check SwSpellIter also when modifying this
367 //!!
369 uno::Any aConvRet{ OUString() };
370 SwEditShell *pMySh = GetSh();
371 if( !pMySh )
372 return aConvRet;
374 OSL_ENSURE( GetEnd(), "SwConvIter::Continue() without Start?");
376 OUString aConvText;
377 bool bGoOn = true;
378 do {
379 SwPaM *pCursor = pMySh->GetCursor();
380 if ( !pCursor->HasMark() )
381 pCursor->SetMark();
383 *pMySh->GetCursor()->GetPoint() = *GetCurr();
384 *pMySh->GetCursor()->GetMark() = *GetEnd();
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;
392 bGoOn = GetCursorCnt() > 1;
393 if( !aConvText.isEmpty() )
395 bGoOn = false;
396 SwPosition* pNewPoint = new SwPosition( *pCursor->GetPoint() );
397 SwPosition* pNewMark = new SwPosition( *pCursor->GetMark() );
399 SetCurr( pNewPoint );
400 SetCurrX( pNewMark );
402 if( bGoOn )
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();
419 }while ( bGoOn );
420 return Any( aConvText );
423 bool SwHyphIter::IsAuto()
425 uno::Reference< beans::XPropertySet > xProp( ::GetLinguPropertySet() );
426 return xProp.is() && *o3tl::doAccess<bool>(xProp->getPropertyValue(
427 UPN_IS_HYPH_AUTO ));
430 void SwHyphIter::ShowSelection()
432 SwEditShell *pMySh = GetSh();
433 if( pMySh )
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();
442 void SwHyphIter::Start( SwEditShell *pShell, SwDocPositions eStart, SwDocPositions eEnd )
444 // robust
445 if( GetSh() || GetEnd() )
447 OSL_ENSURE( !GetSh(), "SwHyphIter::Start: missing HyphEnd()" );
448 return;
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 );
457 // restore selections
458 void SwHyphIter::End()
460 if( !GetSh() )
461 return;
462 GetSh()->GetViewOptions()->SetIdle(m_bOldIdle);
463 End_();
466 uno::Any SwHyphIter::Continue( sal_uInt16* pPageCnt, sal_uInt16* pPageSt )
468 uno::Any aHyphRet;
469 SwEditShell *pMySh = GetSh();
470 if( !pMySh )
471 return aHyphRet;
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() )
485 pCursor->Exchange();
486 pCursor->SetMark();
489 if ( *pCursor->End() <= *GetEnd() )
491 *pCursor->GetMark() = *GetEnd();
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 );
499 if( bAuto && xHyphWord.is() )
501 SwEditShell::InsertSoftHyph( xHyphWord->getHyphenationPos() + 1);
503 } while( bAuto && xHyphWord.is() ); //end of do-while
504 bGoOn = !xHyphWord.is() && GetCursorCnt() > 1;
506 if( bGoOn )
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();
517 } while ( bGoOn );
518 aHyphRet <<= xHyphWord;
519 return aHyphRet;
522 /// ignore hyphenation
523 void SwHyphIter::Ignore()
525 SwEditShell *pMySh = GetSh();
526 SwPaM *pCursor = pMySh->GetCursor();
528 // delete old SoftHyphen
529 DelSoftHyph( *pCursor );
531 // and continue
532 pCursor->Start()->SetContent( pCursor->End()->GetContentIndex() );
533 pCursor->SetMark();
536 void SwHyphIter::DelSoftHyph( SwPaM &rPam )
538 const SwPosition* pStt = rPam.Start();
539 const sal_Int32 nStart = pStt->GetContentIndex();
540 const sal_Int32 nEnd = rPam.End()->GetContentIndex();
541 SwTextNode *pNode = pStt->GetNode().GetTextNode();
542 pNode->DelSoftHyph( nStart, nEnd );
545 void SwHyphIter::InsertSoftHyph( const sal_Int32 nHyphPos )
547 SwEditShell *pMySh = GetSh();
548 OSL_ENSURE( pMySh, "SwHyphIter::InsertSoftHyph: missing HyphStart()");
549 if( !pMySh )
550 return;
552 SwPaM *pCursor = pMySh->GetCursor();
553 auto [pSttPos, pEndPos] = pCursor->StartEnd(); // SwPosition*
555 const sal_Int32 nLastHyphLen = GetEnd()->GetContentIndex() -
556 pSttPos->GetContentIndex();
558 if( pSttPos->GetNode() != pEndPos->GetNode() || !nLastHyphLen )
560 OSL_ENSURE( pSttPos->GetNode() == pEndPos->GetNode(),
561 "SwHyphIter::InsertSoftHyph: node warp during hyphenation" );
562 OSL_ENSURE(nLastHyphLen, "SwHyphIter::InsertSoftHyph: missing HyphContinue()");
563 *pSttPos = *pEndPos;
564 return;
567 pMySh->StartAction();
569 SwDoc *pDoc = pMySh->GetDoc();
570 DelSoftHyph( *pCursor );
571 pSttPos->AdjustContent( +nHyphPos );
572 SwPaM aRg( *pSttPos );
573 pDoc->getIDocumentContentOperations().InsertString( aRg, OUString(CHAR_SOFTHYPHEN) );
575 // revoke selection
576 pCursor->DeleteMark();
577 pMySh->EndAction();
578 pCursor->SetMark();
581 namespace sw {
583 SwTextFrame *
584 SwHyphIterCacheLastTextFrame(SwTextNode const * pNode, const sw::Creator& create)
586 assert(g_pHyphIter);
587 if (pNode != g_pHyphIter->m_pLastNode || !g_pHyphIter->m_pLastFrame)
589 g_pHyphIter->m_pLastNode = pNode;
590 g_pHyphIter->m_pLastFrame = create();
592 return g_pHyphIter->m_pLastFrame;
597 bool SwEditShell::HasLastSentenceGotGrammarChecked()
599 bool bTextWasGrammarChecked = false;
600 if (g_pSpellIter)
602 svx::SpellPortions aLastPortions( g_pSpellIter->GetLastPortions() );
603 for (size_t i = 0; i < aLastPortions.size() && !bTextWasGrammarChecked; ++i)
605 // bIsGrammarError is also true if the text was only checked but no
606 // grammar error was found. (That is if a ProofreadingResult was obtained in
607 // SwDoc::Spell and in turn bIsGrammarError was set in SwSpellIter::CreatePortion)
608 if (aLastPortions[i].bIsGrammarError)
609 bTextWasGrammarChecked = true;
612 return bTextWasGrammarChecked;
615 bool SwEditShell::HasConvIter()
617 return nullptr != g_pConvIter;
620 bool SwEditShell::HasHyphIter()
622 return nullptr != g_pHyphIter;
625 void SwEditShell::SetLinguRange( SwDocPositions eStart, SwDocPositions eEnd )
627 SwPaM *pCursor = GetCursor();
628 MakeFindRange( eStart, eEnd, pCursor );
629 if( *pCursor->GetPoint() > *pCursor->GetMark() )
630 pCursor->Exchange();
633 void SwEditShell::SpellStart(
634 SwDocPositions eStart, SwDocPositions eEnd, SwDocPositions eCurr,
635 SwConversionArgs *pConvArgs )
637 SwLinguIter *pLinguIter = nullptr;
639 // do not spell if interactive spelling is active elsewhere
640 if (!pConvArgs && !g_pSpellIter)
642 g_pSpellIter = new SwSpellIter;
643 pLinguIter = g_pSpellIter;
645 // do not do text conversion if it is active elsewhere
646 if (pConvArgs && !g_pConvIter)
648 g_pConvIter = new SwConvIter( *pConvArgs );
649 pLinguIter = g_pConvIter;
652 if (pLinguIter)
654 SwCursor* pSwCursor = GetCursor();
656 SwPosition *pTmp = new SwPosition( *pSwCursor->GetPoint() );
657 pSwCursor->FillFindPos( eCurr, *pTmp );
658 pLinguIter->SetCurr( pTmp );
660 pTmp = new SwPosition( *pTmp );
661 pLinguIter->SetCurrX( pTmp );
664 if (!pConvArgs && g_pSpellIter)
665 g_pSpellIter->Start( this, eStart, eEnd );
666 if (pConvArgs && g_pConvIter)
667 g_pConvIter->Start( this, eStart, eEnd );
670 void SwEditShell::SpellEnd( SwConversionArgs const *pConvArgs, bool bRestoreSelection )
672 if (!pConvArgs && g_pSpellIter && g_pSpellIter->GetSh() == this)
674 g_pSpellIter->End_(bRestoreSelection);
675 delete g_pSpellIter;
676 g_pSpellIter = nullptr;
678 if (pConvArgs && g_pConvIter && g_pConvIter->GetSh() == this)
680 g_pConvIter->End_();
681 delete g_pConvIter;
682 g_pConvIter = nullptr;
686 /// @returns SPL_ return values as in splchk.hxx
687 uno::Any SwEditShell::SpellContinue(
688 sal_uInt16* pPageCnt, sal_uInt16* pPageSt,
689 SwConversionArgs const *pConvArgs )
691 uno::Any aRes;
693 if ((!pConvArgs && g_pSpellIter->GetSh() != this) ||
694 ( pConvArgs && g_pConvIter->GetSh() != this))
695 return aRes;
697 if( pPageCnt && !*pPageCnt )
699 sal_uInt16 nEndPage = GetLayout()->GetPageNum();
700 nEndPage += nEndPage * 10 / 100;
701 *pPageCnt = nEndPage;
702 if( nEndPage )
703 ::StartProgress( STR_STATSTR_SPELL, 0, nEndPage, GetDoc()->GetDocShell() );
706 OSL_ENSURE( pConvArgs || g_pSpellIter, "SpellIter missing" );
707 OSL_ENSURE( !pConvArgs || g_pConvIter, "ConvIter missing" );
708 //JP 18.07.95: prevent displaying selection on error messages. NO StartAction so that all
709 // Paints are also disabled.
710 ++mnStartAction;
711 OUString aRet;
712 uno::Reference< uno::XInterface > xRet;
713 if (pConvArgs)
715 g_pConvIter->Continue( pPageCnt, pPageSt ) >>= aRet;
716 aRes <<= aRet;
718 else
720 g_pSpellIter->Continue( pPageCnt, pPageSt ) >>= xRet;
721 aRes <<= xRet;
723 --mnStartAction;
725 if( !aRet.isEmpty() || xRet.is() )
727 // then make awt::Selection again visible
728 StartAction();
729 EndAction();
731 return aRes;
734 /* Interactive Hyphenation (BP 10.03.93)
736 * 1) HyphStart
737 * - Revoke all Selections
738 * - Save current Cursor
739 * - if no selections existent:
740 * - create new selection reaching until document end
741 * 2) HyphContinue
742 * - add nLastHyphLen onto SelectionStart
743 * - iterate over all selected areas
744 * - pDoc->Hyphenate() iterates over all Nodes of a selection
745 * - pTextNode->Hyphenate() calls SwTextFrame::Hyphenate of the EditShell
746 * - SwTextFrame:Hyphenate() iterates over all rows of the Pam
747 * - LineIter::Hyphenate() sets the Hyphenator and the Pam based on
748 * the to be separated word.
749 * - Returns true if there is a hyphenation and false if the Pam is processed.
750 * - If true, show the selected word and set nLastHyphLen.
751 * - If false, delete current selection and select next one. Returns HYPH_OK if no more.
752 * 3) InsertSoftHyph (might be called by UI if needed)
753 * - Place current cursor and add attribute.
754 * 4) HyphEnd
755 * - Restore old cursor, EndAction
757 void SwEditShell::HyphStart( SwDocPositions eStart, SwDocPositions eEnd )
759 // do not hyphenate if interactive hyphenation is active elsewhere
760 if (!g_pHyphIter)
762 g_pHyphIter = new SwHyphIter;
763 g_pHyphIter->Start( this, eStart, eEnd );
767 /// restore selections
768 void SwEditShell::HyphEnd()
770 assert(g_pHyphIter);
771 if (g_pHyphIter->GetSh() == this)
773 g_pHyphIter->End();
774 delete g_pHyphIter;
775 g_pHyphIter = nullptr;
779 /// @returns HYPH_CONTINUE if hyphenation, HYPH_OK if selected area was processed.
780 uno::Reference< uno::XInterface >
781 SwEditShell::HyphContinue( sal_uInt16* pPageCnt, sal_uInt16* pPageSt )
783 assert(g_pHyphIter);
784 if (g_pHyphIter->GetSh() != this)
785 return nullptr;
787 if( pPageCnt && !*pPageCnt && !*pPageSt )
789 sal_uInt16 nEndPage = GetLayout()->GetPageNum();
790 nEndPage += nEndPage * 10 / 100;
791 if( nEndPage > 14 )
793 *pPageCnt = nEndPage;
794 ::StartProgress( STR_STATSTR_HYPHEN, 0, nEndPage, GetDoc()->GetDocShell());
796 else // here we once and for all suppress StatLineStartPercent
797 *pPageSt = 1;
800 //JP 18.07.95: prevent displaying selection on error messages. NO StartAction so that all
801 // Paints are also disabled.
802 ++mnStartAction;
803 uno::Reference< uno::XInterface > xRet;
804 g_pHyphIter->Continue( pPageCnt, pPageSt ) >>= xRet;
805 --mnStartAction;
807 if( xRet.is() )
808 g_pHyphIter->ShowSelection();
810 return xRet;
813 /** Insert soft hyphen
815 * @param nHyphPos Offset in the to be separated word
817 void SwEditShell::InsertSoftHyph( const sal_Int32 nHyphPos )
819 assert(g_pHyphIter);
820 g_pHyphIter->InsertSoftHyph( nHyphPos );
823 /// ignore hyphenation
824 void SwEditShell::HyphIgnore()
826 assert(g_pHyphIter);
827 //JP 18.07.95: prevent displaying selection on error messages. NO StartAction so that all
828 // Paints are also disabled.
829 ++mnStartAction;
830 g_pHyphIter->Ignore();
831 --mnStartAction;
833 g_pHyphIter->ShowSelection();
836 void SwEditShell::HandleCorrectionError(const OUString& aText, SwPosition aPos, sal_Int32 nBegin,
837 sal_Int32 nLen, const Point* pPt,
838 SwRect& rSelectRect)
840 // save the start and end positions of the line and the starting point
841 SwNode const& rNode(GetCursor()->GetPoint()->GetNode());
842 Push();
843 LeftMargin();
844 const sal_Int32 nLineStart = &rNode == &GetCursor()->GetPoint()->GetNode()
845 ? GetCursor()->GetPoint()->GetContentIndex()
846 : 0;
847 RightMargin();
848 const sal_Int32 nLineEnd = &rNode == &GetCursor()->GetPoint()->GetNode()
849 ? GetCursor()->GetPoint()->GetContentIndex()
850 : rNode.GetTextNode()->Len();
851 Pop(PopMode::DeleteCurrent);
853 // make sure the selection build later from the data below does
854 // not "in word" character to the left and right in order to
855 // preserve those. Therefore count those "in words" in order to
856 // modify the selection accordingly.
857 const sal_Unicode* pChar = aText.getStr();
858 sal_Int32 nLeft = 0;
859 while (*pChar++ == CH_TXTATR_INWORD)
860 ++nLeft;
861 pChar = aText.getLength() ? aText.getStr() + aText.getLength() - 1 : nullptr;
862 sal_Int32 nRight = 0;
863 while (pChar && *pChar-- == CH_TXTATR_INWORD)
864 ++nRight;
866 aPos.SetContent( nBegin + nLeft );
867 SwPaM* pCursor = GetCursor();
868 *pCursor->GetPoint() = aPos;
869 pCursor->SetMark();
870 ExtendSelection( true, nLen - nLeft - nRight );
871 // don't determine the rectangle in the current line
872 const sal_Int32 nWordStart = (nBegin + nLeft) < nLineStart ? nLineStart : nBegin + nLeft;
873 // take one less than the line end - otherwise the next line would be calculated
874 const sal_Int32 nWordEnd = (nBegin + nLen - nLeft - nRight) > nLineEnd
875 ? nLineEnd : (nBegin + nLen - nLeft - nRight);
876 Push();
877 pCursor->DeleteMark();
878 SwPosition& rPtPos = *GetCursor()->GetPoint();
879 rPtPos.SetContent(nWordStart);
880 SwRect aStartRect;
881 SwCursorMoveState aState;
882 aState.m_bRealWidth = true;
883 SwContentNode* pContentNode = pCursor->GetPointContentNode();
884 std::pair<Point, bool> tmp;
885 if (pPt)
887 tmp.first = *pPt;
888 tmp.second = false;
890 SwContentFrame *const pContentFrame = pContentNode->getLayoutFrame(GetLayout(), pCursor->GetPoint(), pPt ? &tmp : nullptr);
892 pContentFrame->GetCharRect( aStartRect, *pCursor->GetPoint(), &aState );
893 rPtPos.SetContent(nWordEnd - 1);
894 SwRect aEndRect;
895 pContentFrame->GetCharRect( aEndRect, *pCursor->GetPoint(),&aState );
896 rSelectRect = aStartRect.Union( aEndRect );
897 Pop(PopMode::DeleteCurrent);
900 /** Get a list of potential corrections for misspelled word.
902 * If empty, word is unknown but there are no corrections available.
903 * If NULL then the word is not misspelled but correct.
905 * @brief SwEditShell::GetCorrection
906 * @return list or NULL pointer
908 uno::Reference< XSpellAlternatives >
909 SwEditShell::GetCorrection( const Point* pPt, SwRect& rSelectRect )
911 uno::Reference< XSpellAlternatives > xSpellAlt;
913 if( IsTableMode() )
914 return nullptr;
915 SwPaM* pCursor = GetCursor();
916 SwPosition aPos( *pCursor->GetPoint() );
917 SwCursorMoveState eTmpState( CursorMoveState::SetOnlyText );
918 SwTextNode *pNode = nullptr;
919 SwWrongList *pWrong = nullptr;
920 if (pPt && GetLayout()->GetModelPositionForViewPoint( &aPos, *const_cast<Point*>(pPt), &eTmpState ))
921 pNode = aPos.GetNode().GetTextNode();
922 if (nullptr == pNode)
923 pNode = pCursor->GetPointNode().GetTextNode();
924 if (nullptr != pNode)
925 pWrong = pNode->GetWrong();
926 if (nullptr != pWrong && !pNode->IsInProtectSect())
928 sal_Int32 nBegin = aPos.GetContentIndex();
929 sal_Int32 nLen = 1;
930 if (pWrong->InWrongWord(nBegin, nLen) && !pNode->IsSymbolAt(nBegin))
932 const OUString aText(pNode->GetText().copy(nBegin, nLen));
933 // TODO: this doesn't handle fieldmarks properly
934 ModelToViewHelper const aConversionMap(*pNode, GetLayout(),
935 ExpandMode::ExpandFields | ExpandMode::ExpandFootnote | ExpandMode::ReplaceMode
936 | ExpandMode::HideFieldmarkCommands
937 | (GetLayout()->IsHideRedlines() ? ExpandMode::HideDeletions : ExpandMode(0))
938 | (GetViewOptions()->IsShowHiddenChar() ? ExpandMode(0) : ExpandMode::HideInvisible));
939 auto const nBeginView(aConversionMap.ConvertToViewPosition(nBegin));
940 OUString const aWord(aConversionMap.getViewText().copy(nBeginView,
941 aConversionMap.ConvertToViewPosition(nBegin+nLen) - nBeginView));
943 uno::Reference< XSpellChecker1 > xSpell( ::GetSpellChecker() );
944 if( xSpell.is() )
946 LanguageType eActLang = pNode->GetLang( nBegin, nLen );
947 if( xSpell->hasLanguage( static_cast<sal_uInt16>(eActLang) ))
949 // restrict the maximal number of suggestions displayed
950 // in the context menu.
951 // Note: That could of course be done by clipping the
952 // resulting sequence but the current third party
953 // implementations result differs greatly if the number of
954 // suggestions to be returned gets changed. Statistically
955 // it gets much better if told to return e.g. only 7 strings
956 // than returning e.g. 16 suggestions and using only the
957 // first 7. Thus we hand down the value to use to that
958 // implementation here by providing an additional parameter.
959 Sequence< PropertyValue > aPropVals ( { comphelper::makePropertyValue( UPN_MAX_NUMBER_OF_SUGGESTIONS, sal_Int16(7)) } );
961 xSpellAlt = xSpell->spell( aWord, static_cast<sal_uInt16>(eActLang), aPropVals );
965 if ( xSpellAlt.is() ) // error found?
967 HandleCorrectionError( aText, std::move(aPos), nBegin, nLen, pPt, rSelectRect );
971 return xSpellAlt;
974 bool SwEditShell::GetGrammarCorrection(
975 linguistic2::ProofreadingResult /*out*/ &rResult, // the complete result
976 sal_Int32 /*out*/ &rErrorPosInText, // offset of error position in string that was grammar checked...
977 sal_Int32 /*out*/ &rErrorIndexInResult, // index of error in rResult.aGrammarErrors
978 uno::Sequence< OUString > /*out*/ &rSuggestions, // suggestions to be used for the error found
979 const Point *pPt, SwRect &rSelectRect )
981 bool bRes = false;
983 if( IsTableMode() )
984 return bRes;
986 SwPaM* pCursor = GetCursor();
987 SwPosition aPos( *pCursor->GetPoint() );
988 SwCursorMoveState eTmpState( CursorMoveState::SetOnlyText );
989 SwTextNode *pNode = nullptr;
990 SwGrammarMarkUp *pWrong = nullptr;
991 if (pPt && GetLayout()->GetModelPositionForViewPoint( &aPos, *const_cast<Point*>(pPt), &eTmpState ))
992 pNode = aPos.GetNode().GetTextNode();
993 if (nullptr == pNode)
994 pNode = pCursor->GetPointNode().GetTextNode();
995 if (nullptr != pNode)
996 pWrong = pNode->GetGrammarCheck();
997 if (nullptr != pWrong && !pNode->IsInProtectSect())
999 sal_Int32 nBegin = aPos.GetContentIndex();
1000 sal_Int32 nLen = 1;
1001 if (pWrong->InWrongWord(nBegin, nLen))
1003 const OUString aText(pNode->GetText().copy(nBegin, nLen));
1005 uno::Reference< linguistic2::XProofreadingIterator > xGCIterator( mxDoc->GetGCIterator() );
1006 if (xGCIterator.is())
1008 uno::Reference< lang::XComponent > xDoc = mxDoc->GetDocShell()->GetBaseModel();
1010 // Expand the string:
1011 const ModelToViewHelper aConversionMap(*pNode, GetLayout());
1012 const OUString& aExpandText = aConversionMap.getViewText();
1013 // get XFlatParagraph to use...
1014 uno::Reference< text::XFlatParagraph > xFlatPara = new SwXFlatParagraph( *pNode, aExpandText, aConversionMap );
1016 // get error position of cursor in XFlatParagraph
1017 rErrorPosInText = aConversionMap.ConvertToViewPosition( nBegin );
1019 const sal_Int32 nStartOfSentence = aConversionMap.ConvertToViewPosition( pWrong->getSentenceStart( nBegin ) );
1020 const sal_Int32 nEndOfSentence = aConversionMap.ConvertToViewPosition( pWrong->getSentenceEnd( nBegin ) );
1022 rResult = xGCIterator->checkSentenceAtPosition(
1023 xDoc, xFlatPara, aExpandText, lang::Locale(), nStartOfSentence,
1024 nEndOfSentence == COMPLETE_STRING ? aExpandText.getLength() : nEndOfSentence,
1025 rErrorPosInText );
1026 bRes = true;
1028 // get suggestions to use for the specific error position
1029 rSuggestions.realloc( 0 );
1030 // return suggestions for first error that includes the given error position
1031 auto pError = std::find_if(std::cbegin(rResult.aErrors), std::cend(rResult.aErrors),
1032 [rErrorPosInText, nLen](const linguistic2::SingleProofreadingError &rError) {
1033 return rError.nErrorStart <= rErrorPosInText
1034 && rErrorPosInText + nLen <= rError.nErrorStart + rError.nErrorLength; });
1035 if (pError != std::cend(rResult.aErrors))
1037 rSuggestions = pError->aSuggestions;
1038 rErrorIndexInResult = static_cast<sal_Int32>(std::distance(std::cbegin(rResult.aErrors), pError));
1042 if (rResult.aErrors.hasElements()) // error found?
1044 HandleCorrectionError( aText, std::move(aPos), nBegin, nLen, pPt, rSelectRect );
1049 return bRes;
1052 bool SwEditShell::SpellSentence(svx::SpellPortions& rPortions, bool bIsGrammarCheck)
1054 OSL_ENSURE( g_pSpellIter, "SpellIter missing" );
1055 if (!g_pSpellIter)
1056 return false;
1057 bool bRet = g_pSpellIter->SpellSentence(rPortions, bIsGrammarCheck);
1059 // make Selection visible - this should simply move the
1060 // cursor to the end of the sentence
1061 StartAction();
1062 EndAction();
1063 return bRet;
1066 ///make SpellIter start with the current sentence when called next time
1067 void SwEditShell::PutSpellingToSentenceStart()
1069 OSL_ENSURE( g_pSpellIter, "SpellIter missing" );
1070 if (!g_pSpellIter)
1071 return;
1072 g_pSpellIter->ToSentenceStart();
1075 static sal_uInt32 lcl_CountRedlines(const svx::SpellPortions& rLastPortions)
1077 return static_cast<sal_uInt32>(std::count_if(rLastPortions.begin(), rLastPortions.end(),
1078 [](const svx::SpellPortion& rPortion) { return rPortion.bIsHidden; }));
1081 void SwEditShell::MoveContinuationPosToEndOfCheckedSentence()
1083 // give hint that continuation position for spell/grammar checking is
1084 // at the end of this sentence
1085 if (g_pSpellIter)
1087 g_pSpellIter->SetCurr( new SwPosition( *g_pSpellIter->GetCurrX() ) );
1091 void SwEditShell::ApplyChangedSentence(const svx::SpellPortions& rNewPortions, bool bRecheck)
1093 // Note: rNewPortions.size() == 0 is valid and happens when the whole
1094 // sentence got removed in the dialog
1096 OSL_ENSURE( g_pSpellIter, "SpellIter missing" );
1097 if (!g_pSpellIter ||
1098 g_pSpellIter->GetLastPortions().empty()) // no portions -> no text to be changed
1099 return;
1101 const SpellPortions& rLastPortions = g_pSpellIter->GetLastPortions();
1102 const SpellContentPositions rLastPositions = g_pSpellIter->GetLastPositions();
1103 OSL_ENSURE(!rLastPortions.empty() &&
1104 rLastPortions.size() == rLastPositions.size(),
1105 "last vectors of spelling results are not set or not equal");
1107 // iterate over the new portions, beginning at the end to take advantage of the previously
1108 // saved content positions
1110 mxDoc->GetIDocumentUndoRedo().StartUndo( SwUndoId::UI_TEXT_CORRECTION, nullptr );
1111 StartAction();
1113 SwPaM *pCursor = GetCursor();
1114 // save cursor position (which should be at the end of the current sentence)
1115 // for later restoration
1116 Push();
1118 sal_uInt32 nRedlinePortions = lcl_CountRedlines(rLastPortions);
1119 if((rLastPortions.size() - nRedlinePortions) == rNewPortions.size())
1121 OSL_ENSURE( !rNewPortions.empty(), "rNewPortions should not be empty here" );
1122 OSL_ENSURE( !rLastPortions.empty(), "rLastPortions should not be empty here" );
1123 OSL_ENSURE( !rLastPositions.empty(), "rLastPositions should not be empty here" );
1125 // the simple case: the same number of elements on both sides
1126 // each changed element has to be applied to the corresponding source element
1127 svx::SpellPortions::const_iterator aCurrentNewPortion = rNewPortions.end();
1128 SpellPortions::const_iterator aCurrentOldPortion = rLastPortions.end();
1129 SpellContentPositions::const_iterator aCurrentOldPosition = rLastPositions.end();
1132 --aCurrentNewPortion;
1133 --aCurrentOldPortion;
1134 --aCurrentOldPosition;
1135 //jump over redline portions
1136 while(aCurrentOldPortion->bIsHidden)
1138 if (aCurrentOldPortion != rLastPortions.begin() &&
1139 aCurrentOldPosition != rLastPositions.begin())
1141 --aCurrentOldPortion;
1142 --aCurrentOldPosition;
1144 else
1146 OSL_FAIL("ApplyChangedSentence: iterator positions broken" );
1147 break;
1150 if ( !pCursor->HasMark() )
1151 pCursor->SetMark();
1152 pCursor->GetPoint()->SetContent( aCurrentOldPosition->nLeft );
1153 pCursor->GetMark()->SetContent( aCurrentOldPosition->nRight );
1154 sal_uInt16 nScriptType = SvtLanguageOptions::GetI18NScriptTypeOfLanguage( aCurrentNewPortion->eLanguage );
1155 sal_uInt16 nLangWhichId = RES_CHRATR_LANGUAGE;
1156 switch(nScriptType)
1158 case css::i18n::ScriptType::ASIAN : nLangWhichId = RES_CHRATR_CJK_LANGUAGE; break;
1159 case css::i18n::ScriptType::COMPLEX : nLangWhichId = RES_CHRATR_CTL_LANGUAGE; break;
1161 if(aCurrentNewPortion->sText != aCurrentOldPortion->sText)
1163 // change text ...
1164 // ... and apply language if necessary
1165 if(aCurrentNewPortion->eLanguage != aCurrentOldPortion->eLanguage)
1166 SetAttrItem( SvxLanguageItem(aCurrentNewPortion->eLanguage, nLangWhichId) );
1168 // if there is a comment inside the original word, don't delete it:
1169 // but keep it at the end of the replacement
1170 // TODO: keep all the comments with a recursive function
1171 sal_Int32 nCommentPos(pCursor->GetText().indexOf(OUStringChar(CH_TXTATR_INWORD)));
1172 if ( nCommentPos > -1 )
1174 // delete the original word after the comment
1175 pCursor->GetPoint()->AdjustContent(nCommentPos + 1);
1177 mxDoc->getIDocumentContentOperations().ReplaceRange(*pCursor, OUString(), false);
1178 // and select only the remaining part before the comment
1179 pCursor->GetPoint()->AdjustContent(-(nCommentPos + 1));
1180 pCursor->GetMark()->AdjustContent(-1);
1183 mxDoc->getIDocumentContentOperations().ReplaceRange(*pCursor, aCurrentNewPortion->sText, false);
1185 else if(aCurrentNewPortion->eLanguage != aCurrentOldPortion->eLanguage)
1187 // apply language
1188 SetAttrItem( SvxLanguageItem(aCurrentNewPortion->eLanguage, nLangWhichId) );
1190 else if( aCurrentNewPortion->bIgnoreThisError )
1192 // add the 'ignore' markup to the TextNode's grammar ignore markup list
1193 IgnoreGrammarErrorAt( *pCursor );
1194 OSL_FAIL("TODO: add ignore mark to text node");
1197 while(aCurrentNewPortion != rNewPortions.begin());
1199 else
1201 OSL_ENSURE( !rLastPositions.empty(), "rLastPositions should not be empty here" );
1203 // select the complete sentence
1204 SpellContentPositions::const_iterator aCurrentEndPosition = rLastPositions.end();
1205 --aCurrentEndPosition;
1206 SpellContentPositions::const_iterator aCurrentStartPosition = rLastPositions.begin();
1207 pCursor->GetPoint()->SetContent( aCurrentStartPosition->nLeft );
1208 pCursor->GetMark()->SetContent( aCurrentEndPosition->nRight );
1210 // delete the sentence completely
1211 mxDoc->getIDocumentContentOperations().DeleteAndJoin(*pCursor);
1212 for(const auto& rCurrentNewPortion : rNewPortions)
1214 // set the language attribute
1215 SvtScriptType nScriptType = GetScriptType();
1216 sal_uInt16 nLangWhichId = RES_CHRATR_LANGUAGE;
1217 switch(nScriptType)
1219 case SvtScriptType::ASIAN : nLangWhichId = RES_CHRATR_CJK_LANGUAGE; break;
1220 case SvtScriptType::COMPLEX : nLangWhichId = RES_CHRATR_CTL_LANGUAGE; break;
1221 default: break;
1223 SfxItemSet aSet(GetAttrPool(), nLangWhichId, nLangWhichId);
1224 GetCurAttr( aSet );
1225 const SvxLanguageItem& rLang = static_cast<const SvxLanguageItem& >(aSet.Get(nLangWhichId));
1226 if(rLang.GetLanguage() != rCurrentNewPortion.eLanguage)
1227 SetAttrItem( SvxLanguageItem(rCurrentNewPortion.eLanguage, nLangWhichId) );
1228 // insert the new string
1229 mxDoc->getIDocumentContentOperations().InsertString(*pCursor, rCurrentNewPortion.sText);
1231 // set the cursor to the end of the inserted string
1232 *pCursor->Start() = *pCursor->End();
1236 // restore cursor to the end of the sentence
1237 // (will work also if the sentence length has changed,
1238 // since cursors get updated automatically!)
1239 Pop(PopMode::DeleteCurrent);
1241 // collapse cursor to the end of the modified sentence
1242 *pCursor->Start() = *pCursor->End();
1243 if (bRecheck)
1245 // in grammar check the current sentence has to be checked again
1246 GoStartSentence();
1248 // set continuation position for spell/grammar checking to the end of this sentence
1249 g_pSpellIter->SetCurr( new SwPosition(*pCursor->Start()) );
1251 mxDoc->GetIDocumentUndoRedo().EndUndo( SwUndoId::UI_TEXT_CORRECTION, nullptr );
1252 EndAction();
1255 /** Collect all deleted redlines of the current text node
1256 * beginning at the start of the cursor position
1258 static SpellContentPositions lcl_CollectDeletedRedlines(SwEditShell const * pSh)
1260 SpellContentPositions aRedlines;
1261 SwDoc* pDoc = pSh->GetDoc();
1262 const bool bShowChg = IDocumentRedlineAccess::IsShowChanges( pDoc->getIDocumentRedlineAccess().GetRedlineFlags() );
1263 if ( bShowChg )
1265 SwPaM *pCursor = pSh->GetCursor();
1266 const SwPosition* pStartPos = pCursor->Start();
1267 const SwTextNode* pTextNode = pCursor->GetPointNode().GetTextNode();
1269 SwRedlineTable::size_type nAct = pDoc->getIDocumentRedlineAccess().GetRedlinePos( *pTextNode, RedlineType::Any );
1270 const sal_Int32 nStartIndex = pStartPos->GetContentIndex();
1271 for ( ; nAct < pDoc->getIDocumentRedlineAccess().GetRedlineTable().size(); nAct++ )
1273 const SwRangeRedline* pRed = pDoc->getIDocumentRedlineAccess().GetRedlineTable()[ nAct ];
1275 if ( pRed->Start()->GetNode() > *pTextNode )
1276 break;
1278 if( RedlineType::Delete == pRed->GetType() )
1280 sal_Int32 nStart_, nEnd_;
1281 pRed->CalcStartEnd( pTextNode->GetIndex(), nStart_, nEnd_ );
1282 sal_Int32 nStart = nStart_;
1283 sal_Int32 nEnd = nEnd_;
1284 if(nStart >= nStartIndex || nEnd >= nStartIndex)
1286 SpellContentPosition aAdd;
1287 aAdd.nLeft = nStart;
1288 aAdd.nRight = nEnd;
1289 aRedlines.push_back(aAdd);
1294 return aRedlines;
1297 /// remove the redline positions after the current selection
1298 static void lcl_CutRedlines( SpellContentPositions& aDeletedRedlines, SwEditShell const * pSh )
1300 if(!aDeletedRedlines.empty())
1302 SwPaM *pCursor = pSh->GetCursor();
1303 const SwPosition* pEndPos = pCursor->End();
1304 const sal_Int32 nEnd = pEndPos->GetContentIndex();
1305 while(!aDeletedRedlines.empty() &&
1306 aDeletedRedlines.back().nLeft > nEnd)
1308 aDeletedRedlines.pop_back();
1313 static SpellContentPosition lcl_FindNextDeletedRedline(
1314 const SpellContentPositions& rDeletedRedlines,
1315 sal_Int32 nSearchFrom )
1317 SpellContentPosition aRet;
1318 aRet.nLeft = aRet.nRight = SAL_MAX_INT32;
1319 if(!rDeletedRedlines.empty())
1321 auto aIter = std::find_if_not(rDeletedRedlines.begin(), rDeletedRedlines.end(),
1322 [nSearchFrom](const SpellContentPosition& rPos) { return rPos.nLeft < nSearchFrom; });
1323 if (aIter != rDeletedRedlines.end())
1324 aRet = *aIter;
1326 return aRet;
1329 bool SwSpellIter::SpellSentence(svx::SpellPortions& rPortions, bool bIsGrammarCheck)
1331 bool bRet = false;
1332 m_aLastPortions.clear();
1333 m_aLastPositions.clear();
1335 SwEditShell *pMySh = GetSh();
1336 if( !pMySh )
1337 return false;
1339 OSL_ENSURE( GetEnd(), "SwSpellIter::SpellSentence without Start?");
1341 uno::Reference< XSpellAlternatives > xSpellRet;
1342 linguistic2::ProofreadingResult aGrammarResult;
1343 bool bGoOn = true;
1344 bool bGrammarErrorFound = false;
1345 do {
1346 SwPaM *pCursor = pMySh->GetCursor();
1347 if ( !pCursor->HasMark() )
1348 pCursor->SetMark();
1350 *pCursor->GetPoint() = *GetCurr();
1351 *pCursor->GetMark() = *GetEnd();
1353 if (m_bBackToStartOfSentence)
1355 pMySh->GoStartSentence();
1356 m_bBackToStartOfSentence = false;
1358 uno::Any aSpellRet = pMySh->GetDoc()->Spell(*pCursor, m_xSpeller, nullptr, nullptr,
1359 bIsGrammarCheck, pMySh->GetLayout());
1360 aSpellRet >>= xSpellRet;
1361 aSpellRet >>= aGrammarResult;
1362 bGoOn = GetCursorCnt() > 1;
1363 bGrammarErrorFound = aGrammarResult.aErrors.hasElements();
1364 if( xSpellRet.is() || bGrammarErrorFound )
1366 bGoOn = false;
1367 SwPosition* pNewPoint = new SwPosition( *pCursor->GetPoint() );
1368 SwPosition* pNewMark = new SwPosition( *pCursor->GetMark() );
1370 SetCurr( pNewPoint );
1371 SetCurrX( pNewMark );
1373 if( bGoOn )
1375 pMySh->Pop(SwCursorShell::PopMode::DeleteCurrent);
1376 pCursor = pMySh->GetCursor();
1377 if ( *pCursor->GetPoint() > *pCursor->GetMark() )
1378 pCursor->Exchange();
1379 SwPosition* pNew = new SwPosition( *pCursor->GetPoint() );
1380 SetStart( pNew );
1381 pNew = new SwPosition( *pCursor->GetMark() );
1382 SetEnd( pNew );
1383 pNew = new SwPosition( *GetStart() );
1384 SetCurr( pNew );
1385 pNew = new SwPosition( *pNew );
1386 SetCurrX( pNew );
1387 pCursor->SetMark();
1388 --GetCursorCnt();
1390 } while ( bGoOn );
1392 if(xSpellRet.is() || bGrammarErrorFound)
1394 // an error has been found
1395 // To fill the spell portions the beginning of the sentence has to be found
1396 SwPaM *pCursor = pMySh->GetCursor();
1397 // set the mark to the right if necessary
1398 if ( *pCursor->GetPoint() > *pCursor->GetMark() )
1399 pCursor->Exchange();
1400 // the cursor has to be collapsed on the left to go to the start of the sentence - if sentence ends inside of the error
1401 pCursor->DeleteMark();
1402 pCursor->SetMark();
1403 bool bStartSent = pMySh->GoStartSentence();
1404 SpellContentPositions aDeletedRedlines = lcl_CollectDeletedRedlines(pMySh);
1405 if(bStartSent)
1407 // create a portion from the start part
1408 AddPortion(nullptr, nullptr, aDeletedRedlines);
1410 // Set the cursor to the error already found
1411 *pCursor->GetPoint() = *GetCurrX();
1412 *pCursor->GetMark() = *GetCurr();
1413 AddPortion(xSpellRet, &aGrammarResult, aDeletedRedlines);
1415 // save the end position of the error to continue from here
1416 SwPosition aSaveStartPos = *pCursor->End();
1417 // determine the end of the current sentence
1418 if ( *pCursor->GetPoint() < *pCursor->GetMark() )
1419 pCursor->Exchange();
1420 // again collapse to start marking after the end of the error
1421 pCursor->DeleteMark();
1422 pCursor->SetMark();
1424 pMySh->GoEndSentence();
1425 if( bGrammarErrorFound )
1427 const ModelToViewHelper aConversionMap(static_cast<SwTextNode&>(pCursor->GetPointNode()), pMySh->GetLayout());
1428 const OUString& aExpandText = aConversionMap.getViewText();
1429 sal_Int32 nSentenceEnd =
1430 aConversionMap.ConvertToViewPosition( aGrammarResult.nBehindEndOfSentencePosition );
1431 // remove trailing space
1432 if( aExpandText[nSentenceEnd - 1] == ' ' )
1433 --nSentenceEnd;
1434 if( pCursor->End()->GetContentIndex() < nSentenceEnd )
1436 pCursor->End()->SetContent(nSentenceEnd);
1440 lcl_CutRedlines( aDeletedRedlines, pMySh );
1441 // save the 'global' end of the spellchecking
1442 const SwPosition aSaveEndPos = *GetEnd();
1443 // set the sentence end as 'local' end
1444 SetEnd( new SwPosition( *pCursor->End() ));
1446 *pCursor->GetPoint() = aSaveStartPos;
1447 *pCursor->GetMark() = *GetEnd();
1448 // now the rest of the sentence has to be searched for errors
1449 // for each error the non-error text between the current and the last error has
1450 // to be added to the portions - if necessary broken into same-language-portions
1451 if( !bGrammarErrorFound ) //in grammar check there's only one error returned
1455 xSpellRet = nullptr;
1456 // don't search for grammar errors here anymore!
1457 pMySh->GetDoc()->Spell(*pCursor, m_xSpeller, nullptr, nullptr, false,
1458 pMySh->GetLayout())
1459 >>= xSpellRet;
1460 if ( *pCursor->GetPoint() > *pCursor->GetMark() )
1461 pCursor->Exchange();
1462 SetCurr( new SwPosition( *pCursor->GetPoint() ));
1463 SetCurrX( new SwPosition( *pCursor->GetMark() ));
1465 // if an error has been found go back to the text preceding the error
1466 if(xSpellRet.is())
1468 *pCursor->GetPoint() = aSaveStartPos;
1469 *pCursor->GetMark() = *GetCurr();
1471 // add the portion
1472 AddPortion(nullptr, nullptr, aDeletedRedlines);
1474 if(xSpellRet.is())
1476 *pCursor->GetPoint() = *GetCurr();
1477 *pCursor->GetMark() = *GetCurrX();
1478 AddPortion(xSpellRet, nullptr, aDeletedRedlines);
1479 // move the cursor to the end of the error string
1480 *pCursor->GetPoint() = *GetCurrX();
1481 // and save the end of the error as new start position
1482 aSaveStartPos = *GetCurrX();
1483 // and the end of the sentence
1484 *pCursor->GetMark() = *GetEnd();
1486 // if the end of the sentence has already been reached then break here
1487 if(*GetCurrX() >= *GetEnd())
1488 break;
1490 while(xSpellRet.is());
1492 else
1494 // go to the end of sentence as the grammar check returned it
1495 // at this time the Point is behind the grammar error
1496 // and the mark points to the sentence end as
1497 if ( *pCursor->GetPoint() < *pCursor->GetMark() )
1498 pCursor->Exchange();
1501 // the part between the last error and the end of the sentence has to be added
1502 *pMySh->GetCursor()->GetPoint() = *GetEnd();
1503 if(*GetCurrX() < *GetEnd())
1505 AddPortion(nullptr, nullptr, aDeletedRedlines);
1507 // set the shell cursor to the end of the sentence to prevent a visible selection
1508 *pCursor->GetMark() = *GetEnd();
1509 if( !bIsGrammarCheck )
1511 // set the current position to the end of the sentence
1512 SetCurr( new SwPosition(*GetEnd()) );
1514 // restore the 'global' end
1515 SetEnd( new SwPosition(aSaveEndPos) );
1516 rPortions = m_aLastPortions;
1517 bRet = true;
1519 else
1521 // if no error could be found the selection has to be corrected - at least if it's not in the body
1522 *pMySh->GetCursor()->GetPoint() = *GetEnd();
1523 pMySh->GetCursor()->DeleteMark();
1526 return bRet;
1529 void SwSpellIter::ToSentenceStart() { m_bBackToStartOfSentence = true; }
1531 static LanguageType lcl_GetLanguage(SwEditShell& rSh)
1533 SvtScriptType nScriptType = rSh.GetScriptType();
1534 sal_uInt16 nLangWhichId = RES_CHRATR_LANGUAGE;
1536 switch(nScriptType)
1538 case SvtScriptType::ASIAN : nLangWhichId = RES_CHRATR_CJK_LANGUAGE; break;
1539 case SvtScriptType::COMPLEX : nLangWhichId = RES_CHRATR_CTL_LANGUAGE; break;
1540 default: break;
1542 SfxItemSet aSet(rSh.GetAttrPool(), nLangWhichId, nLangWhichId);
1543 rSh.GetCurAttr( aSet );
1544 const SvxLanguageItem& rLang = static_cast<const SvxLanguageItem& >(aSet.Get(nLangWhichId));
1545 return rLang.GetLanguage();
1548 /// create a text portion at the given position
1549 void SwSpellIter::CreatePortion(uno::Reference< XSpellAlternatives > const & xAlt,
1550 const linguistic2::ProofreadingResult* pGrammarResult,
1551 bool bIsField, bool bIsHidden)
1553 svx::SpellPortion aPortion;
1554 OUString sText;
1555 GetSh()->GetSelectedText( sText );
1556 if(sText.isEmpty())
1557 return;
1559 // in case of redlined deletions the selection of an error is not the same as the _real_ word
1560 if(xAlt.is())
1561 aPortion.sText = xAlt->getWord();
1562 else if(pGrammarResult)
1564 aPortion.bIsGrammarError = true;
1565 if(pGrammarResult->aErrors.hasElements())
1567 aPortion.aGrammarError = pGrammarResult->aErrors[0];
1568 aPortion.sText = pGrammarResult->aText.copy( aPortion.aGrammarError.nErrorStart, aPortion.aGrammarError.nErrorLength );
1569 aPortion.xGrammarChecker = pGrammarResult->xProofreader;
1570 auto pProperty = std::find_if(std::cbegin(pGrammarResult->aProperties), std::cend(pGrammarResult->aProperties),
1571 [](const beans::PropertyValue& rProperty) { return rProperty.Name == "DialogTitle"; });
1572 if (pProperty != std::cend(pGrammarResult->aProperties))
1573 pProperty->Value >>= aPortion.sDialogTitle;
1576 else
1577 aPortion.sText = sText;
1578 aPortion.eLanguage = lcl_GetLanguage(*GetSh());
1579 aPortion.bIsField = bIsField;
1580 aPortion.bIsHidden = bIsHidden;
1581 aPortion.xAlternatives = xAlt;
1582 SpellContentPosition aPosition;
1583 SwPaM *pCursor = GetSh()->GetCursor();
1584 aPosition.nLeft = pCursor->Start()->GetContentIndex();
1585 aPosition.nRight = pCursor->End()->GetContentIndex();
1586 m_aLastPortions.push_back(aPortion);
1587 m_aLastPositions.push_back(aPosition);
1590 void SwSpellIter::AddPortion(uno::Reference< XSpellAlternatives > const & xAlt,
1591 const linguistic2::ProofreadingResult* pGrammarResult,
1592 const SpellContentPositions& rDeletedRedlines)
1594 SwEditShell *pMySh = GetSh();
1595 OUString sText;
1596 pMySh->GetSelectedText( sText );
1597 if(sText.isEmpty())
1598 return;
1600 if(xAlt.is() || pGrammarResult != nullptr)
1602 CreatePortion(xAlt, pGrammarResult, false, false);
1604 else
1606 SwPaM *pCursor = GetSh()->GetCursor();
1607 if ( *pCursor->GetPoint() > *pCursor->GetMark() )
1608 pCursor->Exchange();
1609 // save the start and end positions
1610 SwPosition aStart(*pCursor->GetPoint());
1611 SwPosition aEnd(*pCursor->GetMark());
1612 // iterate over the text to find changes in language
1613 // set the mark equal to the point
1614 *pCursor->GetMark() = aStart;
1615 SwTextNode* pTextNode = pCursor->GetPointNode().GetTextNode();
1616 LanguageType eStartLanguage = lcl_GetLanguage(*GetSh());
1617 SpellContentPosition aNextRedline = lcl_FindNextDeletedRedline(
1618 rDeletedRedlines, aStart.GetContentIndex() );
1619 if( aNextRedline.nLeft == aStart.GetContentIndex() )
1621 // select until the end of the current redline
1622 const sal_Int32 nEnd = aEnd.GetContentIndex() < aNextRedline.nRight ?
1623 aEnd.GetContentIndex() : aNextRedline.nRight;
1624 pCursor->GetPoint()->SetContent( nEnd );
1625 CreatePortion(xAlt, pGrammarResult, false, true);
1626 aStart = *pCursor->End();
1627 // search for next redline
1628 aNextRedline = lcl_FindNextDeletedRedline(
1629 rDeletedRedlines, aStart.GetContentIndex() );
1631 while(*pCursor->GetPoint() < aEnd)
1633 // #125786 in table cell with fixed row height the cursor might not move forward
1634 if(!GetSh()->Right(1, SwCursorSkipMode::Cells))
1635 break;
1637 bool bField = false;
1638 // read the character at the current position to check if it's a field
1639 sal_Unicode const cChar =
1640 pTextNode->GetText()[pCursor->GetMark()->GetContentIndex()];
1641 if( CH_TXTATR_BREAKWORD == cChar || CH_TXTATR_INWORD == cChar)
1643 const SwTextAttr* pTextAttr = pTextNode->GetTextAttrForCharAt(
1644 pCursor->GetMark()->GetContentIndex() );
1645 const sal_uInt16 nWhich = pTextAttr
1646 ? pTextAttr->Which()
1647 : RES_TXTATR_END;
1648 switch (nWhich)
1650 case RES_TXTATR_FIELD:
1651 case RES_TXTATR_ANNOTATION:
1652 case RES_TXTATR_FTN:
1653 case RES_TXTATR_FLYCNT:
1654 bField = true;
1655 break;
1658 else if (cChar == CH_TXT_ATR_FORMELEMENT)
1660 SwPosition aPos(*pCursor->GetMark());
1661 bField = pMySh->GetDoc()->getIDocumentMarkAccess()->getDropDownFor(aPos);
1664 LanguageType eCurLanguage = lcl_GetLanguage(*GetSh());
1665 bool bRedline = aNextRedline.nLeft == pCursor->GetPoint()->GetContentIndex();
1666 // create a portion if the next character
1667 // - is a field,
1668 // - is at the beginning of a deleted redline
1669 // - has a different language
1670 if(bField || bRedline || eCurLanguage != eStartLanguage)
1672 eStartLanguage = eCurLanguage;
1673 // go one step back - the cursor currently selects the first character
1674 // with a different language
1675 // in the case of redlining it's different
1676 if(eCurLanguage != eStartLanguage || bField)
1677 *pCursor->GetPoint() = *pCursor->GetMark();
1678 // set to the last start
1679 *pCursor->GetMark() = aStart;
1680 // create portion should only be called if a selection exists
1681 // there's no selection if there's a field at the beginning
1682 if(*pCursor->Start() != *pCursor->End())
1683 CreatePortion(xAlt, pGrammarResult, false, false);
1684 aStart = *pCursor->End();
1685 // now export the field - if there is any
1686 if(bField)
1688 *pCursor->GetMark() = *pCursor->GetPoint();
1689 GetSh()->Right(1, SwCursorSkipMode::Cells);
1690 CreatePortion(xAlt, pGrammarResult, true, false);
1691 aStart = *pCursor->End();
1694 // if a redline start then create a portion for it
1695 if(bRedline)
1697 *pCursor->GetMark() = *pCursor->GetPoint();
1698 // select until the end of the current redline
1699 const sal_Int32 nEnd = aEnd.GetContentIndex() < aNextRedline.nRight ?
1700 aEnd.GetContentIndex() : aNextRedline.nRight;
1701 pCursor->GetPoint()->SetContent( nEnd );
1702 CreatePortion(xAlt, pGrammarResult, false, true);
1703 aStart = *pCursor->End();
1704 // search for next redline
1705 aNextRedline = lcl_FindNextDeletedRedline(
1706 rDeletedRedlines, aStart.GetContentIndex() );
1708 *pCursor->GetMark() = *pCursor->GetPoint();
1710 pCursor->SetMark();
1711 *pCursor->GetMark() = aStart;
1712 CreatePortion(xAlt, pGrammarResult, false, false);
1716 void SwEditShell::IgnoreGrammarErrorAt( SwPaM& rErrorPosition )
1718 SwTextNode *pNode;
1719 SwWrongList *pWrong;
1720 SwNodeIndex aIdx(rErrorPosition.Start()->GetNode());
1721 SwNodeIndex aEndIdx(rErrorPosition.Start()->GetNode());
1722 sal_Int32 nStart = rErrorPosition.Start()->GetContentIndex();
1723 sal_Int32 nEnd = COMPLETE_STRING;
1724 while( aIdx <= aEndIdx )
1726 pNode = aIdx.GetNode().GetTextNode();
1727 if( pNode ) {
1728 if( aIdx == aEndIdx )
1729 nEnd = rErrorPosition.End()->GetContentIndex();
1730 pWrong = pNode->GetGrammarCheck();
1731 if( pWrong )
1732 pWrong->RemoveEntry( nStart, nEnd );
1733 pWrong = pNode->GetWrong();
1734 if( pWrong )
1735 pWrong->RemoveEntry( nStart, nEnd );
1736 SwTextFrame::repaintTextFrames( *pNode );
1738 ++aIdx;
1739 nStart = 0;
1743 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */