1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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>
41 #include <IDocumentUndoRedo.hxx>
42 #include <IDocumentRedlineAccess.hxx>
43 #include <rootfrm.hxx>
47 #include <viewopt.hxx>
48 #include <SwGrammarMarkUp.hxx>
51 #include <splargs.hxx>
52 #include <redline.hxx>
55 #include <txatbase.hxx>
57 #include <comphelper/propertyvalue.hxx>
58 #include <unotxdoc.hxx>
60 using namespace ::svx
;
61 using namespace ::com::sun::star
;
62 using namespace ::com::sun::star::uno
;
63 using namespace ::com::sun::star::beans
;
64 using namespace ::com::sun::star::linguistic2
;
72 std::optional
<SwPosition
> m_oStart
;
73 std::optional
<SwPosition
> m_oEnd
;
74 std::optional
<SwPosition
> m_oCurr
;
75 std::optional
<SwPosition
> m_oCurrX
;
76 sal_uInt16 m_nCursorCount
;
80 SwEditShell
* GetSh() { return m_pSh
; }
82 sal_uInt16
& GetCursorCnt() { return m_nCursorCount
; }
85 void Start_( SwEditShell
*pSh
, SwDocPositions eStart
,
86 SwDocPositions eEnd
);
87 void End_(bool bRestoreSelection
= true);
90 // #i18881# to be able to identify the positions of the changed words
91 // the content positions of each portion need to be saved
92 struct SpellContentPosition
100 typedef std::vector
<SpellContentPosition
> SpellContentPositions
;
104 class SwSpellIter
: public SwLinguIter
106 uno::Reference
<XSpellChecker1
> m_xSpeller
;
107 svx::SpellPortions m_aLastPortions
;
109 SpellContentPositions m_aLastPositions
;
110 bool m_bBackToStartOfSentence
;
112 void CreatePortion(uno::Reference
< XSpellAlternatives
> const & xAlt
,
113 const linguistic2::ProofreadingResult
* pGrammarResult
,
114 bool bIsField
, bool bIsHidden
);
116 void AddPortion(uno::Reference
< XSpellAlternatives
> const & xAlt
,
117 const linguistic2::ProofreadingResult
* pGrammarResult
,
118 const SpellContentPositions
& rDeletedRedlines
);
121 : m_bBackToStartOfSentence(false)
125 void Start( SwEditShell
*pSh
, SwDocPositions eStart
, SwDocPositions eEnd
);
127 uno::Any
Continue( sal_uInt16
* pPageCnt
, sal_uInt16
* pPageSt
);
129 bool SpellSentence(svx::SpellPortions
& rPortions
, bool bIsGrammarCheck
);
130 void ToSentenceStart();
131 const svx::SpellPortions
& GetLastPortions() const { return m_aLastPortions
; }
132 const SpellContentPositions
& GetLastPositions() const { return m_aLastPositions
; }
135 /// used for text conversion
136 class SwConvIter
: public SwLinguIter
138 SwConversionArgs
& m_rArgs
;
141 explicit SwConvIter(SwConversionArgs
& rConvArgs
)
146 void Start( SwEditShell
*pSh
, SwDocPositions eStart
, SwDocPositions eEnd
);
148 uno::Any
Continue( sal_uInt16
* pPageCnt
, sal_uInt16
* pPageSt
);
151 class SwHyphIter
: public SwLinguIter
153 // With that we save a GetFrame() in Hyphenate //TODO: does it actually matter?
154 const SwTextNode
*m_pLastNode
;
155 SwTextFrame
*m_pLastFrame
;
156 friend SwTextFrame
* sw::SwHyphIterCacheLastTextFrame(SwTextNode
const * pNode
, const sw::Creator
& rCreator
);
159 static void DelSoftHyph( SwPaM
&rPam
);
163 : m_pLastNode(nullptr)
164 , m_pLastFrame(nullptr)
169 void Start( SwEditShell
*pSh
, SwDocPositions eStart
, SwDocPositions eEnd
);
174 uno::Any
Continue( sal_uInt16
* pPageCnt
, sal_uInt16
* pPageSt
);
176 static bool IsAuto();
177 void InsertSoftHyph( const sal_Int32 nHyphPos
);
178 void ShowSelection();
183 static SwSpellIter
* g_pSpellIter
= nullptr;
184 static SwConvIter
* g_pConvIter
= nullptr;
185 static SwHyphIter
* g_pHyphIter
= nullptr;
187 SwLinguIter::SwLinguIter()
191 // TODO missing: ensurance of re-entrance, OSL_ENSURE( etc.
194 void SwLinguIter::Start_( SwEditShell
*pShell
, SwDocPositions eStart
,
195 SwDocPositions eEnd
)
197 // TODO missing: ensurance of re-entrance, locking
205 CurrShell
aCurr(m_pSh
);
207 OSL_ENSURE(!m_oEnd
, "SwLinguIter::Start_ without End?");
209 SwPaM
* pCursor
= m_pSh
->GetCursor();
211 if( pShell
->HasSelection() || pCursor
!= pCursor
->GetNext() )
213 bSetCurr
= m_oCurr
.has_value();
214 m_nCursorCount
= m_pSh
->GetCursorCnt();
215 if (m_pSh
->IsTableMode())
216 m_pSh
->TableCursorToCursor();
220 for (n
= 0; n
< m_nCursorCount
; ++n
)
223 m_pSh
->DestroyCursor();
225 m_pSh
->Pop(SwCursorShell::PopMode::DeleteCurrent
);
232 m_pSh
->SetLinguRange(eStart
, eEnd
);
235 pCursor
= m_pSh
->GetCursor();
236 if ( *pCursor
->GetPoint() > *pCursor
->GetMark() )
239 m_oStart
.emplace(*pCursor
->GetPoint());
240 m_oEnd
.emplace(*pCursor
->GetMark());
243 m_oCurr
.emplace( *m_oStart
);
244 m_oCurrX
.emplace( *m_oCurr
);
250 void SwLinguIter::End_(bool bRestoreSelection
)
255 OSL_ENSURE(m_oEnd
, "SwLinguIter::End_ without end?");
256 if(bRestoreSelection
)
258 while (m_nCursorCount
--)
259 m_pSh
->Pop(SwCursorShell::PopMode::DeleteCurrent
);
272 void SwSpellIter::Start( SwEditShell
*pShell
, SwDocPositions eStart
,
273 SwDocPositions eEnd
)
278 m_xSpeller
= ::GetSpellChecker();
280 Start_( pShell
, eStart
, eEnd
);
281 m_aLastPortions
.clear();
282 m_aLastPositions
.clear();
285 // This method is the origin of SwEditShell::SpellContinue()
286 uno::Any
SwSpellIter::Continue( sal_uInt16
* pPageCnt
, sal_uInt16
* pPageSt
)
289 //!! Please check SwConvIter also when modifying this
293 SwEditShell
*pMySh
= GetSh();
297 OSL_ENSURE( m_oEnd
, "SwSpellIter::Continue without start?");
299 uno::Reference
< uno::XInterface
> xSpellRet
;
302 SwPaM
*pCursor
= pMySh
->GetCursor();
303 if ( !pCursor
->HasMark() )
306 *pMySh
->GetCursor()->GetPoint() = *m_oCurr
;
307 *pMySh
->GetCursor()->GetMark() = *m_oEnd
;
308 pMySh
->GetDoc()->Spell(*pMySh
->GetCursor(), m_xSpeller
, pPageCnt
, pPageSt
, false,
311 bGoOn
= GetCursorCnt() > 1;
315 m_oCurr
.emplace( *pCursor
->GetPoint() );
316 m_oCurrX
.emplace( *pCursor
->GetMark() );
320 pMySh
->Pop(SwCursorShell::PopMode::DeleteCurrent
);
321 pCursor
= pMySh
->GetCursor();
322 if ( *pCursor
->GetPoint() > *pCursor
->GetMark() )
324 m_oStart
.emplace( *pCursor
->GetPoint() );
325 m_oEnd
.emplace( *pCursor
->GetMark() );
326 m_oCurr
.emplace( *m_oStart
);
327 m_oCurrX
.emplace( *m_oCurr
);
332 aSpellRet
<<= xSpellRet
;
336 void SwConvIter::Start( SwEditShell
*pShell
, SwDocPositions eStart
,
337 SwDocPositions eEnd
)
341 Start_( pShell
, eStart
, eEnd
);
344 uno::Any
SwConvIter::Continue( sal_uInt16
* pPageCnt
, sal_uInt16
* pPageSt
)
347 //!! Please check SwSpellIter also when modifying this
350 uno::Any aConvRet
{ OUString() };
351 SwEditShell
*pMySh
= GetSh();
355 OSL_ENSURE( m_oEnd
, "SwConvIter::Continue() without Start?");
360 SwPaM
*pCursor
= pMySh
->GetCursor();
361 if ( !pCursor
->HasMark() )
364 *pMySh
->GetCursor()->GetPoint() = *m_oCurr
;
365 *pMySh
->GetCursor()->GetMark() = *m_oEnd
;
367 // call function to find next text portion to be converted
368 uno::Reference
< linguistic2::XSpellChecker1
> xEmpty
;
369 pMySh
->GetDoc()->Spell(*pMySh
->GetCursor(), xEmpty
, pPageCnt
, pPageSt
, false,
370 pMySh
->GetLayout(), &m_rArgs
)
373 bGoOn
= GetCursorCnt() > 1;
374 if( !aConvText
.isEmpty() )
378 m_oCurr
.emplace( *pCursor
->GetPoint() );
379 m_oCurrX
.emplace( *pCursor
->GetMark() );
383 pMySh
->Pop(SwCursorShell::PopMode::DeleteCurrent
);
384 pCursor
= pMySh
->GetCursor();
385 if ( *pCursor
->GetPoint() > *pCursor
->GetMark() )
387 m_oStart
.emplace( *pCursor
->GetPoint() );
388 m_oEnd
.emplace( *pCursor
->GetMark() );
389 m_oCurr
.emplace( *m_oStart
);
390 m_oCurrX
.emplace( *m_oCurr
);
395 return Any( aConvText
);
398 bool SwHyphIter::IsAuto()
400 uno::Reference
< beans::XPropertySet
> xProp( ::GetLinguPropertySet() );
401 return xProp
.is() && *o3tl::doAccess
<bool>(xProp
->getPropertyValue(
405 void SwHyphIter::ShowSelection()
407 SwEditShell
*pMySh
= GetSh();
410 pMySh
->StartAction();
411 // Caution! Due to EndAction() formatting is started which can lead to the fact that new
412 // words are added to/set in the Hyphenator. Thus: save!
417 void SwHyphIter::Start( SwEditShell
*pShell
, SwDocPositions eStart
, SwDocPositions eEnd
)
420 if( GetSh() || m_oEnd
)
422 OSL_ENSURE( !GetSh(), "SwHyphIter::Start: missing HyphEnd()" );
426 // nothing to do (at least not in the way as in the "else" part)
427 m_bOldIdle
= pShell
->GetViewOptions()->IsIdle();
428 pShell
->GetViewOptions()->SetIdle( false );
429 Start_( pShell
, eStart
, eEnd
);
432 // restore selections
433 void SwHyphIter::End()
437 GetSh()->GetViewOptions()->SetIdle(m_bOldIdle
);
441 uno::Any
SwHyphIter::Continue( sal_uInt16
* pPageCnt
, sal_uInt16
* pPageSt
)
444 SwEditShell
*pMySh
= GetSh();
448 const bool bAuto
= IsAuto();
449 uno::Reference
< XHyphenatedWord
> xHyphWord
;
454 OSL_ENSURE( m_oEnd
, "SwHyphIter::Continue without Start?" );
455 pCursor
= pMySh
->GetCursor();
456 if ( !pCursor
->HasMark() )
458 if ( *pCursor
->GetPoint() < *pCursor
->GetMark() )
464 if ( *pCursor
->End() <= *m_oEnd
)
466 *pCursor
->GetMark() = *m_oEnd
;
468 // Do we need to break the word at the current cursor position?
469 const Point
aCursorPos( pMySh
->GetCharRect().Pos() );
470 xHyphWord
= pMySh
->GetDoc()->Hyphenate( pCursor
, aCursorPos
,
474 if( bAuto
&& xHyphWord
.is() )
476 SwEditShell::InsertSoftHyph( xHyphWord
->getHyphenationPos() + 1);
478 } while( bAuto
&& xHyphWord
.is() ); //end of do-while
479 bGoOn
= !xHyphWord
.is() && GetCursorCnt() > 1;
483 pMySh
->Pop(SwCursorShell::PopMode::DeleteCurrent
);
484 pCursor
= pMySh
->GetCursor();
485 if ( *pCursor
->GetPoint() > *pCursor
->GetMark() )
487 m_oEnd
.emplace( *pCursor
->End() );
492 aHyphRet
<<= xHyphWord
;
496 /// ignore hyphenation
497 void SwHyphIter::Ignore()
499 SwEditShell
*pMySh
= GetSh();
500 SwPaM
*pCursor
= pMySh
->GetCursor();
502 // delete old SoftHyphen
503 DelSoftHyph( *pCursor
);
506 pCursor
->Start()->SetContent( pCursor
->End()->GetContentIndex() );
510 void SwHyphIter::DelSoftHyph( SwPaM
&rPam
)
512 const SwPosition
* pStart
= rPam
.Start();
513 const sal_Int32 nStart
= pStart
->GetContentIndex();
514 const sal_Int32 nEnd
= rPam
.End()->GetContentIndex();
515 SwTextNode
*pNode
= pStart
->GetNode().GetTextNode();
516 pNode
->DelSoftHyph( nStart
, nEnd
);
519 void SwHyphIter::InsertSoftHyph( const sal_Int32 nHyphPos
)
521 SwEditShell
*pMySh
= GetSh();
522 OSL_ENSURE( pMySh
, "SwHyphIter::InsertSoftHyph: missing HyphStart()");
526 SwPaM
*pCursor
= pMySh
->GetCursor();
527 auto [pSttPos
, pEndPos
] = pCursor
->StartEnd(); // SwPosition*
529 const sal_Int32 nLastHyphLen
= m_oEnd
->GetContentIndex() -
530 pSttPos
->GetContentIndex();
532 if( pSttPos
->GetNode() != pEndPos
->GetNode() || !nLastHyphLen
)
534 OSL_ENSURE( pSttPos
->GetNode() == pEndPos
->GetNode(),
535 "SwHyphIter::InsertSoftHyph: node warp during hyphenation" );
536 OSL_ENSURE(nLastHyphLen
, "SwHyphIter::InsertSoftHyph: missing HyphContinue()");
541 pMySh
->StartAction();
543 SwDoc
*pDoc
= pMySh
->GetDoc();
544 DelSoftHyph( *pCursor
);
545 pSttPos
->AdjustContent( +nHyphPos
);
546 SwPaM
aRg( *pSttPos
);
547 pDoc
->getIDocumentContentOperations().InsertString( aRg
, OUString(CHAR_SOFTHYPHEN
) );
550 pCursor
->DeleteMark();
558 SwHyphIterCacheLastTextFrame(SwTextNode
const * pNode
, const sw::Creator
& create
)
561 if (pNode
!= g_pHyphIter
->m_pLastNode
|| !g_pHyphIter
->m_pLastFrame
)
563 g_pHyphIter
->m_pLastNode
= pNode
;
564 g_pHyphIter
->m_pLastFrame
= create();
566 return g_pHyphIter
->m_pLastFrame
;
571 bool SwEditShell::HasLastSentenceGotGrammarChecked()
573 bool bTextWasGrammarChecked
= false;
576 const svx::SpellPortions
& aLastPortions( g_pSpellIter
->GetLastPortions() );
577 for (size_t i
= 0; i
< aLastPortions
.size() && !bTextWasGrammarChecked
; ++i
)
579 // bIsGrammarError is also true if the text was only checked but no
580 // grammar error was found. (That is if a ProofreadingResult was obtained in
581 // SwDoc::Spell and in turn bIsGrammarError was set in SwSpellIter::CreatePortion)
582 if (aLastPortions
[i
].bIsGrammarError
)
583 bTextWasGrammarChecked
= true;
586 return bTextWasGrammarChecked
;
589 bool SwEditShell::HasConvIter()
591 return nullptr != g_pConvIter
;
594 bool SwEditShell::HasHyphIter()
596 return nullptr != g_pHyphIter
;
599 void SwEditShell::SetLinguRange( SwDocPositions eStart
, SwDocPositions eEnd
)
601 SwPaM
*pCursor
= GetCursor();
602 MakeFindRange( eStart
, eEnd
, pCursor
);
603 if( *pCursor
->GetPoint() > *pCursor
->GetMark() )
607 void SwEditShell::SpellStart(
608 SwDocPositions eStart
, SwDocPositions eEnd
, SwDocPositions eCurr
,
609 SwConversionArgs
*pConvArgs
)
611 SwLinguIter
*pLinguIter
= nullptr;
613 // do not spell if interactive spelling is active elsewhere
614 if (!pConvArgs
&& !g_pSpellIter
)
616 g_pSpellIter
= new SwSpellIter
;
617 pLinguIter
= g_pSpellIter
;
619 // do not do text conversion if it is active elsewhere
620 if (pConvArgs
&& !g_pConvIter
)
622 g_pConvIter
= new SwConvIter( *pConvArgs
);
623 pLinguIter
= g_pConvIter
;
628 SwCursor
* pSwCursor
= GetCursor();
630 pLinguIter
->m_oCurr
.emplace( *pSwCursor
->GetPoint() );
631 pSwCursor
->FillFindPos( eCurr
, *pLinguIter
->m_oCurr
);
633 pLinguIter
->m_oCurrX
.emplace( *pLinguIter
->m_oCurr
);
636 if (!pConvArgs
&& g_pSpellIter
)
637 g_pSpellIter
->Start( this, eStart
, eEnd
);
638 if (pConvArgs
&& g_pConvIter
)
639 g_pConvIter
->Start( this, eStart
, eEnd
);
642 void SwEditShell::SpellEnd( SwConversionArgs
const *pConvArgs
, bool bRestoreSelection
)
644 if (!pConvArgs
&& g_pSpellIter
&& g_pSpellIter
->GetSh() == this)
646 g_pSpellIter
->End_(bRestoreSelection
);
648 g_pSpellIter
= nullptr;
650 if (pConvArgs
&& g_pConvIter
&& g_pConvIter
->GetSh() == this)
654 g_pConvIter
= nullptr;
658 /// @returns SPL_ return values as in splchk.hxx
659 uno::Any
SwEditShell::SpellContinue(
660 sal_uInt16
* pPageCnt
, sal_uInt16
* pPageSt
,
661 SwConversionArgs
const *pConvArgs
)
665 if ((!pConvArgs
&& g_pSpellIter
->GetSh() != this) ||
666 ( pConvArgs
&& g_pConvIter
->GetSh() != this))
669 if( pPageCnt
&& !*pPageCnt
)
671 sal_uInt16 nEndPage
= GetLayout()->GetPageNum();
672 nEndPage
+= nEndPage
* 10 / 100;
673 *pPageCnt
= nEndPage
;
675 ::StartProgress( STR_STATSTR_SPELL
, 0, nEndPage
, GetDoc()->GetDocShell() );
678 OSL_ENSURE( pConvArgs
|| g_pSpellIter
, "SpellIter missing" );
679 OSL_ENSURE( !pConvArgs
|| g_pConvIter
, "ConvIter missing" );
680 //JP 18.07.95: prevent displaying selection on error messages. NO StartAction so that all
681 // Paints are also disabled.
684 uno::Reference
< uno::XInterface
> xRet
;
687 g_pConvIter
->Continue( pPageCnt
, pPageSt
) >>= aRet
;
692 g_pSpellIter
->Continue( pPageCnt
, pPageSt
) >>= xRet
;
697 if( !aRet
.isEmpty() || xRet
.is() )
699 // then make awt::Selection again visible
706 /* Interactive Hyphenation (BP 10.03.93)
709 * - Revoke all Selections
710 * - Save current Cursor
711 * - if no selections existent:
712 * - create new selection reaching until document end
714 * - add nLastHyphLen onto SelectionStart
715 * - iterate over all selected areas
716 * - pDoc->Hyphenate() iterates over all Nodes of a selection
717 * - pTextNode->Hyphenate() calls SwTextFrame::Hyphenate of the EditShell
718 * - SwTextFrame:Hyphenate() iterates over all rows of the Pam
719 * - LineIter::Hyphenate() sets the Hyphenator and the Pam based on
720 * the to be separated word.
721 * - Returns true if there is a hyphenation and false if the Pam is processed.
722 * - If true, show the selected word and set nLastHyphLen.
723 * - If false, delete current selection and select next one. Returns HYPH_OK if no more.
724 * 3) InsertSoftHyph (might be called by UI if needed)
725 * - Place current cursor and add attribute.
727 * - Restore old cursor, EndAction
729 void SwEditShell::HyphStart( SwDocPositions eStart
, SwDocPositions eEnd
)
731 // do not hyphenate if interactive hyphenation is active elsewhere
734 g_pHyphIter
= new SwHyphIter
;
735 g_pHyphIter
->Start( this, eStart
, eEnd
);
739 /// restore selections
740 void SwEditShell::HyphEnd()
743 if (g_pHyphIter
->GetSh() == this)
747 g_pHyphIter
= nullptr;
751 /// @returns HYPH_CONTINUE if hyphenation, HYPH_OK if selected area was processed.
752 uno::Reference
< uno::XInterface
>
753 SwEditShell::HyphContinue( sal_uInt16
* pPageCnt
, sal_uInt16
* pPageSt
)
756 if (g_pHyphIter
->GetSh() != this)
759 if( pPageCnt
&& !*pPageCnt
&& !*pPageSt
)
761 sal_uInt16 nEndPage
= GetLayout()->GetPageNum();
762 nEndPage
+= nEndPage
* 10 / 100;
765 *pPageCnt
= nEndPage
;
766 ::StartProgress( STR_STATSTR_HYPHEN
, 0, nEndPage
, GetDoc()->GetDocShell());
768 else // here we once and for all suppress StatLineStartPercent
772 //JP 18.07.95: prevent displaying selection on error messages. NO StartAction so that all
773 // Paints are also disabled.
775 uno::Reference
< uno::XInterface
> xRet
;
776 g_pHyphIter
->Continue( pPageCnt
, pPageSt
) >>= xRet
;
780 g_pHyphIter
->ShowSelection();
785 /** Insert soft hyphen
787 * @param nHyphPos Offset in the to be separated word
789 void SwEditShell::InsertSoftHyph( const sal_Int32 nHyphPos
)
792 g_pHyphIter
->InsertSoftHyph( nHyphPos
);
795 /// ignore hyphenation
796 void SwEditShell::HyphIgnore()
799 //JP 18.07.95: prevent displaying selection on error messages. NO StartAction so that all
800 // Paints are also disabled.
802 g_pHyphIter
->Ignore();
805 g_pHyphIter
->ShowSelection();
808 void SwEditShell::HandleCorrectionError(const OUString
& aText
, SwPosition aPos
, sal_Int32 nBegin
,
809 sal_Int32 nLen
, const Point
* pPt
,
812 // save the start and end positions of the line and the starting point
813 SwNode
const& rNode(GetCursor()->GetPoint()->GetNode());
816 const sal_Int32 nLineStart
= &rNode
== &GetCursor()->GetPoint()->GetNode()
817 ? GetCursor()->GetPoint()->GetContentIndex()
820 const sal_Int32 nLineEnd
= &rNode
== &GetCursor()->GetPoint()->GetNode()
821 ? GetCursor()->GetPoint()->GetContentIndex()
822 : rNode
.GetTextNode()->Len();
823 Pop(PopMode::DeleteCurrent
);
825 // make sure the selection build later from the data below does
826 // not "in word" character to the left and right in order to
827 // preserve those. Therefore count those "in words" in order to
828 // modify the selection accordingly.
829 const sal_Unicode
* pChar
= aText
.getStr();
831 while (*pChar
++ == CH_TXTATR_INWORD
)
833 pChar
= aText
.getLength() ? aText
.getStr() + aText
.getLength() - 1 : nullptr;
834 sal_Int32 nRight
= 0;
835 while (pChar
&& *pChar
-- == CH_TXTATR_INWORD
)
838 aPos
.SetContent( nBegin
+ nLeft
);
839 SwPaM
* pCursor
= GetCursor();
840 *pCursor
->GetPoint() = std::move(aPos
);
842 ExtendSelection( true, nLen
- nLeft
- nRight
);
843 // don't determine the rectangle in the current line
844 const sal_Int32 nWordStart
= (nBegin
+ nLeft
) < nLineStart
? nLineStart
: nBegin
+ nLeft
;
845 // take one less than the line end - otherwise the next line would be calculated
846 const sal_Int32 nWordEnd
= (nBegin
+ nLen
- nLeft
- nRight
) > nLineEnd
847 ? nLineEnd
: (nBegin
+ nLen
- nLeft
- nRight
);
849 pCursor
->DeleteMark();
850 SwPosition
& rPtPos
= *GetCursor()->GetPoint();
851 rPtPos
.SetContent(nWordStart
);
853 SwCursorMoveState aState
;
854 aState
.m_bRealWidth
= true;
855 SwContentNode
* pContentNode
= pCursor
->GetPointContentNode();
856 std::pair
<Point
, bool> tmp
;
862 SwContentFrame
*const pContentFrame
= pContentNode
->getLayoutFrame(GetLayout(), pCursor
->GetPoint(), pPt
? &tmp
: nullptr);
864 pContentFrame
->GetCharRect( aStartRect
, *pCursor
->GetPoint(), &aState
);
865 rPtPos
.SetContent(nWordEnd
- 1);
867 pContentFrame
->GetCharRect( aEndRect
, *pCursor
->GetPoint(),&aState
);
868 rSelectRect
= aStartRect
.Union( aEndRect
);
869 Pop(PopMode::DeleteCurrent
);
872 /** Get a list of potential corrections for misspelled word.
874 * If empty, word is unknown but there are no corrections available.
875 * If NULL then the word is not misspelled but correct.
877 * @brief SwEditShell::GetCorrection
878 * @return list or NULL pointer
880 uno::Reference
< XSpellAlternatives
>
881 SwEditShell::GetCorrection( const Point
* pPt
, SwRect
& rSelectRect
)
883 uno::Reference
< XSpellAlternatives
> xSpellAlt
;
887 SwPaM
* pCursor
= GetCursor();
888 SwPosition
aPos( *pCursor
->GetPoint() );
889 SwCursorMoveState
eTmpState( CursorMoveState::SetOnlyText
);
890 eTmpState
.m_bPosMatchesBounds
= true; // treat last half of character same as first half
891 SwTextNode
*pNode
= nullptr;
892 SwWrongList
*pWrong
= nullptr;
893 if (pPt
&& GetLayout()->GetModelPositionForViewPoint( &aPos
, *const_cast<Point
*>(pPt
), &eTmpState
))
894 pNode
= aPos
.GetNode().GetTextNode();
895 if (nullptr == pNode
)
896 pNode
= pCursor
->GetPointNode().GetTextNode();
897 if (nullptr != pNode
)
898 pWrong
= pNode
->GetWrong();
899 if (nullptr != pWrong
&& !pNode
->IsInProtectSect())
901 sal_Int32 nBegin
= aPos
.GetContentIndex();
903 if (pWrong
->InWrongWord(nBegin
, nLen
) && !pNode
->IsSymbolAt(nBegin
))
905 const OUString
aText(pNode
->GetText().copy(nBegin
, nLen
));
906 // TODO: this doesn't handle fieldmarks properly
907 ModelToViewHelper
const aConversionMap(*pNode
, GetLayout(),
908 ExpandMode::ExpandFields
| ExpandMode::ExpandFootnote
| ExpandMode::ReplaceMode
909 | ExpandMode::HideFieldmarkCommands
910 | (GetLayout()->IsHideRedlines() ? ExpandMode::HideDeletions
: ExpandMode(0))
911 | (GetViewOptions()->IsShowHiddenChar() ? ExpandMode(0) : ExpandMode::HideInvisible
));
912 auto const nBeginView(aConversionMap
.ConvertToViewPosition(nBegin
));
913 OUString
const aWord(aConversionMap
.getViewText().copy(nBeginView
,
914 aConversionMap
.ConvertToViewPosition(nBegin
+nLen
) - nBeginView
));
916 uno::Reference
< XSpellChecker1
> xSpell( ::GetSpellChecker() );
919 LanguageType eActLang
= pNode
->GetLang( nBegin
, nLen
);
920 if( xSpell
->hasLanguage( static_cast<sal_uInt16
>(eActLang
) ))
922 // restrict the maximal number of suggestions displayed
923 // in the context menu.
924 // Note: That could of course be done by clipping the
925 // resulting sequence but the current third party
926 // implementations result differs greatly if the number of
927 // suggestions to be returned gets changed. Statistically
928 // it gets much better if told to return e.g. only 7 strings
929 // than returning e.g. 16 suggestions and using only the
930 // first 7. Thus we hand down the value to use to that
931 // implementation here by providing an additional parameter.
932 Sequence
< PropertyValue
> aPropVals ( { comphelper::makePropertyValue( UPN_MAX_NUMBER_OF_SUGGESTIONS
, sal_Int16(7)) } );
934 xSpellAlt
= xSpell
->spell( aWord
, static_cast<sal_uInt16
>(eActLang
), aPropVals
);
938 if ( xSpellAlt
.is() ) // error found?
940 HandleCorrectionError( aText
, std::move(aPos
), nBegin
, nLen
, pPt
, rSelectRect
);
947 bool SwEditShell::GetGrammarCorrection(
948 linguistic2::ProofreadingResult
/*out*/ &rResult
, // the complete result
949 sal_Int32
/*out*/ &rErrorPosInText
, // offset of error position in string that was grammar checked...
950 sal_Int32
/*out*/ &rErrorIndexInResult
, // index of error in rResult.aGrammarErrors
951 uno::Sequence
< OUString
> /*out*/ &rSuggestions
, // suggestions to be used for the error found
952 const Point
*pPt
, SwRect
&rSelectRect
)
959 SwPaM
* pCursor
= GetCursor();
960 SwPosition
aPos( *pCursor
->GetPoint() );
961 SwCursorMoveState
eTmpState( CursorMoveState::SetOnlyText
);
962 eTmpState
.m_bPosMatchesBounds
= true; // treat last half of character same as first half
963 SwTextNode
*pNode
= nullptr;
964 SwGrammarMarkUp
*pWrong
= nullptr;
965 if (pPt
&& GetLayout()->GetModelPositionForViewPoint( &aPos
, *const_cast<Point
*>(pPt
), &eTmpState
))
966 pNode
= aPos
.GetNode().GetTextNode();
967 if (nullptr == pNode
)
968 pNode
= pCursor
->GetPointNode().GetTextNode();
969 if (nullptr != pNode
)
970 pWrong
= pNode
->GetGrammarCheck();
971 if (nullptr != pWrong
&& !pNode
->IsInProtectSect())
973 sal_Int32 nBegin
= aPos
.GetContentIndex();
975 if (pWrong
->InWrongWord(nBegin
, nLen
))
977 const OUString
aText(pNode
->GetText().copy(nBegin
, nLen
));
979 uno::Reference
< linguistic2::XProofreadingIterator
> xGCIterator( mxDoc
->GetGCIterator() );
980 if (xGCIterator
.is())
982 SwDocShell
* pShell
= mxDoc
->GetDocShell();
985 rtl::Reference
< SwXTextDocument
> xDoc
= pShell
->GetBaseModel();
987 // Expand the string:
988 const ModelToViewHelper
aConversionMap(*pNode
, GetLayout());
989 const OUString
& aExpandText
= aConversionMap
.getViewText();
990 // get XFlatParagraph to use...
991 uno::Reference
< text::XFlatParagraph
> xFlatPara
= new SwXFlatParagraph( *pNode
, aExpandText
, aConversionMap
);
993 // get error position of cursor in XFlatParagraph
994 rErrorPosInText
= aConversionMap
.ConvertToViewPosition( nBegin
);
996 const sal_Int32 nStartOfSentence
= aConversionMap
.ConvertToViewPosition( pWrong
->getSentenceStart( nBegin
) );
997 const sal_Int32 nEndOfSentence
= aConversionMap
.ConvertToViewPosition( pWrong
->getSentenceEnd( nBegin
) );
999 rResult
= xGCIterator
->checkSentenceAtPosition(
1000 cppu::getXWeak(xDoc
.get()), xFlatPara
, aExpandText
, lang::Locale(), nStartOfSentence
,
1001 nEndOfSentence
== COMPLETE_STRING
? aExpandText
.getLength() : nEndOfSentence
,
1005 // get suggestions to use for the specific error position
1006 rSuggestions
.realloc( 0 );
1007 // return suggestions for first error that includes the given error position
1008 auto pError
= std::find_if(std::cbegin(rResult
.aErrors
), std::cend(rResult
.aErrors
),
1009 [rErrorPosInText
, nLen
](const linguistic2::SingleProofreadingError
&rError
) {
1010 return rError
.nErrorStart
<= rErrorPosInText
1011 && rErrorPosInText
+ nLen
<= rError
.nErrorStart
+ rError
.nErrorLength
1012 && (rError
.aSuggestions
.hasElements() || !rError
.aShortComment
.isEmpty()); });
1013 if (pError
!= std::cend(rResult
.aErrors
))
1015 rSuggestions
= pError
->aSuggestions
;
1016 rErrorIndexInResult
= static_cast<sal_Int32
>(std::distance(std::cbegin(rResult
.aErrors
), pError
));
1020 if (rResult
.aErrors
.hasElements()) // error found?
1022 HandleCorrectionError( aText
, std::move(aPos
), nBegin
, nLen
, pPt
, rSelectRect
);
1030 bool SwEditShell::SpellSentence(svx::SpellPortions
& rPortions
, bool bIsGrammarCheck
)
1032 OSL_ENSURE( g_pSpellIter
, "SpellIter missing" );
1035 bool bRet
= g_pSpellIter
->SpellSentence(rPortions
, bIsGrammarCheck
);
1037 // make Selection visible - this should simply move the
1038 // cursor to the end of the sentence
1044 ///make SpellIter start with the current sentence when called next time
1045 void SwEditShell::PutSpellingToSentenceStart()
1047 OSL_ENSURE( g_pSpellIter
, "SpellIter missing" );
1050 g_pSpellIter
->ToSentenceStart();
1053 static sal_uInt32
lcl_CountRedlines(const svx::SpellPortions
& rLastPortions
)
1055 return static_cast<sal_uInt32
>(std::count_if(rLastPortions
.begin(), rLastPortions
.end(),
1056 [](const svx::SpellPortion
& rPortion
) { return rPortion
.bIsHidden
; }));
1059 void SwEditShell::MoveContinuationPosToEndOfCheckedSentence()
1061 // give hint that continuation position for spell/grammar checking is
1062 // at the end of this sentence
1065 g_pSpellIter
->m_oCurr
.emplace( *g_pSpellIter
->m_oCurrX
);
1069 void SwEditShell::ApplyChangedSentence(const svx::SpellPortions
& rNewPortions
, bool bRecheck
)
1071 // Note: rNewPortions.size() == 0 is valid and happens when the whole
1072 // sentence got removed in the dialog
1074 OSL_ENSURE( g_pSpellIter
, "SpellIter missing" );
1075 if (!g_pSpellIter
||
1076 g_pSpellIter
->GetLastPortions().empty()) // no portions -> no text to be changed
1079 const SpellPortions
& rLastPortions
= g_pSpellIter
->GetLastPortions();
1080 const SpellContentPositions rLastPositions
= g_pSpellIter
->GetLastPositions();
1081 OSL_ENSURE(!rLastPortions
.empty() &&
1082 rLastPortions
.size() == rLastPositions
.size(),
1083 "last vectors of spelling results are not set or not equal");
1085 // iterate over the new portions, beginning at the end to take advantage of the previously
1086 // saved content positions
1088 mxDoc
->GetIDocumentUndoRedo().StartUndo( SwUndoId::UI_TEXT_CORRECTION
, nullptr );
1091 SwPaM
*pCursor
= GetCursor();
1092 // save cursor position (which should be at the end of the current sentence)
1093 // for later restoration
1096 sal_uInt32 nRedlinePortions
= lcl_CountRedlines(rLastPortions
);
1097 if((rLastPortions
.size() - nRedlinePortions
) == rNewPortions
.size())
1099 OSL_ENSURE( !rNewPortions
.empty(), "rNewPortions should not be empty here" );
1100 OSL_ENSURE( !rLastPortions
.empty(), "rLastPortions should not be empty here" );
1101 OSL_ENSURE( !rLastPositions
.empty(), "rLastPositions should not be empty here" );
1103 // the simple case: the same number of elements on both sides
1104 // each changed element has to be applied to the corresponding source element
1105 svx::SpellPortions::const_iterator aCurrentNewPortion
= rNewPortions
.end();
1106 SpellPortions::const_iterator aCurrentOldPortion
= rLastPortions
.end();
1107 SpellContentPositions::const_iterator aCurrentOldPosition
= rLastPositions
.end();
1110 --aCurrentNewPortion
;
1111 --aCurrentOldPortion
;
1112 --aCurrentOldPosition
;
1113 //jump over redline portions
1114 while(aCurrentOldPortion
->bIsHidden
)
1116 if (aCurrentOldPortion
!= rLastPortions
.begin() &&
1117 aCurrentOldPosition
!= rLastPositions
.begin())
1119 --aCurrentOldPortion
;
1120 --aCurrentOldPosition
;
1124 OSL_FAIL("ApplyChangedSentence: iterator positions broken" );
1128 if ( !pCursor
->HasMark() )
1130 pCursor
->GetPoint()->SetContent( aCurrentOldPosition
->nLeft
);
1131 pCursor
->GetMark()->SetContent( aCurrentOldPosition
->nRight
);
1132 sal_uInt16 nScriptType
= SvtLanguageOptions::GetI18NScriptTypeOfLanguage( aCurrentNewPortion
->eLanguage
);
1133 sal_uInt16 nLangWhichId
= RES_CHRATR_LANGUAGE
;
1136 case css::i18n::ScriptType::ASIAN
: nLangWhichId
= RES_CHRATR_CJK_LANGUAGE
; break;
1137 case css::i18n::ScriptType::COMPLEX
: nLangWhichId
= RES_CHRATR_CTL_LANGUAGE
; break;
1139 if(aCurrentNewPortion
->sText
!= aCurrentOldPortion
->sText
)
1142 // ... and apply language if necessary
1143 if(aCurrentNewPortion
->eLanguage
!= aCurrentOldPortion
->eLanguage
)
1144 SetAttrItem( SvxLanguageItem(aCurrentNewPortion
->eLanguage
, nLangWhichId
) );
1146 // if there is a comment inside the original word, don't delete it:
1147 // but keep it at the end of the replacement
1148 ReplaceKeepComments(aCurrentNewPortion
->sText
);
1150 else if(aCurrentNewPortion
->eLanguage
!= aCurrentOldPortion
->eLanguage
)
1153 SetAttrItem( SvxLanguageItem(aCurrentNewPortion
->eLanguage
, nLangWhichId
) );
1155 else if( aCurrentNewPortion
->bIgnoreThisError
)
1157 // add the 'ignore' markup to the TextNode's grammar ignore markup list
1158 IgnoreGrammarErrorAt( *pCursor
);
1159 OSL_FAIL("TODO: add ignore mark to text node");
1162 while(aCurrentNewPortion
!= rNewPortions
.begin());
1166 OSL_ENSURE( !rLastPositions
.empty(), "rLastPositions should not be empty here" );
1168 // select the complete sentence
1169 SpellContentPositions::const_iterator aCurrentEndPosition
= rLastPositions
.end();
1170 --aCurrentEndPosition
;
1171 SpellContentPositions::const_iterator aCurrentStartPosition
= rLastPositions
.begin();
1172 pCursor
->GetPoint()->SetContent( aCurrentStartPosition
->nLeft
);
1173 pCursor
->GetMark()->SetContent( aCurrentEndPosition
->nRight
);
1175 // delete the sentence completely
1176 mxDoc
->getIDocumentContentOperations().DeleteAndJoin(*pCursor
);
1177 for(const auto& rCurrentNewPortion
: rNewPortions
)
1179 // set the language attribute
1180 SvtScriptType nScriptType
= GetScriptType();
1181 sal_uInt16 nLangWhichId
= RES_CHRATR_LANGUAGE
;
1184 case SvtScriptType::ASIAN
: nLangWhichId
= RES_CHRATR_CJK_LANGUAGE
; break;
1185 case SvtScriptType::COMPLEX
: nLangWhichId
= RES_CHRATR_CTL_LANGUAGE
; break;
1188 SfxItemSet
aSet(GetAttrPool(), nLangWhichId
, nLangWhichId
);
1190 const SvxLanguageItem
& rLang
= static_cast<const SvxLanguageItem
& >(aSet
.Get(nLangWhichId
));
1191 if(rLang
.GetLanguage() != rCurrentNewPortion
.eLanguage
)
1192 SetAttrItem( SvxLanguageItem(rCurrentNewPortion
.eLanguage
, nLangWhichId
) );
1193 // insert the new string
1194 mxDoc
->getIDocumentContentOperations().InsertString(*pCursor
, rCurrentNewPortion
.sText
);
1196 // set the cursor to the end of the inserted string
1197 *pCursor
->Start() = *pCursor
->End();
1201 // restore cursor to the end of the sentence
1202 // (will work also if the sentence length has changed,
1203 // since cursors get updated automatically!)
1204 Pop(PopMode::DeleteCurrent
);
1206 // collapse cursor to the end of the modified sentence
1207 *pCursor
->Start() = *pCursor
->End();
1210 // in grammar check the current sentence has to be checked again
1213 // set continuation position for spell/grammar checking to the end of this sentence
1214 g_pSpellIter
->m_oCurr
.emplace( *pCursor
->Start() );
1216 mxDoc
->GetIDocumentUndoRedo().EndUndo( SwUndoId::UI_TEXT_CORRECTION
, nullptr );
1220 /** Collect all deleted redlines of the current text node
1221 * beginning at the start of the cursor position
1223 static SpellContentPositions
lcl_CollectDeletedRedlines(SwEditShell
const * pSh
)
1225 SpellContentPositions aRedlines
;
1226 SwDoc
* pDoc
= pSh
->GetDoc();
1227 const bool bShowChg
= IDocumentRedlineAccess::IsShowChanges( pDoc
->getIDocumentRedlineAccess().GetRedlineFlags() );
1230 SwPaM
*pCursor
= pSh
->GetCursor();
1231 const SwPosition
* pStartPos
= pCursor
->Start();
1232 const SwTextNode
* pTextNode
= pCursor
->GetPointNode().GetTextNode();
1234 SwRedlineTable::size_type nAct
= pDoc
->getIDocumentRedlineAccess().GetRedlinePos( *pTextNode
, RedlineType::Any
);
1235 const sal_Int32 nStartIndex
= pStartPos
->GetContentIndex();
1236 for ( ; nAct
< pDoc
->getIDocumentRedlineAccess().GetRedlineTable().size(); nAct
++ )
1238 const SwRangeRedline
* pRed
= pDoc
->getIDocumentRedlineAccess().GetRedlineTable()[ nAct
];
1240 if ( pRed
->Start()->GetNode() > *pTextNode
)
1243 if( RedlineType::Delete
== pRed
->GetType() )
1245 sal_Int32 nStart_
, nEnd_
;
1246 pRed
->CalcStartEnd( pTextNode
->GetIndex(), nStart_
, nEnd_
);
1247 sal_Int32 nStart
= nStart_
;
1248 sal_Int32 nEnd
= nEnd_
;
1249 if(nStart
>= nStartIndex
|| nEnd
>= nStartIndex
)
1251 SpellContentPosition aAdd
;
1252 aAdd
.nLeft
= nStart
;
1254 aRedlines
.push_back(aAdd
);
1262 /// remove the redline positions after the current selection
1263 static void lcl_CutRedlines( SpellContentPositions
& aDeletedRedlines
, SwEditShell
const * pSh
)
1265 if(!aDeletedRedlines
.empty())
1267 SwPaM
*pCursor
= pSh
->GetCursor();
1268 const SwPosition
* pEndPos
= pCursor
->End();
1269 const sal_Int32 nEnd
= pEndPos
->GetContentIndex();
1270 while(!aDeletedRedlines
.empty() &&
1271 aDeletedRedlines
.back().nLeft
> nEnd
)
1273 aDeletedRedlines
.pop_back();
1278 static SpellContentPosition
lcl_FindNextDeletedRedline(
1279 const SpellContentPositions
& rDeletedRedlines
,
1280 sal_Int32 nSearchFrom
)
1282 SpellContentPosition aRet
;
1283 aRet
.nLeft
= aRet
.nRight
= SAL_MAX_INT32
;
1284 if(!rDeletedRedlines
.empty())
1286 auto aIter
= std::find_if_not(rDeletedRedlines
.begin(), rDeletedRedlines
.end(),
1287 [nSearchFrom
](const SpellContentPosition
& rPos
) { return rPos
.nLeft
< nSearchFrom
; });
1288 if (aIter
!= rDeletedRedlines
.end())
1294 bool SwSpellIter::SpellSentence(svx::SpellPortions
& rPortions
, bool bIsGrammarCheck
)
1297 m_aLastPortions
.clear();
1298 m_aLastPositions
.clear();
1300 SwEditShell
*pMySh
= GetSh();
1304 OSL_ENSURE( m_oEnd
, "SwSpellIter::SpellSentence without Start?");
1306 uno::Reference
< XSpellAlternatives
> xSpellRet
;
1307 linguistic2::ProofreadingResult aGrammarResult
;
1309 bool bGrammarErrorFound
= false;
1311 SwPaM
*pCursor
= pMySh
->GetCursor();
1312 if ( !pCursor
->HasMark() )
1315 *pCursor
->GetPoint() = *m_oCurr
;
1316 *pCursor
->GetMark() = *m_oEnd
;
1318 if (m_bBackToStartOfSentence
)
1320 pMySh
->GoStartSentence();
1321 m_bBackToStartOfSentence
= false;
1323 uno::Any aSpellRet
= pMySh
->GetDoc()->Spell(*pCursor
, m_xSpeller
, nullptr, nullptr,
1324 bIsGrammarCheck
, pMySh
->GetLayout());
1325 aSpellRet
>>= xSpellRet
;
1326 aSpellRet
>>= aGrammarResult
;
1327 bGoOn
= GetCursorCnt() > 1;
1328 bGrammarErrorFound
= aGrammarResult
.aErrors
.hasElements();
1329 if( xSpellRet
.is() || bGrammarErrorFound
)
1333 m_oCurr
.emplace( *pCursor
->GetPoint() );
1334 m_oCurrX
.emplace( *pCursor
->GetMark() );
1338 pMySh
->Pop(SwCursorShell::PopMode::DeleteCurrent
);
1339 pCursor
= pMySh
->GetCursor();
1340 if ( *pCursor
->GetPoint() > *pCursor
->GetMark() )
1341 pCursor
->Exchange();
1342 m_oStart
.emplace( *pCursor
->GetPoint() );
1343 m_oEnd
.emplace( *pCursor
->GetMark() );
1344 m_oCurr
.emplace( *m_oStart
);
1345 m_oCurrX
.emplace( *m_oCurr
);
1351 if(xSpellRet
.is() || bGrammarErrorFound
)
1353 // an error has been found
1354 // To fill the spell portions the beginning of the sentence has to be found
1355 SwPaM
*pCursor
= pMySh
->GetCursor();
1356 // set the mark to the right if necessary
1357 if ( *pCursor
->GetPoint() > *pCursor
->GetMark() )
1358 pCursor
->Exchange();
1359 // the cursor has to be collapsed on the left to go to the start of the sentence - if sentence ends inside of the error
1360 pCursor
->DeleteMark();
1362 bool bStartSent
= pMySh
->GoStartSentence();
1363 SpellContentPositions aDeletedRedlines
= lcl_CollectDeletedRedlines(pMySh
);
1366 // create a portion from the start part
1367 AddPortion(nullptr, nullptr, aDeletedRedlines
);
1369 // Set the cursor to the error already found
1370 *pCursor
->GetPoint() = *m_oCurrX
;
1371 *pCursor
->GetMark() = *m_oCurr
;
1372 AddPortion(xSpellRet
, &aGrammarResult
, aDeletedRedlines
);
1374 // save the end position of the error to continue from here
1375 SwPosition aSaveStartPos
= *pCursor
->End();
1376 // determine the end of the current sentence
1377 if ( *pCursor
->GetPoint() < *pCursor
->GetMark() )
1378 pCursor
->Exchange();
1379 // again collapse to start marking after the end of the error
1380 pCursor
->DeleteMark();
1383 pMySh
->GoEndSentence();
1384 if( bGrammarErrorFound
)
1386 const ModelToViewHelper
aConversionMap(static_cast<SwTextNode
&>(pCursor
->GetPointNode()), pMySh
->GetLayout());
1387 const OUString
& aExpandText
= aConversionMap
.getViewText();
1388 sal_Int32 nSentenceEnd
=
1389 aConversionMap
.ConvertToViewPosition( aGrammarResult
.nBehindEndOfSentencePosition
);
1390 // remove trailing space
1391 if( aExpandText
[nSentenceEnd
- 1] == ' ' )
1393 if( pCursor
->End()->GetContentIndex() < nSentenceEnd
)
1395 pCursor
->End()->SetContent(nSentenceEnd
);
1399 lcl_CutRedlines( aDeletedRedlines
, pMySh
);
1400 // save the 'global' end of the spellchecking
1401 const SwPosition aSaveEndPos
= *m_oEnd
;
1402 // set the sentence end as 'local' end
1403 m_oEnd
.emplace( *pCursor
->End() );
1405 *pCursor
->GetPoint() = aSaveStartPos
;
1406 *pCursor
->GetMark() = *m_oEnd
;
1407 // now the rest of the sentence has to be searched for errors
1408 // for each error the non-error text between the current and the last error has
1409 // to be added to the portions - if necessary broken into same-language-portions
1410 if( !bGrammarErrorFound
) //in grammar check there's only one error returned
1414 xSpellRet
= nullptr;
1415 // don't search for grammar errors here anymore!
1416 pMySh
->GetDoc()->Spell(*pCursor
, m_xSpeller
, nullptr, nullptr, false,
1419 if ( *pCursor
->GetPoint() > *pCursor
->GetMark() )
1420 pCursor
->Exchange();
1421 m_oCurr
.emplace( *pCursor
->GetPoint() );
1422 m_oCurrX
.emplace( *pCursor
->GetMark() );
1424 // if an error has been found go back to the text preceding the error
1427 *pCursor
->GetPoint() = aSaveStartPos
;
1428 *pCursor
->GetMark() = *m_oCurr
;
1431 AddPortion(nullptr, nullptr, aDeletedRedlines
);
1435 *pCursor
->GetPoint() = *m_oCurr
;
1436 *pCursor
->GetMark() = *m_oCurrX
;
1437 AddPortion(xSpellRet
, nullptr, aDeletedRedlines
);
1438 // move the cursor to the end of the error string
1439 *pCursor
->GetPoint() = *m_oCurrX
;
1440 // and save the end of the error as new start position
1441 aSaveStartPos
= *m_oCurrX
;
1442 // and the end of the sentence
1443 *pCursor
->GetMark() = *m_oEnd
;
1445 // if the end of the sentence has already been reached then break here
1446 if(*m_oCurrX
>= *m_oEnd
)
1449 while(xSpellRet
.is());
1453 // go to the end of sentence as the grammar check returned it
1454 // at this time the Point is behind the grammar error
1455 // and the mark points to the sentence end as
1456 if ( *pCursor
->GetPoint() < *pCursor
->GetMark() )
1457 pCursor
->Exchange();
1460 // the part between the last error and the end of the sentence has to be added
1461 *pMySh
->GetCursor()->GetPoint() = *m_oEnd
;
1462 if(*m_oCurrX
< *m_oEnd
)
1464 AddPortion(nullptr, nullptr, aDeletedRedlines
);
1466 // set the shell cursor to the end of the sentence to prevent a visible selection
1467 *pCursor
->GetMark() = *m_oEnd
;
1468 if( !bIsGrammarCheck
)
1470 // set the current position to the end of the sentence
1471 m_oCurr
.emplace( *m_oEnd
);
1473 // restore the 'global' end
1474 m_oEnd
.emplace( aSaveEndPos
);
1475 rPortions
= m_aLastPortions
;
1480 // if no error could be found the selection has to be corrected - at least if it's not in the body
1481 *pMySh
->GetCursor()->GetPoint() = *m_oEnd
;
1482 pMySh
->GetCursor()->DeleteMark();
1488 void SwSpellIter::ToSentenceStart() { m_bBackToStartOfSentence
= true; }
1490 static LanguageType
lcl_GetLanguage(SwEditShell
& rSh
)
1492 SvtScriptType nScriptType
= rSh
.GetScriptType();
1493 TypedWhichId
<SvxLanguageItem
> nLangWhichId
= RES_CHRATR_LANGUAGE
;
1497 case SvtScriptType::ASIAN
: nLangWhichId
= RES_CHRATR_CJK_LANGUAGE
; break;
1498 case SvtScriptType::COMPLEX
: nLangWhichId
= RES_CHRATR_CTL_LANGUAGE
; break;
1501 SfxItemSet
aSet(rSh
.GetAttrPool(), nLangWhichId
, nLangWhichId
);
1502 rSh
.GetCurAttr( aSet
);
1503 const SvxLanguageItem
& rLang
= aSet
.Get(nLangWhichId
);
1504 return rLang
.GetLanguage();
1507 /// create a text portion at the given position
1508 void SwSpellIter::CreatePortion(uno::Reference
< XSpellAlternatives
> const & xAlt
,
1509 const linguistic2::ProofreadingResult
* pGrammarResult
,
1510 bool bIsField
, bool bIsHidden
)
1512 svx::SpellPortion aPortion
;
1514 GetSh()->GetSelectedText( sText
);
1518 // in case of redlined deletions the selection of an error is not the same as the _real_ word
1520 aPortion
.sText
= xAlt
->getWord();
1521 else if(pGrammarResult
)
1523 aPortion
.bIsGrammarError
= true;
1524 if(pGrammarResult
->aErrors
.hasElements())
1526 aPortion
.aGrammarError
= pGrammarResult
->aErrors
[0];
1527 aPortion
.sText
= pGrammarResult
->aText
.copy( aPortion
.aGrammarError
.nErrorStart
, aPortion
.aGrammarError
.nErrorLength
);
1528 aPortion
.xGrammarChecker
= pGrammarResult
->xProofreader
;
1529 auto pProperty
= std::find_if(std::cbegin(pGrammarResult
->aProperties
), std::cend(pGrammarResult
->aProperties
),
1530 [](const beans::PropertyValue
& rProperty
) { return rProperty
.Name
== "DialogTitle"; });
1531 if (pProperty
!= std::cend(pGrammarResult
->aProperties
))
1532 pProperty
->Value
>>= aPortion
.sDialogTitle
;
1536 aPortion
.sText
= sText
;
1537 aPortion
.eLanguage
= lcl_GetLanguage(*GetSh());
1538 aPortion
.bIsField
= bIsField
;
1539 aPortion
.bIsHidden
= bIsHidden
;
1540 aPortion
.xAlternatives
= xAlt
;
1541 SpellContentPosition aPosition
;
1542 SwPaM
*pCursor
= GetSh()->GetCursor();
1543 aPosition
.nLeft
= pCursor
->Start()->GetContentIndex();
1544 aPosition
.nRight
= pCursor
->End()->GetContentIndex();
1545 m_aLastPortions
.push_back(aPortion
);
1546 m_aLastPositions
.push_back(aPosition
);
1549 void SwSpellIter::AddPortion(uno::Reference
< XSpellAlternatives
> const & xAlt
,
1550 const linguistic2::ProofreadingResult
* pGrammarResult
,
1551 const SpellContentPositions
& rDeletedRedlines
)
1553 SwEditShell
*pMySh
= GetSh();
1555 pMySh
->GetSelectedText( sText
);
1559 if(xAlt
.is() || pGrammarResult
!= nullptr)
1561 CreatePortion(xAlt
, pGrammarResult
, false, false);
1565 SwPaM
*pCursor
= GetSh()->GetCursor();
1566 if ( *pCursor
->GetPoint() > *pCursor
->GetMark() )
1567 pCursor
->Exchange();
1568 // save the start and end positions
1569 SwPosition
aStart(*pCursor
->GetPoint());
1570 SwPosition
aEnd(*pCursor
->GetMark());
1571 // iterate over the text to find changes in language
1572 // set the mark equal to the point
1573 *pCursor
->GetMark() = aStart
;
1574 SwTextNode
* pTextNode
= pCursor
->GetPointNode().GetTextNode();
1575 LanguageType eStartLanguage
= lcl_GetLanguage(*GetSh());
1576 SpellContentPosition aNextRedline
= lcl_FindNextDeletedRedline(
1577 rDeletedRedlines
, aStart
.GetContentIndex() );
1578 if( aNextRedline
.nLeft
== aStart
.GetContentIndex() )
1580 // select until the end of the current redline
1581 const sal_Int32 nEnd
= aEnd
.GetContentIndex() < aNextRedline
.nRight
?
1582 aEnd
.GetContentIndex() : aNextRedline
.nRight
;
1583 pCursor
->GetPoint()->SetContent( nEnd
);
1584 CreatePortion(xAlt
, pGrammarResult
, false, true);
1585 aStart
= *pCursor
->End();
1586 // search for next redline
1587 aNextRedline
= lcl_FindNextDeletedRedline(
1588 rDeletedRedlines
, aStart
.GetContentIndex() );
1590 while(*pCursor
->GetPoint() < aEnd
)
1592 // #125786 in table cell with fixed row height the cursor might not move forward
1593 if(!GetSh()->Right(1, SwCursorSkipMode::Cells
))
1596 bool bField
= false;
1597 // read the character at the current position to check if it's a field
1598 sal_Unicode
const cChar
=
1599 pTextNode
->GetText()[pCursor
->GetMark()->GetContentIndex()];
1600 if( CH_TXTATR_BREAKWORD
== cChar
|| CH_TXTATR_INWORD
== cChar
)
1602 const SwTextAttr
* pTextAttr
= pTextNode
->GetTextAttrForCharAt(
1603 pCursor
->GetMark()->GetContentIndex() );
1604 const sal_uInt16 nWhich
= pTextAttr
1605 ? pTextAttr
->Which()
1609 case RES_TXTATR_FIELD
:
1610 case RES_TXTATR_ANNOTATION
:
1611 case RES_TXTATR_FTN
:
1612 case RES_TXTATR_FLYCNT
:
1617 else if (cChar
== CH_TXT_ATR_FORMELEMENT
)
1619 SwPosition
aPos(*pCursor
->GetMark());
1620 bField
= pMySh
->GetDoc()->getIDocumentMarkAccess()->getDropDownFor(aPos
);
1623 LanguageType eCurLanguage
= lcl_GetLanguage(*GetSh());
1624 bool bRedline
= aNextRedline
.nLeft
== pCursor
->GetPoint()->GetContentIndex();
1625 // create a portion if the next character
1627 // - is at the beginning of a deleted redline
1628 // - has a different language
1629 if(bField
|| bRedline
|| eCurLanguage
!= eStartLanguage
)
1631 eStartLanguage
= eCurLanguage
;
1632 // go one step back - the cursor currently selects the first character
1633 // with a different language
1634 // in the case of redlining it's different
1635 if(eCurLanguage
!= eStartLanguage
|| bField
)
1636 *pCursor
->GetPoint() = *pCursor
->GetMark();
1637 // set to the last start
1638 *pCursor
->GetMark() = aStart
;
1639 // create portion should only be called if a selection exists
1640 // there's no selection if there's a field at the beginning
1641 if(*pCursor
->Start() != *pCursor
->End())
1642 CreatePortion(xAlt
, pGrammarResult
, false, false);
1643 aStart
= *pCursor
->End();
1644 // now export the field - if there is any
1647 *pCursor
->GetMark() = *pCursor
->GetPoint();
1648 GetSh()->Right(1, SwCursorSkipMode::Cells
);
1649 CreatePortion(xAlt
, pGrammarResult
, true, false);
1650 aStart
= *pCursor
->End();
1653 // if a redline start then create a portion for it
1656 *pCursor
->GetMark() = *pCursor
->GetPoint();
1657 // select until the end of the current redline
1658 const sal_Int32 nEnd
= aEnd
.GetContentIndex() < aNextRedline
.nRight
?
1659 aEnd
.GetContentIndex() : aNextRedline
.nRight
;
1660 pCursor
->GetPoint()->SetContent( nEnd
);
1661 CreatePortion(xAlt
, pGrammarResult
, false, true);
1662 aStart
= *pCursor
->End();
1663 // search for next redline
1664 aNextRedline
= lcl_FindNextDeletedRedline(
1665 rDeletedRedlines
, aStart
.GetContentIndex() );
1667 *pCursor
->GetMark() = *pCursor
->GetPoint();
1670 *pCursor
->GetMark() = std::move(aStart
);
1671 CreatePortion(xAlt
, pGrammarResult
, false, false);
1675 void SwEditShell::IgnoreGrammarErrorAt( SwPaM
& rErrorPosition
)
1678 SwWrongList
*pWrong
;
1679 SwNodeIndex
aIdx(rErrorPosition
.Start()->GetNode());
1680 SwNodeIndex
aEndIdx(rErrorPosition
.Start()->GetNode());
1681 sal_Int32 nStart
= rErrorPosition
.Start()->GetContentIndex();
1682 sal_Int32 nEnd
= COMPLETE_STRING
;
1683 while( aIdx
<= aEndIdx
)
1685 pNode
= aIdx
.GetNode().GetTextNode();
1687 if( aIdx
== aEndIdx
)
1688 nEnd
= rErrorPosition
.End()->GetContentIndex();
1689 pWrong
= pNode
->GetGrammarCheck();
1691 pWrong
->RemoveEntry( nStart
, nEnd
);
1692 pWrong
= pNode
->GetWrong();
1694 pWrong
->RemoveEntry( nStart
, nEnd
);
1695 SwTextFrame::repaintTextFrames( *pNode
);
1702 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */